从汇编层看64位程序运行——有惊无险的栈溢出

《从汇编层看64位程序运行——ROP攻击以控制程序执行流程》一文中,我们通过“篡改”函数的返回地址来达成修改程序执行流程的目的。
在这里插入图片描述

但是这个方案存在一个问题:它会导致栈溢出。这是因为ret指令会导致栈的pop,call指令会导致栈的push。而案例中,foo7调用foo函数时,使用的是ROP攻击方法——没有使用call指令。这样就导致整个流程call了1次(main–>foo7),即压栈1次;而ret了2次,foo7一次,foo一次。

在foo7进入foo之后,我们修改了foo的返回地址。这个地址是0x7fffffffdef8,而它属于main函数的栈帧。这样的栈溢出,并没有导致我们程序出问题。这是因为0x7fffffffdef8保存的是main函数调用foo7时,push到栈中的临时数据(即第7个参数,foo函数的地址)。所以在从foo回到main函数后,这个空间的值就不会被逻辑所使用,进而没有发生问题。

但是由于call一次、ret两次,最终会导致栈会被多pop一次。于是回到main函数后,rsp的值会比正确的时候大0x08(即main的栈帧小了0x08),而减小的空间正好是上一段分析的main函数push到foo7中的foo函数地址。
我们知道,很多时候栈上变量的表达是通过rbp寄存器(rbp寄存器的值一直是正确的)来进行的(见《从汇编层看64位程序运行——栈上变量的rbp表达》),所以这次rsp寄存器的变动,一般不会影响main函数临时变量的取值,也不会影响程序执行。

即使后面调用了其他函数,甚至push栈传递数据的行为,也不会造成程序的崩溃。这是因为此时rsp指向了main函数栈上变量地址空间的末尾。调用其他函数时,只会让栈在此基础上增长,并不会影响到main函数关键的栈帧空间。
在这里插入图片描述

从main函数退出时,汇编会执行Leave指令。这步指令会

mv %rbp,%rsp

这样rsp寄存器的值就回到进入main函数时正确的值,进而导致代码进入main函数的调用者时,堆栈由恢复到正确的状态,所以程序也没有出问题。

整体调试过程如下:

main

进入时寄存器状态

rbp:1

rsp:df18

分析过程

刚进入main函数时,栈底寄存器rbp的值是1,栈顶寄存器rsp的值是df18。
在这里插入图片描述
+4行将rbp寄存器的值压入栈后,栈顶寄存器rsp的值变成df10。
+5行让rbp和rsp寄存器中的值相同,即rbp和rsp都是df10。
+8行预先给main函数分配了栈空间(0x10)。这样rsp变成df00,rbp还是df10。
在这里插入图片描述
+23行会foo函数的地址压入栈,这样rsp会变成def8。(0x7fffffffdef8,即后续在foo函数中被用作返回RIP的空间。)
在这里插入图片描述

离开时寄存器状态

rbp:df10

rsp:def8

foo7

进入时寄存器状态

rbp:df10

rsp:def8

分析过程

执行call指令,进入foo函数内部。由于call会将next_rip压入栈,所以rsp的值会继续减少0x08,变成def0。
在这里插入图片描述
可以看到栈顶的值就是next_rip的值。
在这里插入图片描述
将main函数的栈底寄存器的值保存到栈上,这样rsp变成了dee8。
在这里插入图片描述
此时的栈顶rsp就是可以用于给foo7函数预分配栈上空间的值。但是由于要用rbp做变量表达转换,所以会将rbp的值赋值为rsp的值。这样rbp和rsp的值都是dee8了。
在这里插入图片描述
由于中间过程没有进入出入栈的操作,所以一直到+75行,rsp和rbp的值都没变。
在这里插入图片描述
但是我们修改了栈上空间的值,即foo7的返回地址,
在这里插入图片描述
+75会抛出之前保存的rbp的值,这样rbp的值会还原到df10;同时rip会退栈,变成def0。
在这里插入图片描述
这样,foo7的返回地址(foo函数的入口地址)就处于栈的顶端。
在这里插入图片描述

离开时寄存器状态

rbp:df10

rsp:def0

foo

进入时寄存器状态

rbp:df10

rsp:def8

分析过程

foo7最后的ret会从栈中pop出next_rip,然后跳转到foo。这样退栈行为,让rsp变成def8。
在这里插入图片描述
+4行,会将rbp压入栈,这样rsp变成了def0。
在这里插入图片描述
+5行会让rbp等于rsp。这样foo函数就会可以在自己的栈底上分配空间和修改变量。
在这里插入图片描述
+8行会给foo函数分配0x10大小的栈上空间。这样rsp的值就会变成dee0。
在这里插入图片描述
+12和+16行分别会赋值一个栈上变量以及修改foo函数的返回地址
在这里插入图片描述
打印输出函数执行完,栈的状态没有变
在这里插入图片描述
+36行的Leave做了几件事:

  • mov %rbp, %rsp。即还原栈顶寄存器(对应于+5行),这样rsp变成def0。
  • pop %rbp。这样保存在栈顶def0的df10会被赋值给rbp。而rsp也因为退栈,从而变成了def8。
    在这里插入图片描述

离开时寄存器状态

rbp:df10

rsp:def8

main

进入时寄存器状态

rbp:df10

rsp:df00

分析过程

foo最后的ret会将foo的next_rip(即51fb)从栈中pop出来,这样rsp进一步变成了df00。

再次回到main函数后,rsp的值不再是离开main是的def8。
在这里插入图片描述
+63行又会让rsp进一步改变,变成df08。
在这里插入图片描述
+72的Leave,会干两件事:

  • mov %rbp, %rsp。还原栈顶寄存器(对应于+5行)。这样rsp变成df10。
  • pop出rbp。rsp变成df18。rbp变成1。
    在这里插入图片描述

离开时寄存器状态

rbp:1

rsp:df18

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值