Stack Spoof-堆栈欺骗
字数 1598 2025-08-29 22:41:02

Stack Spoof - 堆栈欺骗技术详解

1. 背景知识

在Windows x86_32架构中,函数使用EBP(扩展基址指针)记录栈帧地址(调用者地址)。但在x86_64架构中:

  • 主要使用RSP(栈指针)同时作为"栈顶指针"和"帧指针"
  • 某些情况下仍会使用RBP(帧指针):
    • 动态栈分配
    • 异常处理
    • 需要稳定栈指针的场景

x86_64下无法通过EBP进行栈展开,改用PE文件中.pdata节的UNWIND信息表,其中记录了:

  • 每个函数的起止地址
  • prologue/epilogue中对栈的操作(通过UNWIND_CODE描述)
  • 系统和调试器使用这些信息进行回退操作

2. ReturnAddress Truncation(返回地址截断)

原理:通过修改返回地址阻止栈回溯

实现步骤

  1. 使用_AddressOfReturnAddress获取返回地址位置
  2. 保存原始返回地址
  3. 将返回地址置为0
  4. 执行目标操作(如Sleep)
  5. 恢复原始返回地址
ULONG_PTR* overwrite = (ULONG_PTR*)_AddressOfReturnAddress();
ULONG_PTR origReturnAddress = *overwrite;
*overwrite = 0;
Sleep(dwTime);
*overwrite = origReturnAddress;

效果

  • 调用栈无法回溯
  • 可能被检测为可疑行为(调用栈不可回溯本身是检测点)

3. Stack Truncation(栈截断)

原理:破坏栈结构阻止分析

实现思路

  1. 插入随机值破坏栈结构
  2. 伪造调用链(需了解函数真实栈分配大小,记录在UNWIND_INFO中)
  3. 保存原始返回地址
  4. 截断栈(如push 0)
  5. 使用Trampoline(跳板)恢复执行流:
    • 压入jmp rbx的Trampoline
    • 将restore地址存入rbx
    • 跳转到目标函数

恢复过程

  1. Trampoline将执行流转到restore模块
  2. 恢复栈指针
  3. 跳转到原始返回地址

4. Stack Moon Walk(栈月步)

基于DEFCON提出的STACKMOONWALK技术,利用UNWIND的Windows栈回溯机制。

关键帧操作

  1. 第一帧(UWOP_SET_FPREG)

    • 将当前RSP或RSP偏移设置为RBP
    • 汇编实现:mov rbp, rsp或类似操作
  2. 第二帧(UWOP_PUSH_NONVOL)

    • 将RBP推送到堆栈
    • 汇编实现:push rbp

回溯流程

  1. 系统识别UWOP_SET_FPREG操作
  2. 使用RBP作为新的栈指针
  3. 识别UWOP_PUSH_NONVOL操作
  4. 从栈中弹出值恢复RBP

欺骗实现

  1. 在第二帧的push rbp位置预先设置"伪造"的RBP值
  2. 回溯过程中会将该值赋给RBP
  3. 在第一帧回溯时,伪造的RBP会被用作新的栈指针

完整流程

  1. 第一帧执行UWOP_SET_FPREG操作,设置帧指针
  2. 第二帧推送RBP到堆栈(UWOP_PUSH_NONVOL)
  3. 插入堆栈去同步框架(包含ROP小工具,执行JMP [RBX]
  4. 插入RIP隐藏框架(包含堆栈枢轴小工具,隐藏原始RIP)

5. 检测与绕过

现有检测方法

  1. 返回地址检测

    • 对目标函数打补丁(int 3/0xCC)
    • 注册VEH(Vectored Exception Handler)
    • 分析堆栈顶返回地址
  2. 指令序列检测

    • 检查jmp前指令是否为call
    • Elastic检测:jmp rbx前不是call则报stack spoof

注意事项

  1. 修改RSP/RBP需谨慎:

    • 除非函数设置了帧指针,否则在prologue/epilogue外修改RSP是非法的
    • 需要精确控制StackOffsetWhereRbpIsPush
  2. 伪造调用链需准确:

    • 必须知道函数真实栈分配大小
    • 需正确处理UNWIND_INFO结构

6. 参考实现

; 示例汇编代码片段
; 第一帧 - UWOP_SET_FPREG
mov rbp, rsp
sub rsp, 0x20

; 第二帧 - UWOP_PUSH_NONVOL
push rbp  ; 在此处可预先设置伪造的RBP值

; ROP小工具
jmp [rbx] ; 用于跳转到真正的控制流

7. 参考链接

  1. DEFCON 31 StackMoonwalk Presentation
  2. Stack Spoofing on dtsec.us
  3. klezvirus Red Teaming笔记
  4. BattlEye Stack Walking检测
Stack Spoof - 堆栈欺骗技术详解 1. 背景知识 在Windows x86_ 32架构中,函数使用EBP(扩展基址指针)记录栈帧地址(调用者地址)。但在x86_ 64架构中: 主要使用RSP(栈指针)同时作为"栈顶指针"和"帧指针" 某些情况下仍会使用RBP(帧指针): 动态栈分配 异常处理 需要稳定栈指针的场景 x86_ 64下无法通过EBP进行栈展开,改用PE文件中.pdata节的UNWIND信息表,其中记录了: 每个函数的起止地址 prologue/epilogue中对栈的操作(通过UNWIND_ CODE描述) 系统和调试器使用这些信息进行回退操作 2. ReturnAddress Truncation(返回地址截断) 原理 :通过修改返回地址阻止栈回溯 实现步骤 : 使用 _AddressOfReturnAddress 获取返回地址位置 保存原始返回地址 将返回地址置为0 执行目标操作(如Sleep) 恢复原始返回地址 效果 : 调用栈无法回溯 可能被检测为可疑行为(调用栈不可回溯本身是检测点) 3. Stack Truncation(栈截断) 原理 :破坏栈结构阻止分析 实现思路 : 插入随机值破坏栈结构 伪造调用链(需了解函数真实栈分配大小,记录在UNWIND_ INFO中) 保存原始返回地址 截断栈(如push 0) 使用Trampoline(跳板)恢复执行流: 压入jmp rbx的Trampoline 将restore地址存入rbx 跳转到目标函数 恢复过程 : Trampoline将执行流转到restore模块 恢复栈指针 跳转到原始返回地址 4. Stack Moon Walk(栈月步) 基于DEFCON提出的STACKMOONWALK技术,利用UNWIND的Windows栈回溯机制。 关键帧操作 第一帧(UWOP_ SET_ FPREG) : 将当前RSP或RSP偏移设置为RBP 汇编实现: mov rbp, rsp 或类似操作 第二帧(UWOP_ PUSH_ NONVOL) : 将RBP推送到堆栈 汇编实现: push rbp 回溯流程 系统识别UWOP_ SET_ FPREG操作 使用RBP作为新的栈指针 识别UWOP_ PUSH_ NONVOL操作 从栈中弹出值恢复RBP 欺骗实现 在第二帧的 push rbp 位置预先设置"伪造"的RBP值 回溯过程中会将该值赋给RBP 在第一帧回溯时,伪造的RBP会被用作新的栈指针 完整流程 第一帧执行UWOP_ SET_ FPREG操作,设置帧指针 第二帧推送RBP到堆栈(UWOP_ PUSH_ NONVOL) 插入堆栈去同步框架(包含ROP小工具,执行 JMP [RBX] ) 插入RIP隐藏框架(包含堆栈枢轴小工具,隐藏原始RIP) 5. 检测与绕过 现有检测方法 返回地址检测 : 对目标函数打补丁(int 3/0xCC) 注册VEH(Vectored Exception Handler) 分析堆栈顶返回地址 指令序列检测 : 检查jmp前指令是否为call Elastic检测:jmp rbx前不是call则报stack spoof 注意事项 修改RSP/RBP需谨慎: 除非函数设置了帧指针,否则在prologue/epilogue外修改RSP是非法的 需要精确控制StackOffsetWhereRbpIsPush 伪造调用链需准确: 必须知道函数真实栈分配大小 需正确处理UNWIND_ INFO结构 6. 参考实现 7. 参考链接 DEFCON 31 StackMoonwalk Presentation Stack Spoofing on dtsec.us klezvirus Red Teaming笔记 BattlEye Stack Walking检测