我们在做pwn题目的时候,常常会使用很多类似于rop之类的技巧,这些技巧往往对溢出长度有着很高的要求。但是有的时候,我们所能溢出的部分可能只有一个ebp和一个ret的地址,甚至有的时候只能溢出ebp,这个时候使用rop等技巧往往不能够getshell,这里所介绍的栈迁移,其实就是利用比较小的溢出条件,通过两次leave和ret去成功完成栈溢出利用。
1.栈迁移使用条件
首先是canary,这个是所有栈溢出都怕的东西,虽然可以绕过,但是还是比较困难的。
然后就是溢出至少能够覆盖整个ebp寄存器,不然没法实现迁移。
最后就是你要有地方能够迁移过去,也就是得有个可写的地方,首先考虑bss段,bss段有个最大的特点,就是可读可写。我们把栈迁移过去后,就可以执行比如rop之类的溢出手段了。
2.预备知识
这里不再介绍函数的调用栈,请大家自行补充。
1.对ebp寄存器的理解
这个部分非常重要,因为栈迁移实际上就是把ebp寄存器和esp寄存器所指的位置换了个地方。而很多人对ebp寄存器并没有一个非常深入的理解。尤其是对于ebp以及ebp的内容的理解。大家都知道,ebp寄存器所指向的就是当前函数调用栈的栈底,然而,ebp寄存器里面存了什么,其实大家没有一个比较深刻的理解。比如ebp里面存的值是0xffe78534,这其实说明ebp寄存器是指向0xffe78534这个内存地址的。而ebp的内容,则是这个地址里面存了什么,如下图。
我们可以看到,ebp所指向的这个地址里面存的是0xffe78594,往往这个地方存的是调用这个函数的函数的栈底寄存器的地址。
2.leave指令
leave指令和mov esp,ebp pop ebp的作用是一样的(但是这是不同的指令,只是说作用一样),是一条调整栈的指令。也就是把ebp的值赋给esp,这个时候ebp和esp指向同一个位置,接着把esp的内容,也就是所指向的地址存的值赋值给ebp。下面是这个过程(字有点丑):
3.ret指令
这个指令的本质就是pop eip,这个指令比较常见,我在这里就不做过多讲解。
3.栈迁移原理
重要的事情提前说,栈迁移最最核心的就是两次的leave和ret
重点关注leave的行为,mov ebp,esp这条语句以ebp为基准,调整了esp的位置,而pop ebp这条语句则是调整了ebp的位置,也就是把栈顶的值pop给ebp。我们本质是要让栈到另一个位置,所以我们需要把ebp和esp全部修改到那个位置。修改顺序显而易见,我们需要先修改ebp寄存器的地方,因为esp寄存器是以ebp寄存器为基准调整的。而我们的leave指令的行为,是先调整esp,所以我们需要两次leave指令,而第一次ret指令的作用就是让我们找到下一个leave和ret在哪里,而在第二次ret之前我们就把所有的栈全部迁移成功了,第二次ret和普通栈溢出一样,放你的system的地址或者放rop链。
首先,在leave时,我们的第一个行为是mov ebp,esp,这个语句只是让esp和ebp指向同一个位置,由于ebp寄存器的位置这个时候无法修改,所以无法利用,而第二个行为,是pop ebp,也就是把栈顶的值赋值给ebp,然后pop栈,也就是让ebp指向原先ebp的内容,其实也就是调用者函数的ebp寄存器位置,那如果我们在执行leave时,通过栈溢出的方式把这个ebp寄存器所存的地址,也就是ebp的内容修改,也就是让ebp寄存器在执行leave时跳到一个我们规定的地方,比如修改成某个我们可读可写并且足够大的地方,这个时候我们就可以把这个我们跳转的地方当成我们的新栈,去实现一些比如rop之类的利用。
leave指令的作用,就是劫持ebp寄存器到我们想让他在的位置(一般是bss段,可读可写)
但是,这个时候,我们的esp寄存器还在原来的栈上,我们还要对esp寄存器进行调整。而我们知道,leave的第一个行为就是用来调整esp寄存器的,所以我们还需要一个leave,而我们需要把程序劫持到下一个leave和ret那里,这个时候我们可以利用我们的返回地址,把返回地址覆盖成下一个leave和ret的地方,然后通过ret劫持eip调转到那个地方即可。
ret指令的作用,就是劫持eip寄存器,到我们想执行的代码的位置,两次都是。
下面是一个图示过程
这个时候需要的是调整esp
这个时候,esp指向了shellcode的地址,而第二次ret时将会触发。
4.思想和一些讨论
我们无论是在ctf和在平时挖漏洞的时候,我们会发现我们经常对栈或者堆的数据进行修改,从而达到我们的目的,计算机在运行指令,从栈上取值时,不会去检查你的数据是不是正确的,计算机永远只是个状态机,状态机只会没有感情地执行你给他的命令,无情地读取栈的值,而我们挖漏洞时所关注的指令,很多时候是一些指令,从栈上取值然后赋值给某个变量或者寄存器,或者把某个地址赋值给某个指针,比如双向链表的删除操作如果没有加上保护机制,可以修改栈上的数据,使得在删除节点时,得到一次往任意位置写数据的机会。而我们关注的指令还有一些跳转指令,比如jmp系列,ret,leave之类的,特别是如果这些指令跳转的依据是栈上面的某个值,那么将会有很大的可能把程序劫持到一些奇怪的地方。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程