

思路:
bank是bss段上的,栈空间不足,把泄露libc的东西放在bank上,通过buf跳转到bank泄露libc去找到libc基址
然后加上对应libc的one_gadget偏移,跳转过去执行ong_gadgat就可以了
脚本
1 | from pwn import * |
收获
p64(ret)*0x20
原因
当 main 函数再次被执行时(因为你的 ROP 链最后跳转到了 main 函数的地址):
程序会为新的
main函数调用重新分配一个栈帧。在这个新的栈帧中,会再次为局部变量
buf[96]分配空间。
为什么“临时变量的空间会覆盖到 GOT 表”?
这里就是关键点。在 64 位程序中,栈是向下增长的(从高地址向低地址)。
- GOT 表通常在程序的低地址区域。 (例如:
0x0601018) bank变量在.bss段,通常地址比 GOT 表高。 (例如:0x0601080)- 栈会向下增长。 当
main函数再次被调用,并且它在堆栈上分配buf等局部变量时,这些变量会占用rsp向下增长的内存空间。
现在想象一下:
- 你的栈迁移,把
rsp设置到了bank处(例如0x0601080)。 - 你用
p64(ret)*20把你的 ROP 链推到了bank + 160(例如0x0601120)。 - 你的 ROP 链执行,泄漏
puts地址。 - 然后,你的 ROP 链最后执行
p64(main),导致程序跳转回main函数的开头。 - 当
main函数再次执行时,它会在栈上重新分配空间。 此时,新的栈帧可能会从rsp的当前位置(即0x0601120,也就是你的 ROP 链最后执行完的位置)开始向下(向低地址)增长。 - 如果
main函数分配的局部变量(例如buf[96])的新空间,在栈向下增长的过程中,恰好“覆盖”到了 GOT 表所在的内存区域(例如0x0601018),那么 GOT 表的地址就会被这些局部变量的垃圾数据所破坏。
p64(ret)*20 的作用
所以,p64(ret)*20 在这里的另一个重要作用就是:
- 它将你的伪造栈帧(即你写入
bank的 ROP 链)向高地址方向推得更远。 - 这样,当
main函数再次被调用时,它会从你伪造栈的末尾(bank + 160 + ROP 链长度)开始向下分配新的栈帧。 - 通过把你的伪造栈(和
rsp的位置)设置在足够高的地址,可以确保main函数再次分配的局部变量空间(栈向下增长的部分)不会侵犯到位于低地址的 GOT 表。
用图示来说明:
没有 p64(ret)\*20 的风险:
1 | 高地址 |
有了 p64(ret)\*20 的好处:
1 | 高地址 |
所以,这段解释指的是:p64(ret)\*20 能够将你的 ROP 链的“起点”抬高,从而避免当程序再次回到 main 函数并重新分配栈空间时,main 函数的局部变量(如 buf)在栈向下增长时,其空间与 GOT 表发生重叠,进而破坏 GOT 表。
这是一种非常常见的,也是非常重要的防御性策略,确保了后续攻击(例如获取 Shell)的可靠性,因为这些攻击通常需要一个完好无损的 GOT 表来解析函数地址。
大小
1.可以从一个小数开始尝试(推荐)
一般情况下20就够了
不能太大, read(0, &bank, 0x100uLL) 写入的。这个 read 函数最多只能读取 0x100 (256) 字节。
- 你的
payload长度是有限制的。 p64(ret)*N占据的字节数是N * 8。- 你的核心 ROP 链 (
p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)) 本身也需要空间。p64(pop_rdi): 8 字节p64(puts_got): 8 字节p64(puts_plt): 8 字节p64(main): 8 字节- 核心 ROP 链共
4 * 8 = 32字节。
如果 N 过大,例如 N=100,那么 p64(ret)*100 就需要 100 * 8 = 800 字节。这已经远远超过了 read 函数能读取的最大 0x100 (256) 字节。
one_gadget和LibcSearcher
one_gadget 是一个命令行工具,它用于分析 libc 库文件,并找到其中可以直接用于获取 shell 的指令序列的偏移量。
与传统的 system("/bin/sh") 方法相比,one_gadget 通常更简洁,因为它只需要一个地址就能获取 shell,而不需要 pop_rdi gadget、"/bin/sh" 字符串的地址和 system 函数的地址。 此外,one_gadget 通常对环境(包括栈对齐)的要求更宽松。
用法和shellcode一样
找到位置即可ibc
把泄露出来的函数地址在libc网站匹配

之后下载匹配的的libc版本
用命令
1 | one_gadget /home/p0ach1l/Documents/libc6_2.23-0ubuntu11_amd64.so |

找到合适的偏移量即可(地址下面是它要满足的条件)
LibcSearcher 匹配的版本是它自己数据库中的版本,而不是你本地计算机上 /lib/x86_64-linux-gnu/libc.so.6 这样的文件。
1 | system=libc_base+libc.dump('system') |
是用它LibcSearcher匹配的版本,而one_gadget是用我们匹配下载的libc文件去实际计算的,更准确
system("/bin/sh") 的失败:
system("/bin/sh")方法需要计算system函数的地址 (libc_base + libc.dump('system')) 和"/bin/sh"字符串的地址 (libc_base + libc.dump('str_bin_sh'))。如果这种方法失败,最核心的原因是
LibcSearcher为你选定的那个libc版本(例如libc6_2.23-0ubuntu10_amd64),虽然其puts偏移和one_gadget的特定偏移与远程服务器相符,但其内部记录的system函数和"/bin/sh"字符串的相对偏移量,与远程服务器实际libc中的这两个偏移量不一致**。libc版本号(如2.23)可能相同,但不同的发行版或编译选项(如ubuntu10vsubuntu11,或不同的构建日期)会导致内部函数和字符串的具体相对位置存在细微差异。即使puts和某个one_gadget的偏移恰好相同,system或"/bin/sh"的偏移可能就不同了。当你用错误的偏移量计算出
system和"/bin/sh"的地址并尝试跳转时,程序会因为RIP指向无效地址或system函数接收到无效参数而崩溃(dumped core)。