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(返回地址截断)
原理:通过修改返回地址阻止栈回溯
实现步骤:
- 使用
_AddressOfReturnAddress获取返回地址位置 - 保存原始返回地址
- 将返回地址置为0
- 执行目标操作(如Sleep)
- 恢复原始返回地址
ULONG_PTR* overwrite = (ULONG_PTR*)_AddressOfReturnAddress();
ULONG_PTR origReturnAddress = *overwrite;
*overwrite = 0;
Sleep(dwTime);
*overwrite = origReturnAddress;
效果:
- 调用栈无法回溯
- 可能被检测为可疑行为(调用栈不可回溯本身是检测点)
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. 参考实现
; 示例汇编代码片段
; 第一帧 - UWOP_SET_FPREG
mov rbp, rsp
sub rsp, 0x20
; 第二帧 - UWOP_PUSH_NONVOL
push rbp ; 在此处可预先设置伪造的RBP值
; ROP小工具
jmp [rbx] ; 用于跳转到真正的控制流