一条指令引发的血案···

事情是这样的:

公司采购了新软件,想在新软件下沿用旧的一些插件和功能,于是就将旧版本的dll直接替换了新版本。

然而,诡异的现象出现了,win7下能正常运行,但win10却怎么也跑不起来,一运行就会会闪退。

学习逆向,当然要自己来动手解决这个问题,是时候展示真正的技术了。

经过分析,发现了崩溃的地方,有个函数入口的地方反汇编后是长这个样子的:

图片

注意看,入口处,函数开辟栈帧,把栈顶抬高了0x298的大小,使用了sub rsp, 298h指令。

然后是函数结束返回的地方,要恢复栈空间,使用了add, esp, 298h指令:

图片

聪明的你一定已经发现问题了,怎么开始是用的rsp寄存器,后面用的是esp寄存器呢?

PS:esp是x86架构CPU的栈指针寄存器,rsp是x64架构CPU的栈指针寄存器。x64兼容了x86指令集,可以通过esp访问rsp的低32位。

先别往下看,你先猜一下,这里会出问题吗,为什么会出问题呢?

由于前面正好有一堆nop,于是将紧邻着的一个nop从0x90改为0x48,这样一来,就把add esp, 298h这条指令,改成了add rsp, 298h了。保存,然后成功修复了~然后很得意的在群里吹水去了~

但事情并没有结束。过了几天总结文档时,突然意识到:不对!有问题!

修复程序的当时我给开发那边给出了一个解释,是恢复栈指针时出现了错误导致堆栈不平衡。但现在仔细想想,简简单单一句“堆栈不平衡”完全解释不通。

原因是,win7能跑,意味着在win7下add espadd rsp结果是一致的,也就是栈顶指针的高32位空间没变动(或者变动了,但变动前后是一样的),而win10下却变动了。

没有头绪,找风哥帮忙。在风哥建议下又分析了一次。先找出之前的程序崩溃转储,用windbg再看看有没有什么蛛丝马迹。

dump分析出来是这个样子的:

图片

经典的0xC0000005 错误码。但除此之外也似乎没有别的问题了?等等,再看一下寄存器:

图片

发现了一个很可疑的点,在执行了add esp, 298之后,相比rbp,rsp丢失了高32位,这也就是说……高32位被置零了?

再来动态调试验证一下,执行add esp, 298之前:

图片

执行add esp, 298之后:

图片

这里就很明确了,确实在执行add esp后,高32位被置零了。

这是为啥嘞?难道在64位系统下,操作32位寄存器会将64位的高32位置零吗?

好像……确实如此!

风哥做了试验,肯定了这个现象。

继续寻找资料,微软文档里,介绍x64处理器的体系结构时,提到了这么一句:继续寻找资料,微软文档里,介绍x64处理器的体系结构时,提到了这么一句:

图片

换句话说,操作32位寄存器时会自动将64位的寄存器进行0扩展(即填充0),而16位和(高低)8位则不会。

x86-64 Tour of Intel Manuals (x86asm.net)的文章中,记录了这么一段话:

Perhaps the most surprising fact is that an instruction such as MOV EAX, EBX automatically zeroes upper 32 bits of RAX register. This doesn't happen with instructions that only read destination registers, like TEST EAX, EBX. In this case RAX remains unmodified. There is one exception to this rule, CMOVcc instructions, for example CMOVBE. These instructions zero upper 32 bits even if the condition is false, when the move doesn't occur. Another surprising fact is that it is impossible to use 8-bit registers AH, CH, DH, and BH together with one new feature of 64-bit mode, in instruction that requires REX prefix. For example, instruction MOV AH, SIL cannot be encoded in 64-bit mode, because SIL register requires REX 40 prefix. The reason for this is that any of REX prefixes cause remapping of original registers AH, CH, DH, and BH to SPL, BPL, SIL, and DIL:

-- 88 EC   MOV AH, CH

40 88 EC   MOV SPL, BPL

看来在64位系统下,直接操作32位的寄存器是有不少问题的,如果存在对寄存器值的直接操作,那么就会导致高32位清零;如果不存在对值的修改,只是读取,则可能不会出现这种情况。

至此,这个问题就得到解答了~原因就是add esp操作会将rsp的高32位置零,导致栈顶指针变成了一个“野指针”。

等等,还有个问题:为什么win7下能正常跑?这个规则是指令集规范,win7下应该也是一样的吧?跑了一下,win7下是这样的:

图片

哈哈,win7下的栈指针,高32位本来就是0,当然不会有事了~

你有没有猜对呢?

最后,我还有一个问题,这是怎么编译的,是什么样的编译器,入口使用rsp,结尾使用esp?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值