x64 Windows环境下栈溢出修改返回地址示例不生效问题问询
问题核心:栈结构、偏移计算存在多处根本性错误
你的代码移植失败本质是把32位x86平台的栈溢出逻辑直接套用到x64 Windows环境,两者的调用约定、栈布局、编译防护机制完全不同,具体错误点如下:
- 架构位数认知错误:你所有的长度计算都是按32位x86来的,x64环境下地址、返回地址、栈上保存的帧指针长度都是8字节,不是你写的4字节,这直接导致偏移计算从根上就错了。
- 调用约定认知错误:x64平台的前4个整数/指针参数都是通过寄存器传递的,不会像32位cdecl约定那样压到栈上。你反汇编出来的代码里
f(1,2)的两个参数是分别放到ecx、edx寄存器的,属于Windows x64调用约定,这两个参数根本不会出现在f的栈帧里,你之前栈布局里写的栈上存储参数1、2完全不成立。 - 局部变量布局认知错误:编译器不会严格按照你声明变量的顺序排列栈上局部变量:
- 首先会做栈对齐(x64要求栈保持16字节对齐),5字节的
buffer1、10字节的buffer2实际分配的栈空间都会向上对齐到8或16字节,不会刚好占5、10字节; - 编译器可能重排局部变量顺序,还会在局部变量和返回地址之间插入栈金丝雀(Windows默认开启
/GS编译选项的安全检查)、预留对齐填充空间,你假设buffer1往上12字节就是返回地址完全是主观猜测。
- 首先会做栈对齐(x64要求栈保持16字节对齐),5字节的
- 指针类型使用错误:你用
int*类型存储栈地址,x64下int类型长度是4字节,指针长度是8字节,赋值时会直接截断地址高4位,访问的根本不是你想要的内存位置。 - 返回地址位置认知错误:x64下进入函数
f时,栈上从高到低的结构(无优化、无防护的理想情况)是:
也就是说哪怕没有任何防护、变量顺序和你想的一样,高地址 调用方main预留的32字节shadow space(Windows x64强制要求) call指令压入的8字节返回地址 <-- 目标修改位置,位于f的RBP寄存器+8偏移处 f入口push rbp压入的8字节旧RBP值 <-- f执行时RBP寄存器指向这个位置 栈金丝雀、对齐填充、局部变量(buffer1、buffer2、ret) 低地址buffer1到返回地址的偏移也至少是buffer1实际分配长度 + 8字节旧RBP + 金丝雀/填充长度,远大于12字节。 - 你对跳过指令的长度计算本身是对的:从call指令返回后的地址
0x4015b3(对应x=21的赋值指令)到下一条指令0x4015ba的差是7字节,只要能正确把返回地址加7确实能跳过赋值,但你根本没有定位到正确的返回地址位置。
Windows环境下额外的阻碍
就算你算对了偏移,默认编译选项下程序也不会按你的预期运行:
- Windows默认开启
/GS栈保护,溢出时会先覆盖栈金丝雀,函数返回前校验失败直接终止进程,根本不会走到返回地址跳转的步骤; - 部分编译选项下编译器会直接把短函数
f内联到main里,根本不会生成call f的指令,不存在你要修改的返回地址; - 调试模式下编译器会在栈上插入额外的调试信息、填充值,进一步打乱局部变量布局。
正确的调试方法
你现在只反汇编了main函数,根本不知道f函数实际的栈布局,正确的做法是:
- 编译时关闭所有防护:关闭
/GS、关闭ASLR(链接选项加/DYNAMICBASE:NO)、关闭优化(加/Od)、关闭内联; - 反汇编
f函数本身,跟踪buffer1的实际栈地址,再通过调试器直接查看栈内存,计算buffer1到返回地址的实际偏移,不要靠32位的经验猜测。
内容的提问来源于stack exchange,提问作者idk
相关产品推荐
相关产品推荐

