0x01 线程注入检测原理
Get-InjectedThread是由Joe Desimone和Jared Atkinson发布的powershell 线程注入检测脚本。通过检测线程的的状态和类型,如果内存类型不为MEM_IMAGE
,则说明存在注入。
Get-InjectedThread的具体检测逻辑是这样的:
- 1) 通过
CreateToolhelpSnapshot
,Thread32First
,Thread32Next
遍历所有的线程 - 2)调用
OpenThread
获取目标线程的线程句柄 - 3)调用
NtQueryInformationThread
,将ThreadInformationClass参数指定为ThreadQuerySetWin32StartAddress,获取线程内存的起始地址Thread Start Address - 4)调用
OpenProcess
获取线程对应进程的句柄 - 5)将
Process Handle
和Thread Start Address
传递给VirtualQueryEx
,获取MEMORY_BASIC_INFORMATION
。 - 6) 检查
MEMORY_BASIC_INFORMATION
结构体中状态字段(State
)和类型字段(Type
),如果内存类型不为MEM_IMAGE
,状态是MEM_COMMIT
,则说明存在注入。
根据elastic的John Uhlmann在2022年11月的Get-InjectedThreadEx – Detecting Thread Creation Trampolines一文,已经增加了启发式的方法来检测线程注入。即通过检测线程入口的关键字。
- 1)MZ关键字
- 2)一些返回,跳转或者无意义填充的字节
0x02 绕过策略
John Uhlmann在他的文章描述了几种绕过Get-InjectedThread的方法,但是这些方法都被他修复了。同样的XPN也在2018年针对早期版本的Get-InjectedThread进行了绕过。
利用Dll规避内存类型和状态检测
将shellcode包装到dll模块中,然后通过CreateRemoteThread
远程线程注入的方式执行,因为线程入口点是LoadLibrary
,因此绕过Get-InjectedThread。流程如下:
- 1)获取调用地址LoadLibraryA。
- 2)在我们的目标进程中分配内存。
- 3)将我们的 DLL 的路径写入分配的内存中。
- 4)调用以启动新线程,入口点为LoadLibraryA,将DLL路径内存地址作为参数传递。
|
|
SetThreadContext线程注入
首先,拉起一个挂起的线程,该线程的入口点内存类型为MEM_IMAGE
,通过SetThreadContext
方法,将线程的入口点设置为Shellcode。在线程被拉起的时候,内存类型为MEM_IMAGE
,由此绕过检测。
- 1)在目标进程中分配内存来保存我们的 shellcode。
- 2)将我们的 shellcode 复制到分配的内存中。
- 3)产生一个挂起的线程,将 ThreadProc 设置为任何MEM_IMAGE标记的内存区域。
- 4)检索挂起线程的当前寄存器。
- 5)更新 RIP 寄存器以指向驻留在已分配内存中的 shellcode。
- 6)恢复执行。
|
|
Hook API函数
这个方法就是Hook一个API函数,因为API函数本身内存类型和状态就可以绕过Get-InjectedThread工具。