栈漏洞系列(一)-- 栈迁移
原理
当溢出的长度太小时,可以通过leave_ret_gadget
来将ebp
和esp
迁移到我们想要的位置,比如:一个咱已经布置好ROP
链的地方
假设在栈上布置如下的ROP
链,我将通过图片来展示攻击效果
布置好之后,每个程序就返回前都会恢复栈,就如下图的程序,所以我们就等待程序leave ret
:
leave
就是等价于上面说到的那段汇编代码,首先执行的是mov esp,ebp
,这会让图中的0x108放入到esp
中,我们的栈就会发生如下变化:
接着就是pop ebp
,诶~,这时候我们的ebp
就变成了我们想要迁移的位置了:
我们在回顾一下栈上的情况,因为刚刚pop ebp
,所以我们的esp
指向了leave_ret
这个gadget
:
所以程序又执行了一次leave
,我们迁移后的栈就会变成这样:
非常的神奇吼!我们的esp
指向了system
函数!也就是说我们已经成功的获取了shell
!!!
可能有读者要问了:啊?,你这
ebp
不是栈底寄存器吗?他怎么指向了一个奇怪的地方,你这不行啊?,对,此时的ebp
已经指向了一个奇怪(后面的例题会展示奇怪的点)的地方,但是问题不大,我们只要明白ebp
和esp
最根本的作用是什么就不会觉得奇怪了,ebp
的作用是来定位每个函数的栈的大小,当函数返回时才需要通过ebp
来恢复主函数的栈,而esp
才是真正控制栈的进出,所以我们只要控制esp
指向我们想要指向的函数就可以达到我们的目的了,至于ebp
嘛,我们都拿到shell
了,它能不能成功的返回已经不关我事了嘿嘿嘿~
例题解析
这里拿19年国赛的一道题(ciscn_2019_es_2
)来讲解一下,经典入门题,在buu
上,大伙可以去玩玩
习惯checksec
一下:
main
函数看看:
vul
函数不用多说,肯定就是受害函数了,仔细一看,只能溢出8字节吼!也真够抠的,不过我们刚刚学了栈迁移,不慌
我们一个个来看吼,首先程序memset
了s
这个数组,但是很奇怪,它初始化又不全部清零,就整了0x20个字节,它为啥要这么做呢,其实就是在泄露信息,如果它没全部清零,那后0x10是什么东西呢?诶~,就是栈上的地址对吧,所以当我们覆盖了s
数组最后的"\n"
,那printf
打印s的时候,是不是就不会截断,是不是就打印了那未初始化的值,也就泄露了栈上的地址,有人可能又有疑问了,泄露栈上的地址有什么用呢?别急,慢慢来!
在第一次read
()的时候,我输入了24个a
和我的id
,计算一下s的位置到ebp
的距离,拿起你的小计算器计算一下:0xa8 - 0x80
是不是等于0x28,至于为啥要到ebp
呢,别问,问就是后面好定位偏移
按照上面的想法我们写出下面的exp:
1 | io.recvuntil('your name?\n') |
前面我说过,我们栈迁移是迁移到另一个有我们ROP链的地方,但是这里我们并没有办法往其他地方写入我们的ROP链,我们只能往栈上写入,所以我有个大胆的想法,栈迁移到栈上,这样我们刚刚泄露的栈地址就有用了是不,所以我们在第二次read()的时候就将栈布置成这样:
1 | payload = 'aaaa' |
咱动调看看:
leave之后:
注意esp
和ebp
,是不是esp
比ebp
还大哈哈哈:
再往下执行,咱就发现esp
指向了system
的plt
:
完整EXP:
1 | #!/usr/bin/python |