栈攻击类题目的花式小技巧
本文最后更新于:2024年4月22日 上午
栈是基本功()
前言
这篇文章是总结一些玩的比较花的栈题,都没啥难度,但是关键时刻卡人一下挺难受的。
花式栈迁移
想导演一场标准的栈迁移,需要满足三个条件:
- 能在一个可写地址编写ROP链子(同时也也意味着需要知道ROPgadget地址)
- 能连续执行两次leave ret
- 能劫持当前rbp到ROP链地址上方
一般来说栈迁移的例题是,不开PIE和canary,往bss段写一大堆东西,然后一个小栈溢出,仅仅溢出到返回地址八个字节。这是可以填充rbp为bss段写入地址开头上方,bss段内填充迁移后的rbp地址(一般也在bss内)+ROP链子,返回地址溢出为leave ret地址。这样就可以两次leave ret就可以迁移了
不会栈迁移?那这篇文章可能不适合你。去把BUUCTF第一页的题全刷完再来看吧(乐)
但是这个是理想情况,实际上,现在很少有这么平铺直叙的题目了
main+vuln
这个标题的意思是,漏洞掉出现在多次调用的函数内部,vuln函数leave_ret后,退到外面的main函数也有一个leave_ret。这样就免去了溢出到返回地址
栈旋转
名字是我乱起的,这种情况大致是,如果没有bss段可写,或者溢出实在太小以至于连rbp都没法劫持。我们可以选择低位覆盖last_rbp,这样在leave_ret后,rbp就会被抬到栈上另一个地址。
我们知道,栈地址是从高到低增长的。如果我们最低位覆盖的时候覆盖一个\x00,这个时候rbp大概率会抬到上一个函数中的局部变量区域。如果这里可以写进来ROP链子,且后面还能再调用leave_ret或类似的指令,就可以把栈帧迁移到栈段上的另一个位置。
因为rbp和rsp中间出现了上下反转的阶段,所以我叫他栈旋转。
这种打法通用性极强,只要有能有gadget地址即可,不需要栈地址,不需要溢出到返回值,也不强制需要elf地址(当然,一般来说你需要elf地址的gadget),但是缺点是需要爆破。如果溢出空间够大,可以采用添加ret指令作为滑板
半步迁移法
这是一种利用read函数调用的局部片段,在没有bss写入权限的情况下进行迁移的方法。但是这样做需要你能劫持程序的执行流,也需要程序有一个往栈上局部变量写东西的行为。
攻击的原理是编译时关于局部变量定位的策略。x86_64架构的程序的局部变量定位是根据rbp为基址进行变址定位的。
常规栈迁移的栈帧情况分为三个状态, 由两次leave_ret作为分界线。第一次leave_ret实现的是rbp的迁移。此时rsp在栈段,rbp在bss段。由于之前有溢出的read函数rsi参数是栈上局部变量,因此如果我们再次调用这个read(要包括前面的参数设置片段),就可以做到让bss上写东西,即铺上ROP链子。这一次溢出我们可以继续用leave_ret把栈迁移过来,就可以执行ROP链了。
有限字节数迁移
这种情况往往适用于有bss段的读入,但是bss可写长度极短的情况,这种需要精心布置在bss段上铺写的有限的gadget片段。其实也是利用read函数调用的汇编片段。只不过不是利用rbp偏移。
加入说栈迁移到了先前写到bss段上的位置,但是先前往bss可以写的东西太少了,不足以进行攻击。我们可以在bss段上铺设先前的那种rbp偏移的链子,也可以利用固定bss段read的操作。在有限的栈迁移上布置rdx增大的指令和read到bss的指令的片段(略过原本的rdx设置)。这样在read操作的时候,函数栈帧是刚从bss写入地址往下弹了两次,而新调用的read写在先前往bss写的地址。因此,这次被扩展了rdx的写入,就可以自己给自己铺设很长的ROP链子,就可以劫持执行流了
关于rdx怎么增大,这一点需要你随机应变找gadget