总结
调用堆栈欺骗并不是一种新技术,但在过去几年中它变得越来越流行。调用堆栈是EDR软件的遥测源,可用于确定进程是否执行了可疑操作(向类进程请求句柄、将可疑代码写入新分配的区域,等等)。该技术的目的是构建一个模拟合法调用堆栈的假调用堆栈,以隐藏可能被EDR或其他安全软件检测到的可疑活动。
技术分析
SHA256: 33FD050760E251AB932E5CA4311B494EF72CEE157B20537CE773420845302E49
当你想用记事本创建文件时,调用堆栈如下图所示:
请注意以下操作顺序:
-
CreateFileW (KernelBase.dll)
-
ZwCreateFile (ntdll.dll)
-
NtCreateFile (ntoskrnl.exe) ->内核空间
另一个重要的观察结果是,进程名“notepad.exe”显示在调用堆栈中。
下面的调用堆栈看起来非常相似,但是进程名消失了:
我们将解释调用堆栈欺骗技术的实现,该技术用于调用上面给出的NtCreateFile函数。其基本思想是构造一个“假”堆栈,以掩盖函数调用的真正起源。APT41使用的恶意软件DodgeBox实现了该技术,以欺骗依赖堆栈调用分析进行检测的Antivirus和EDR软件。
相关字符串在运行时使用AES算法解密,密钥在DLL中被硬编码:
恶意进程通过调用LdrGetDllHandle函数来获取kernelbase.dll的句柄。
它遍历".text"部分查找以下字节:0xFF、0x65和0x48。它们对应于"jmp qword ptr [rbp 48]"JOP小工具,如下所述。
".Pdata"节包含分配堆栈空间的所有函数的条目。在下面的段落中,我们将解释计算包含该小部件的函数的展开大小的步骤。
进程从“。pdata "节,用于定位RUNTIME_FUNCTION结构体:
它还获取Unwind代码数组的地址,该数组包含多个具有以下字段的UNWIND_CODE结构:
- 序言中的偏移量(1字节)
- Unwind操作码(4位)
- 操作信息(4位)
显示了最后两个字段值的示例。
如下图所示,我们可以看到多重比较,unwind操作代码取0 (UWOP_PUSH_NONVOL)到10 (UWOP_PUSH_MACHFRAME)之间的不同值:
让我们举一个简单的例子来说明它是如何工作的。如果UNWIND_CODE结构中最后两个字段对应的字节为0x72,则操作码为0x2 (UWOP_ALLOC_SMALL),操作信息为0x7。该代码对应的操作是在堆栈上分配一个较小的区域,其大小等于操作info * 8 8。图12在ASM中显示了相同的示例:
unwind大小是通过耗尽UNWIND_CODE结构来计算的。该值必须大于0x88,如下面的比较所示:
使用该方法共获得56个JOP小部件:
GetTickCount64 API用于检索自系统启动以来所经过的毫秒数。基于这个值,进程进行除法并选择在欺骗操作中使用哪个JOP gadget:
二进制文件通过调用LdrGetProcedureAddressEx方法提取RtlUserThreadStart函数的地址,如下所示:
为了模拟合法堆栈调用的值,该进程向地址添加0x21,并将结果地址存储在堆栈上。
BaseThreadInitThunk也重复了相同的过程,但是,恶意软件将0x14添加到调用返回的地址:
图显示了跳转到NtCreateFile函数的指令。参数存储在RCX, RDX, R8, R9中,然后存储在堆栈中的值中。
NtCreateFile执行0x55系统调用,并准备跳转到JOP小部件:
返回地址以前存储在RBP 0x48,用于将执行流重定向回恶意软件的代码:
参考文献
https://www.zscaler.com/blogs/security-research/dodgebox-deep-dive-updated-arsenal-apt41-part-1
https://labs.withsecure.com/publications/spoofing-call-stacks-to-confuse-edrs