栈迁移

文章发布时间:

最后更新时间:

文章总字数:
1.3k

预计阅读时间:
4 分钟

我们在基础知识扩展的时候,说到了为了避免有些题目供我们构造的字节数过少,以至于无法给system函数传参的时候该怎么解决

今天所要讲到的内容,也是和栈溢出字节数不够有关

当可以供我们编写的字节数仅够覆盖到ret addr时,并且该程序内并没有后门函数可以供我们利用,我们又该如何实现系统调用呢?

我们以往的简单栈溢出是通过覆盖ret addr的办法控制程序执行流导向后门函数的位置

但是其本质上 ebp和esp并没有被我们所控制,他仍然是按照原先栈底的汇编代码所运行的

所以我们换个思路?不妨劫持esp和ebp,让他们前往bss段或者其他可以供我们自由写入的区

这样我们就可以自己构建一个后门函数,并且将程序执行流引导至其

那问题就来到了如何劫持esp和ebp 我们先得清楚一下栈帧这个概念

栈帧

栈帧也叫过程活动记录,是编译器用来实现函数调用过程的一种数据结构

简单理解就是每次函数的调用,都会生成自己的栈帧

栈帧就相当于函数的调用框架,包含了函数的参数,函数的局部变量,函数执行完后的返回地址

系统是如何定义一个栈帧的?ebp指向了栈帧的栈底,esp指向了函数的栈顶

也就是说,我们把esp和ebp劫持的目的,就是让系统错以为我们写入shellcode的bss段(包括但不限)是一个栈帧

从而执行他

栈迁移原理

归根到底,就是要如何劫持esp和ebp

回到我们最开始的栈溢出,我们要溢出的字节数=变量var距离esp的字节数+一个字长

这里的一个字长覆盖的是ebp

在我们没有对ebp覆盖的时候,其保存的是上层函数的栈底地址,而ret addr保存的是上层函数执行到了哪个地方,方便子函数结束后返回父函数最后执行的地方

在一个栈帧结束的时候,eip 即将执行 leaveret 两条指令恢复现场(即返回父函数)

leave指令相当于 mov esp ebp和pop ebp

他将ebp和esp指向同一地址,这一步相当于腾出了栈帧空间

随后pop ebp 将此时esp指向的old ebp(因为我们上面说过了嘛,ebp保存的是上层函数的栈底地址)赋值给真正的ebp(此时的ebp是定义栈帧栈底的ebp)

是不是有点晕?首先你要分清楚ebp保存的内容和ebp寄存器这两个概念

在子函数调用开始之前,系统会将父函数栈底的地址弹出到新的栈帧,这个值就是ebp(就是我们之前栈溢出用垃圾数据覆盖的那个嘛)

然后记录下当前父函数运行到的地址,将其弹出为ret addr,等子函数结束以后,就会返回到这个地址

所以说,如果我们覆盖ebp的时候不用垃圾数据,而是放入我们要使ebp迁移到的地址,那么ebp就会被我们挟持走

但是此时还有个esp寄存器怎么办?栈帧的空间需要这二者才能定义

你还记不记得我们构造rop链的手法?我们自己再找一个leave的汇编代码地址然后覆盖ret addr不就好了?

此时mov esp ebp会起到什么效果?ebp已经指向了我们要迁移的地址,所以esp也被挟持到了那边

但是注意,还有一句pop ebp 虽然这句没有任何作用,因为此时新的栈帧的栈顶,其保存的已经是我们要挟持到的地方的地址

但是这一句是出栈指令,此时我们的esp,他指向的地址就会增加一个字长

a

如图所示,HijackAddr就是我们想要劫持esp ebp到的地址

那栈迁移运作的原理我们已经搞清楚了是吧,接下来想办法构造payload

payload = cyclic(offset)+pxx(addr)+pxx(leave_addr)

这一个没有问题吧

那只剩下最后一个问题了,我们迁移到的那个地址的栈内容要怎么编写

b

aaaa是我们最开始的那个地址存放的垃圾数据,即上文说到的HijackAddr,因为pop ebp的原因,esp会指向高一个字长的地方

dddd则是32位情况下的传参,中间要隔个垃圾数据,这没什么好说的

下一个binsh_addr 和binsh字符串是什么意思,当程序连binsh都没给我们的话,反正我们都能自己编写一段栈帧了,我们不是可以自己写入一段binsh,然后我们也知道其地址了,不是就能调用了

后面的old_ebp和ret_addr也没什么好说的,就是一段栈帧必须的要素