高级进程注入技术:利用线程名与APC实现进程注入(下)
1. 前言
本文是《高级进程注入之利用线程名和APC实现进程注入》的下半部分,将深入探讨利用线程名(Thread Description)进行进程注入的实现细节、技术挑战及规避方案。与传统的CreateRemoteThread注入技术不同,此技术核心在于无需创建远程线程即可将Shellcode写入并执行于目标进程,有效降低了被AV/EDR检测的风险。
上篇回顾:主要介绍了实现此技术所需的关键Windows API,如SetThreadDescription, GetThreadDescription, QueueUserAPC2等。
2. 技术原理与优势
2.1 核心思想
通常的进程注入需要获取目标进程的写权限(PROCESS_VM_WRITE)以写入Shellcode,此行为易被安全软件标记为可疑。本技术通过利用系统API GetThreadDescription 的合法功能,诱使目标进程自己在内存中分配并写入Shellcode(即线程名),从而规避了对进程直接写入的敏感操作。
2.2 所需权限
为最小化可疑指标,应尽可能申请最少的权限。所需基础权限如下:
HANDLE open_process(DWORD processId, bool isCreateThread) {
DWORD access = PROCESS_QUERY_LIMITED_INFORMATION // 用于读取远程进程PEB
| PROCESS_VM_READ // 用于修改PEB中的字段
| PROCESS_VM_OPERATION; // 用于操作内存权限(如VirtualProtectEx)
if (isCreateThread) {
access |= PROCESS_CREATE_THREAD; // 备选方案:当需要创建新线程时才申请
}
return OpenProcess(access, FALSE, processId);
}
PROCESS_QUERY_LIMITED_INFORMATION: 用于获取远程进程的PEB(Process Environment Block)地址。PROCESS_VM_READ: 用于读取/修改PEB中的特定字段。PROCESS_VM_OPERATION: 用于后续调用VirtualProtectEx或VirtualAllocEx来更改内存属性或分配内存。PROCESS_CREATE_THREAD: 可选权限。仅在旧版Windows无法使用新型APC且找不到可警告线程时,作为备选方案(创建新线程)使用。应尽量避免使用以降低检测概率。
操作目标进程的线程所需的最小权限:
DWORD thAccess = SYNCHRONIZE;
thAccess |= THREAD_SET_CONTEXT; // 用于向线程APC队列添加项
thAccess |= THREAD_SET_LIMITED_INFORMATION; // 用于设置线程描述(即Shellcode)
3. 注入流程详解
任何Shellcode注入都包含三个核心步骤:
- 写入:将Shellcode写入远程进程内存。
- 权限:确保存放Shellcode的内存区域具有可执行(X)权限。
- 执行:控制执行流,跳转到Shellcode所在地址。
本技术通过以下方式实现这些步骤:
3.1 第一步:远程写入(无需PROCESS_VM_WRITE)
- Shellcode准备:确保Shellcode无空字节(NULL-byte free)且不会阻塞线程。
- 选择目标线程:
- 理想情况(新API):使用
QueueUserAPC2并设置QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC标志,可选用任意线程。 - 备选情况(旧API):必须找到一个处于可警告(Alertable)状态的线程,或自行创建一个(此时需要
PROCESS_CREATE_THREAD权限)。
- 理想情况(新API):使用
- “写入”机制:
- 在注入器进程中,使用
SetThreadDescription将一个伪句柄(NtCurrentThread())的线程名设置为我们的Shellcode(Unicode字符串形式)。使用伪句柄避免了需要PROCESS_DUP_HANDLE权限来复制远程线程真句柄的问题。 - 通过APC,在目标线程的上下文中排队一个任务,调用
GetThreadDescription。 GetThreadDescription函数被执行时,会自动在目标进程的堆上分配内存,并将线程名(即我们的Shellcode)写入该内存。该函数会将指向这块新内存的指针存入其输出参数ppszThreadDescription中。- 至此,Shellcode已被成功写入远程进程内存,且权限为RW(可读可写)。
- 在注入器进程中,使用
3.2 第二步:存储指针与定位PEB
GetThreadDescription返回的指针ppszThreadDescription需要存储在目标进程的一个已知地址,以便后续检索和使用。
首选方案:利用PEB中的未使用字段
- PEB结构体中包含一些保留或未使用的字段,它们是存储此指针的理想位置,因为PEB本身位于可读写(RW)的内存区域。
- 通过
NtQueryInformationProcess(查询ProcessBasicInformation)来获取目标进程的PEB基地址。 - 在64位Windows 10/11系统中,偏移量
+0x340处的SpareUlongs数组是一个合适的候选字段。ULONG_PTR get_peb_unused(HANDLE hProcess) { ULONG_PTR peb_addr = remote_peb_addr(hProcess); // 获取PEB地址 if (!peb_addr) return NULL; const ULONG_PTR UNUSED_OFFSET = 0x340; // 示例偏移,需根据系统版本调整 const ULONG_PTR remotePtr = peb_addr + UNUSED_OFFSET; return remotePtr; }注意:PEB结构可能随Windows版本更新而变化,此偏移量需要根据目标系统进行验证和调整(例如使用WinDbg的
dt nt!_PEB命令查看实际结构)。使用SpareUlongs相对安全,但未来可能被系统占用。
备选方案:在远程进程内存空间中搜索其他可写的已知地址。
3.3 第三步:分配可执行内存与权限变更
写入的Shellcode位于堆上,权限为RW,不可执行。需要将其转移至可执行的内存区域。
方案分析:
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 1. VirtualProtectEx | 直接调用VirtualProtectEx,将堆上的Shellcode内存页权限改为RWX。 |
步骤简单,无需额外拷贝。 | 高度可疑。直接创建RWX内存页是恶意软件的经典行为,易被内存扫描器检测。 |
| 2. VirtualAllocEx | 调用VirtualAllocEx申请新的RWX内存页,然后将Shellcode拷贝过去。 |
新内存页,相对干净。 | 可疑。申请RWX内存是可检测指标。无法通过APC直接调用(参数超过3个)。 |
| 3. ROP链 | 构造Return-Oriented Programming链来调用VirtualProtect或VirtualAlloc。 |
避免直接调用敏感API,更隐蔽。 | 实现复杂,需要操作线程上下文(Get/SetThreadContext),会产生更多可疑API调用,易触发告警。 |
结论与选择:
尽管VirtualProtectEx和VirtualAllocEx本身是可疑指标,但测试表明,在缺乏其他传统注入指标(如创建线程)的配合下,许多AV/EDR并不会仅因此而被触发。因此,文档中选择直接使用VirtualProtectEx作为实现方案,因其步骤最少。代码中也应提供VirtualAllocEx的备选路径。
重要提示:如果目标进程启用了DCP(Device Guard Code Integrity),任何尝试分配可执行内存的操作都可能会失败。
3.4 第四步:执行Shellcode
- 通过另一个APC,将执行流程跳转到已具备可执行权限的Shellcode地址。
- 可以使用
RtlDispatchAPC作为代理函数来触发APC的执行。
4. 技术总结与规避要点
- 权限最小化:始终遵循最小权限原则,避免申请不必要的权限(如
PROCESS_CREATE_THREAD,PROCESS_DUP_HANDLE)。 - API选择:优先使用新型APC API(
QueueUserAPC2+SPECIAL_USER_APC),以获得最大的线程兼容性和更低的权限要求。 - 线程选择:尽力寻找可警告线程,避免创建新线程。
- 内存操作:
VirtualProtectEx/VirtualAllocEx+ RWX内存是当前方案的已知弱点,但在此技术上下文中检测率较低。- 理想的终极目标是找到进程中现成的RX内存空洞并复制Shellcode进去,但这在实践中非常困难。
- 检测规避:此技术的优势在于拆解并伪装了注入步骤:
- 写入操作由系统API
GetThreadDescription合法完成。 - 避免了
WriteProcessMemory和CreateRemoteThread这两个最敏感的API。 - 即使使用了敏感的权限操作API,也因为其上下文(与线程名操作相关)而变得不那么突兀。
- 写入操作由系统API
5. 结语
利用线程名和APC进行进程注入是一种先进的规避技术(Advanced Evasion Technique),它巧妙地利用了Windows系统的新增功能。其有效性源于将恶意操作分解并隐藏在合法的API调用序列中。
防守方视角:
对于防御者而言,检测此类技术需要更深入的行为分析,而非简单的API钩取或静态规则。应重点关注:
- 异常序列:
SetThreadDescription后紧跟针对其他进程的APC操作。 - PEB修改:对进程PEB中特定字段的写操作。
- 内存权限变更:任何将非映像内存(如堆)设置为可执行的操作,尤其是通过
VirtualProtectEx完成的。 - ETW(Event Tracing for Windows):利用微软提供的ETW日志进行实时监控,可以捕获到这些API的调用及其上下文,是检测此类高级威胁的关键。