- https://github.com/am0nsec/HellsGate 原地址
- 为了防止EDR进行 syscall 调用验证 手动查找并执行ntdll中的syscall
- 关键代码1:*((PBYTE)pFunctionAddress + i) == 0x0F && *((PBYTE)pFunctionAddress + i + 1) == 0x05 && *((PBYTE)pFunctionAddress + i + 2) == 0xC3
- 关键代码2:syscall 替换成 jmp qword ptr [gad_gets_jmp_syscall]
- 个人觉得作用不大 没啥用 仁者见仁智者见智
方法1:看 RIP 是否位于 ntdll
进入内核时:
KiSystemCall64
CPU会保存用户态 RIP。
内核可以获取:
TrapFrame->Rip
如果发现:
RIP = 0x00007FF7xxxxxxx
属于你的EXE
而不是:
RIP = ntdll.dll
那么就是直接syscall。
这也是早期 Hell's Gate 被抓的原因。
方法2:检查 syscall 指令来源
你的 gadget 最终会跳到:
syscall
ret
例如:
ntdll+0x123456
内核拿到 RIP 后:
RIP ∈ ntdll ?
是。
但很多 EDR 不止检查这个。
还会检查:
ReturnAddress
或者
CallStack
例如:
my.exe
└─HellDescent
└─ntdll+0x123456
└─syscall
会发现:
NtOpenProcess
根本不在栈里。
方法3:栈回溯(最常见)
进入内核时:
RtlWalkFrameChain(...)
或者自己 unwind。
得到:
frame0 = ntdll!syscall
frame1 = my.exe
frame2 = my.exe
正常情况:
frame0 = ntdll!NtOpenProcess
frame1 = kernel32
frame2 = my.exe
于是就能发现:
syscall前没有NtOpenProcess
这是典型 indirect syscall 特征。
方法4:验证 syscall stub
很多产品会预先记录:
NtOpenProcess:
4c 8b d1
b8 26 00 00 00
0f 05
c3
然后检查:
syscall执行地址
是否属于:
NtOpenProcess
NtAllocateVirtualMemory
NtWriteVirtualMemory
...
如果发现:
jmp到了NtClose里面的syscall
但是:
SSN = NtOpenProcess
就异常。
例如:
mov eax, 26h ; NtOpenProcess
jmp NtClose+12 ; syscall
这种 HalosGate/TartarusGate 很容易被检测。
方法5:检测 Gadget 跳转
你的代码:
jmp qword ptr [gad_gets_jmp_syscall]
如果产品做用户态监控:
可以扫描:
mov eax, xx
jmp [xxx]
或者:
jmp r11
jmp rax
call rax
最终落点:
0f 05 c3
这种就是明显的 syscall gadget。
很多内存扫描器会专门找:
0F 05 C3
附近的引用。