地狱之门带你下地狱,返程之门带sysid回家。
Direct Syscall算是老生常谈的R3层免杀重要技术之一,网上已有非常多相关的开源项目,用的人最多的就是HellsGate以及syswhisper。
把大部分的Direct Syscall项目拆开来,都是由两部分组成的:
- 绕过EDR监控获取sysid
- 使用sysid绕过EDR监控调用nt api
本文将介绍一种新的思路用于获取sysid,笔者称其为ReturnGate(返程门)
首先我们进入一个Nt API,
如上图所示Nt API采用如下的调用方式
mov r10,rcx
mov eax,xxh
syscall
ret
差别就在传入 eax 寄存器的值不同,存储的是系统调用号,即sysid,不同调用号针对syscall 进入内核调用的不同的内核函数。
而对于r3层的edr hook来说,会在api地址的前段加入inline hook,一例:
jmp 0xffffffffbffe2f48
int3
int3
int3
...
...
syscall
ret
这样在进行API调用的时候就会强制跳转到0xffffffffbffe2f48地址,即为EDR的探针。
在https://github.com/rad9800/TamperingSyscalls 项目中, 作者在syscall前加入硬件断点,并且使用空参数调用api后,在断点处的VEH 函数中插入需添加的参数,做到了调用参数不被EDR记录。
我由此项目得到灵感,实现了ReturnGate返程门。
由上可知:
- EDR hook大部分不会影响到syscall;ret指令。
- 在执行syscall指令时sysid位于eax寄存器中。
- 使用空参数调用api被记录到的恶意程度较低。
- 部分EDR会监控自己的钩子是否被脱钩。
由函数调用规约可知:
ret 指令会将eax寄存器的值返回。
那么,就有了一个获取sysid的新手段,使用writeprocessmemory将Nt API的syscall指令修改为nop,再使用空参数调用api, 返回值即为该api的sysid。
此方式由于采用了ret指令获取sysid,故命名ReturnGate(返程门)
实现代码POC如下(Golang):
replace:= []byte{0x90,0x90}
raw:= []byte{0x0f,0x05}
//获取地址
apiName := "NtReadVirtualMemory"
nt := syscall.NewLazyDLL("ntdll").NewProc(apiName).Addr()
//替换
if *(*byte)(unsafe.Pointer(nt+18)) == 0x0f &&
*(*byte)(unsafe.Pointer(nt+19)) == 0x05 &&
*(*byte)(unsafe.Pointer(nt+20)) == 0xc3{
windows.WriteProcessMemory(0xffffffffffffffff,nt+18,(*byte)(unsafe.Pointer(&replace[0])),2,nil)
}
//空调用获取sysid
sysid,_,_ := syscall.Syscall(nt,0,0,0,0)
fmt.Printf("sysid: %d\n\n",sysid)
//恢复
windows.WriteProcessMemory(0xffffffffffffffff,nt+18,(*byte)(unsafe.Pointer(&raw[0])),2,nil)