|
手动伪造调用栈,对抗堆栈回溯,支持R0/R3,附源码堆栈回溯(Stack Unwind) 堆栈回溯往往可以用来检测敏感API的调用方信息: 检测无痕注入(ShellCode远程Call): 对某国产摸金搜打撤游戏的外挂样本分析后,发现该外挂为手动映射(Manual Map)注入DLL到游戏进程。 通过调用虚幻(UE)引擎相关接口,实现绕过Object指针加密、坐标加密,并利用引擎射线检测(LineTraceSingle)进行掩体判断等,实现各种破坏游戏平衡性的功能。 对部分敏感API进行堆栈回溯,对于不在白名单内的调用方,可将异常调用信息记录并上报至服务端。 该外挂样本的分析工作已基本完成,后续有空会考虑发出来,感兴趣的朋友可以点个关注。
检测无模块驱动: 在如今游戏安全、内核对抗中为了隐藏驱动特征,这类方案通常会分为两层,外层驱动仅作为MapLoader(WHQL或未被AC拉黑的签名)负责PE映射、重定位表与导入表修复等操作,内层功能模块则以Shellcode或无模块映像的形式驻留/执行于内核态(R0),此时常见内核枚举/ARK工具无法在驱动列表中找到该驱动。 此时进行堆栈回溯,一旦回溯到调用方不存在于任何合法的模块范围内,可将该异常调用链记录并上报至服务端。
手动伪造调用栈效果图如下为效果图:
手动伪造调用栈,对抗堆栈回溯,支持R0/R3
手动伪造调用栈,对抗堆栈回溯,支持R0/R3
实现思路污染堆栈中的返回地址这里需要实现一个汇编函数,作为Wrapper使用,并充当一个通用的函数调用器,其功能如下: - 在特定时机,对堆栈中的返回地址进行加密、解密(自定义、随机密钥)
- 构造新的调用栈,并调用真正的目标函数
汇编代码如下: Asm_SpoofWrapper PROC
mov r11, g_XorKey
xor [rsp], r11
push rsi
push rdi
sub rsp, 300h
lea rsi, [rsp + 340h]
lea rdi, [rsp + 20h]
mov r10, rcx
mov ecx, 40h
rep movsq
mov rcx, r10
call qword ptr [rsp + 338h]
add rsp, 300h
pop rdi
pop rsi
mov r11, g_XorKey
xor [rsp], r11
ret
Asm_SpoofWrapper ENDP
Wrapper ShellCode 的偏移修复- 目前还存在一个问题:Asm_SpoofWrapper 中的 call 指令仍会将当前驱动中的返回地址压入堆栈。那么就想办法将Wrapper放置到正常模块内,Wrapper的字节如下所示:
- 但将 Wrapper 放置到正常模块后,又会引出新的问题:用于加密、解密的密钥是通过 RIP 相对寻址获取的,因此需要动态修复其相对偏移。
UCHAR SpoofShellCode[] =
{
// 1. '动态'获取密钥并加密返回地址
0x4C, 0x8B, 0x1D, 0x00, 0x00, 0x00, 0x00, // mov r11, XorKey
0x4C, 0x31, 0x1C, 0x24, // xor [rsp+0], r11
// 2. 保存非易失性寄存器
0x56, // push rsi
0x57, // push rdi
// 3. 提升栈空间 0x300
0x48, 0x81, 0xEC, 0x00, 0x03, 0x00, 0x00, // sub rsp, 300h
// 4. 设置内存拷贝的源和目标地址
0x48, 0x8D, 0xB4, 0x24, 0x40, 0x03, 0x00, 0x00, // lea rsi, [rsp + 340h]
0x48, 0x8D, 0x7C, 0x24, 0x20, // lea rdi, [rsp + 20h]
// 5. 暂存 rcx 并执行内存拷贝 (rep movsq)
0x4C, 0x8B, 0xD1, // mov r10, rcx
0xB9, 0x40, 0x00, 0x00, 0x00, // mov ecx, 40h
0xF3, 0x48, 0xA5, // rep movsq
0x49, 0x8B, 0xCA, // mov rcx, r10
// 6. 调用目标函数
0xFF, 0x94, 0x24, 0x38, 0x03, 0x00, 0x00, // call [rsp + 338h]
// 7. 恢复栈空间和寄存器
0x48, 0x81, 0xC4, 0x00, 0x03, 0x00, 0x00, // add rsp, 300h
0x5F, // pop rdi
0x5E, // pop rsi
// 8. '动态'获取密钥并解密返回地址
0x4C, 0x8B, 0x1D, 0x00, 0x00, 0x00, 0x00, // mov r11, XorKey
0x4C, 0x31, 0x1C, 0x24, // xor [rsp+0], r11
// 9. 函数返回
0xC3 // retn
};
#pragma pack(push, 1)
typedef struct _SPOOF_SHELLCODE_TEMPLATE {
UCHAR mov_r11_opcode[3]; // mov r11, xxx
LONG32 first_xor_key_offset;
UCHAR pad_1[56];
UCHAR mov_r11_opcode_2[3]; // mov r11, xxx
LONG32 second_xor_key_offset;
UCHAR pad_2[5];
} SPOOF_SHELLCODE_TEMPLATE, *PSPOOF_SHELLCODE_TEMPLATE;
#pragma pack(pop)
#define OFFSET(type, field) ((ULONG_PTR)(&((type*)0)->field))
Wrapper ShellCode 的放置遍历系统中的驱动模块,随机选取一个正常加载的模块,在其模块范围内搜索代码空洞,搜索成功后将ShellCode写过去,但需要注意以下内容: - 尽量搜索驱动模块中'.text'节区的内容,因为'PAGE'节区在系统内存资源紧张时可能会被换出到磁盘。
- 避开ntoskrnl、win32k(图形子系统)、hal(硬件抽象层)等可能触发PatchGuard的模块。
Github开源地址此方案理论上同时支持R0、R3,实现代码大同小异,R0层完整源码已经开源,测试环境为Win10 19044
|