APT41-DodgeBox载荷加载器逆向——堆栈欺骗技术探究
字数 6281
更新时间 2026-03-07 10:20:30

APT41-DodgeBox载荷加载器逆向分析与堆栈欺骗技术教学文档

文档概述

本教学文档基于对APT41组织使用的DodgeBox载荷加载器(样本sbiedll.dll)的深度逆向分析,详细拆解了其从加载、验证、内存免杀到高级进程注入与堆栈欺骗的完整技术链。该加载器利用白加黑(taskhost.exe侧载)技术,通过复杂的多阶段流程在内存中隐蔽地加载并执行最终C2载荷(sbiedll.dat),其技术设计精妙,融合了多种先进的规避检测(Evasive)技术。

一、 整体流程概览

加载器的核心入口点为bootstrap_runtime_and_scan_modules函数,执行流程遵循以下阶段:

  1. 验证与解密:校验配置和载荷,并进行流式解密。
  2. API静态免杀:通过哈希算法动态解析API地址,规避静态扫描。
  3. 模块脱钩 (Module Unhooking):从磁盘重新加载关键系统DLL(如ntdll.dll),恢复被安全软件Hook的函数。
  4. 控制流防护绕过 (CFG Bypass):篡改CFG检查关键函数,为后续进程注入铺平道路。
  5. 环境验证与载荷加载:验证机器环境(MAC、主机名、用户账户),并解密特质化处理过的最终载荷。
  6. 进程注入:优先尝试DLL Hollowing技术注入载荷;失败则回落到反射式加载
  7. 堆栈欺骗 (Call Stack Spoofing):在执行敏感API时,伪造线程调用栈以隐藏加载器自身的踪迹。

二、 关键技术点详解

2.1 验证与解密流程

  • 初始校验:使用MD5算法对Config Blob和Payload Blob进行完整性校验。
  • 流式解密:以Config头16字节为IV,使用内置的AES密钥对Payload进行流式解密。
  • 参数验证:解密内嵌数据,获取msvcrt!__getmainargs函数地址,检查进程命令行参数中是否包含--type driver字符串并进行MD5验证。这是一种简单的反沙箱或触发机制。

2.2 API静态免杀

  • 哈希解析器:通过自定义函数init_hashed_api_dispatch_table,使用FNV-1a哈希算法对所需API的函数名进行哈希。
  • 动态解析:在运行时,通过遍历目标系统DLL(如kernel32.dll)的导出表,计算每个导出函数名的FNV-1a哈希值,与预置的哈希值进行匹配,从而获取函数地址,避免了在代码中直接出现敏感的API字符串。

2.3 模块脱钩 (Unhooking)

为了对抗用户态钩子(User-mode Hooks),加载器执行了精细的模块脱钩操作。

  • 目标模块筛选
    1. 遍历PEB->Ldr->InMemoryOrderModuleList链表。
    2. 检查LDR_DATA_TABLE_ENTRY->Flags,跳过已处理过的模块(Flags & 0x60000000 == 0x20000000)。
    3. 检查Characteristics,只处理DLL(IMAGE_FILE_DLL)。
    4. 解析FullDllName,仅选择父目录为system32的模块(如ntdll.dll, kernel32.dll)。
  • 脱钩逻辑
    1. 通过NtReadFileSystem32目录读取干净的DLL文件到内存。
    2. 解密内嵌的节名称(如.pdata, .text)。
    3. 关键创新:遍历内存中目标模块的.pdata节(异常处理数据)。.pdata中的RUNTIME_FUNCTION结构数组包含了模块内所有函数的起始和结束RVA。这比仅遍历导出表更全面,能覆盖所有函数(包括非导出函数)。
    4. RUNTIME_FUNCTION定义的每个函数范围,计算内存中与磁盘中对应字节的FNV-1a哈希值。
    5. 若哈希值不匹配,则使用NtProtectVirtualMemory修改页面权限为可写,将磁盘中的原始字节覆盖到内存,恢复权限,并调用FlushInstructionCache。从而清除了该函数上可能存在的安全软件Hook。

2.4 控制流防护绕过 (CFG Bypass)

为后续的DLL Hollowing注入做准备,需要绕过控制流防护。

  • 环境检查:调用RtlGetVersion检查系统版本(需Windows 8.1以上),并通过GetProcessMitigationPolicy查询ProcessControlFlowGuardPolicy,确认CFG已开启。
  • 方法一:钩子关键函数
    1. 通过二进制特征码匹配,定位ntdll!LdrpHandleInvalidUserCallTarget函数(CFG检查失败时的处理函数)。
    2. 修改该函数头部的5个字节为JMP RAX; INT 3; NOP,使其在CFG检查失败时直接跳转到攻击者控制的RAX寄存器地址,从而绕过检查。
  • 方法二:短路检查指针
    1. 解析msvcrt!longjmp及其内部的__except_validate_jump_buffer函数。
    2. 该函数会检查全局指针__guard_check_icall_fptr。样本获取该指针和_CrtSetDbgBlockType函数(一个空容器,仅包含retn 0)的地址。
    3. __guard_check_icall_fptr指向_CrtSetDbgBlockType。这使得任何间接调用检查都会跳转到一个直接返回的空函数,从而彻底短路CFG机制。

2.5 环境验证与载荷加载

  • 机器绑定:加载器会检查机器的MAC地址、计算机名和当前登录的用户账户SID。样本中内嵌了期望账户名/域名的密文,通过AES-CFB8解密后与当前信息比对。
  • 载荷特质化:初始通用载荷会在第一台受害机器上,使用该机器注册表MachineGUID衍生的MD5值作为新密钥进行重新加密。这使得载荷与特定机器绑定,阻碍了安全厂商通过匿名文件上传进行批量分析。
  • 加载流水线:函数load_and_verify_runtime_payload负责最终载荷的加载。它使用静态内置密钥或本机GUID衍生的AES密钥解密载荷文件,进行MD5校验,并可选择在加载后使用DeleteFileW删除磁盘上的加密文件。

2.6 DLL Hollowing 进程注入技术

这是一种高阶的进程注入技术,旨在使注入的代码在内存中拥有合法的“文件背书”。

  • 核心思路:选择一个“牺牲”DLL,利用NtCreateSection创建带有SEC_IMAGE标志的节对象,并将其映射到进程空间。通过触发写时复制(Copy-on-Write, COW)机制,使内存区域与一个合法的磁盘文件关联,然后将恶意代码覆盖到该内存区域,实现“镂空”。

  • 实施步骤

    1. 选择牺牲DLL:枚举system32目录下的所有DLL,排除一个包含58个关键DLL(如kernel32.dll)的列表,然后从剩余的上千个DLL中随机选择一个。
    2. 创建伪装路径:尝试在C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Data.Trace\下生成一个伪装成.NET全局程序集缓存(GAC)的随机目录,用于存放牺牲DLL的副本。若失败,则使用C:\ProgramData\Microsoft\NET\下的目录。
    3. 处理载荷PE
      • 在内存中清理载荷的导入表、重定位表、调试信息目录。
      • 清除DOS Stub、节区名称、时间戳等特征,但保留资源节。
      • 对载荷进行重定位,使其能在目标地址运行。
    4. 准备牺牲DLL文件
      • 将干净的牺牲DLL复制到伪装目录。
      • 修改其PE头:禁用ASLR标志、清零重定位表和TLS目录。
      • 定位DLL入口点,并Patch为MOV RAX, 1; RET(直接返回1),此步骤目的可能是防止在沙箱等环境意外执行原始DLL代码。
      • 关键步骤:用载荷的.text节覆盖牺牲DLL文件中的.text节。
    5. 内存映射与镂空
      • 使用NtCreateSectionNtMapViewOfSection,以SEC_IMAGEPAGE_READONLY权限将修改后的牺牲DLL文件映射到进程空间的特定地址(通常靠近kernel32模块)。
      • 此时,内存区域(VAD)类型为VadImageMap,并持有对伪装文件的引用,具有合法的“身份”。
      • 使用memmove将已在内存中处理好的完整载荷PE数据复制到该映射区域。此写操作触发COW:系统为这些页面分配新的私有物理页,与原文件脱离关系,但内存区域的SEC_IMAGE属性得以保留。
      • 将原始的、未被修改的干净牺牲DLL再次复制到伪装目录,覆盖之前被修改的文件。现在,磁盘文件是干净的,而内存中的映像却是恶意的,但工具检查时会显示该内存区域映射自一个干净的、有签名的系统文件。
    6. 执行:调用载荷的DllMain入口点,启动恶意代码。

2.7 堆栈欺骗 (Call Stack Spoofing)

该技术旨在API调用时伪造调用栈,使EDR等安全工具在进行栈回溯时无法发现恶意模块的踪迹。

  • API调用分发器:样本通过call_loader_api_dispatch(api_fn_ptr, fnv1a_obfs_enabler, arg_count, ...)函数调用所有敏感API。该函数支持可变参数和堆栈欺骗开关。
  • 跳板函数定位:在kernelbase.dll.text段中,搜索特定指令jmp qword ptr [rbp + 0x48]。通过解析.pdata节和UNWIND_CODE,计算并筛选出栈帧大小在[0x88, 0x1000)范围内的合法函数作为候选跳板。最终随机选择一个。
  • 栈帧构造原理
    1. 获取真实栈基址:为避免使用gs:[0x30]等敏感指令,样本通过NtQueryInformationThread(ThreadBasicInformation)查询TEB地址,进而获取线程栈基址。此结果缓存在线程本地存储(TLS)中以提高效率。
    2. 伪造调用栈链:在堆上分配一块新内存作为伪造的栈,并按以下布局从高地址到低地址布置:
      • 层3(栈顶):jmp_gadget_va - 选中的jmp qword ptr [rbp+0x48]指令地址。
      • 层2:一个合法的中间函数地址(用于增加真实性)。
      • 层1:RtlUserThreadStart+0x21BaseThreadInitThunk+0x14 - 标准的线程入口点,作为栈链的起点。
      • 链顶:NULL 哨兵。
        每一层之间预留的空间大小对应其函数的栈帧大小(从.pdata解析获得)。
  • 控制流转
    1. 在分发器中保存非易失性寄存器后,将RSP切换到伪造的栈顶(即jmp_gadget_va所在位置)。
    2. 将原始参数复制到新栈上,并设置RBP指向一个精心构造的上下文块,其中在RBP+0x48处存放了跳板尾声函数 hook_trampoline_epilog 的地址。
    3. 通过JMP R12跳转到目标API函数执行。
  • 栈回溯欺骗效果
    • 执行流:API执行完毕后,返回到jmp_gadget_va,该指令从[RBP+0x48]读取地址并跳转,即跳转到hook_trampoline_epilog
    • 栈回溯器视角:当EDR或系统尝试进行栈回溯(unwind)时,会依据.pdata中的UNWIND_INFO进行。由于伪造的栈帧链由合法的系统函数地址构成,且帧大小匹配,回溯器会顺利地从jmp_gadget_va -> 中间函数 -> 线程入口点 -> NULL,完全不会出现sbiedll.dll或任何恶意模块的地址,从而实现了隐藏。
  • 恢复:在hook_trampoline_epilog中恢复所有非易失性寄存器,并将控制流返回到真实的调用者,完成一次“隐身”的API调用。

三、 技术对比与深入思考

3.1 堆栈欺骗技术横向对比:SilentMoonwalk

与著名的开源堆栈欺骗项目SilentMoonwalk相比,本样本的实现更为精简:

  • SilentMoonwalk (Desync模式):通过精心构造多个栈帧(如UWOP_SET_FPREG, UWOP_PUSH_NONVOL),主动控制栈回溯器的寄存器(RBP),诱导其跳转到任意指定的返回地址(如漏洞调用点),实现执行流与回溯流的彻底“脱钩”,对抗性更强。
  • 本样本:侧重于“隐藏”而非“诱导”。它利用系统已有的合法跳板函数构造一个看似正常的栈链,使恶意调用“融入”正常的线程调用序列中。这足以对抗基于栈扫描的常规检测,且实现成本更低,符合其作为鱼叉攻击载荷的特点。

3.2 x64栈回溯机制温故知新

  • RUNTIME_FUNCTION.pdata:x64系统不再像x86那样通过扫描栈帧指针来回溯,而是强制要求PE文件包含异常目录(.pdata节),其中包含RUNTIME_FUNCTION数组,记录每个函数的起止RVA和对应的Unwind Info地址。
  • UNWIND_INFOUNWIND_CODEUnwind Info描述了函数序言中如何操作栈指针,包含一系列UNWIND_CODE,告诉异常处理程序或回溯器如何“解开”这个函数帧(例如,分配了多少栈空间、保存了哪些非易失寄存器)。
  • 帧指针寄存器:当UNWIND_INFOFrame Register字段非零时(例如设置为RBP),并且存在UWOP_SET_FPREG操作码,回溯器将使用指定的帧寄存器(FP)作为基准来计算返回地址和局部变量,这为堆栈欺骗提供了操纵的基础。

3.3 多线程适配考量

样本宿主sbiedll.dll来源于沙箱软件Sandboxie-Plus,是一个会被注入到沙箱内所有进程及其所有线程的Hook DLL。因此,加载器代码必须具备高度的线程安全性,包含大量的并发控制机制(如TLS缓存线程特定信息)、休眠等待,以避免在多线程环境下发生资源竞争、重复初始化或崩溃,这反映了其在真实复杂环境中的实战化设计。

四、 总结

APT41-DodgeBox加载器是一个集成了多种高级逃逸技术的复杂恶意软件组件。其技术亮点包括:

  1. 全面的用户态Hook清除:通过.pdata遍历实现更彻底的模块脱钩。
  2. 双重CFG绕过:结合钩子关键函数和短路全局指针两种方法,确保注入顺利进行。
  3. 高隐蔽性进程注入:采用DLL Hollowing技术,使恶意内存区域拥有合法的文件映射身份。
  4. 先进的堆栈欺骗:在API调用层面实现调用栈伪装,有效规避基于栈回溯的检测。
  5. 环境感知与持久化:进行严格的机器环境验证,并对载荷进行机器特质化加密,增加分析难度。

通过对该样本的分析,可以深刻理解现代高级持久性威胁(APT)在对抗安全检测、实现隐蔽持久化方面的技术思路和发展趋势。

相似文章
相似文章
 全屏