1. 程式人生 > 其它 >日誌 2019.7.13 pwn 堆溢位基礎知識

日誌 2019.7.13 pwn 堆溢位基礎知識

十六進位制兩位表示一個位元組

堆溢位

先上堆圖:

堆的資料結構

一般情況下,物理相鄰的兩個空閒 chunk 會被合併為一個 chunk

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size; /*上一個chunk空閒時記錄上一個chunk的大小,上一個chunk被使用時作為上個chunk的資料部分 */
  INTERNAL_SIZE_T      size;       /* 本chunk的大小,低三位是標誌位*/

  struct malloc_chunk* fd;         /* 本chunk空閒時指向下一空閒的chunk,被使用時作為資料部分的指標 */
  struct malloc_chunk* bk;        /* 本chunk空閒時指向上一空閒的chunk,被使用時作為資料部分 */

  /* 僅在大chunk中使用,指向的都是大的chunk ,用於對大的chunk按照大小排序,以減少遍歷chunk的開銷*/
  struct malloc_chunk* fd_nextsize; /* 指向上一個與本chunk不同大小的chunk */
  struct malloc_chunk* bk_nextsize;  /* 指向下一個與本chunk不同大小的chunk */
};

size的低三位標誌位從高到低是:
A:NON_MAIN_ARENA,是否不屬於主執行緒,1 表示不屬於,0 表示屬於。
M:IS_MAPPED,是否由 mmap 分配
P:PREV_INUSE,是否被分配
bin

會根據空閒的 chunk 的大小以及使用狀態將 chunk 初步分為 4 類:fast bins,small bins,large bins,unsorted bin

其中 unsorted bin, small bins,large bins 依次被組織到一個數組中,陣列下標表示的是chunk上的fd或者bk指標,並不代表第幾個bin。每一個bin中都是一個連結串列,包含著多個chunk

1 unsorted bin
沒有排序的bin
來源:
1)當一個較大的 chunk 被分割成兩半後,如果剩下的部分大於 MINSIZE,就會被放到 unsorted bin 中。
2)釋放一個不屬於 fast bin 的 chunk,並且該 chunk 不和 top chunk 緊鄰時,該 chunk 會被首先放到 unsorted bin 中。

2 small bins
陣列下標 2 - 63 是small bin,每一個bin下的連結串列儲存著相同大小的chunk,每兩個相鄰的bin代表的大小為 2個機器字長
small bins 中每個 chunk 的大小與其所在的 bin 的 index 的關係為:chunk_size = 2 * SIZE_SZ *index,具體如下
| 下標 | SIZE_SZ=4(32 位)| SIZE_SZ=8(64 位)|
|--|--|--|--|
| 2 | 16 | 32 |
|3 | 24 | 48|
|4 |32 | 64|
|5 |40 |80|
|x | 2*4*x | 2*8*x|
|63 | 504

| 1008 |
分配採用 FIFO 的規則

3 large bin
small bin之後是large bin,一共有63個bin,每個bin代表一定範圍內的chunk,相同大小的按照最近使用排列
以 32 位平臺的 large bin 為例,第一個 large bin 的起始 chunk 大小為 512 位元組,位於第一組,所以該 bin 可以儲存的 chunk 的大小範圍為 [512,512+64)。

數量 公差
1 32 64B
2 16 512B
3 8 4096B
4 4 32768B
5 2 262144B
6 1 不限制

4 fast bin
當釋放的空間大小小於MAX_FAST_SIZE時,為了節省下一次的遍歷開銷,會將這部分的空間存放到fastbin中,這樣這些經常用到的小塊就不用花時間在合併和分割上。(fastbin 範圍的 chunk 的 inuse 始終被置為 1。因此它們不會和其它被釋放的 chunk 合併。)
分配採用LIFO後進先出的規則
fastbin 最多可以支援的 bin 的個數為 10 個

/* The maximum fastbin request size we support */
#define MAX_FAST_SIZE (80 * SIZE_SZ / 4)
預設情況下(32 位系統為例), fastbin 中預設支援最大的 chunk 的資料空間大小為 64 位元組
top chunk

地址最高的chunk
當所有的bin都無法滿足時,若 top chunk 大於申請的空間,則分割,分割後剩餘的部分作為新的 top chunk。
初始情況下,我們可以將 unsorted chunk 作為 top chunk。

名詞:

chunk:塊
arena:作業系統分配給程式的一塊較大的(大於所申請的)記憶體區域
last remainder:在使用者使用 malloc 請求分配記憶體時,ptmalloc2 找到的 chunk 可能並不和申請的記憶體大小一致,這時候就將分割之後的剩餘部分稱之為 last remainder chunk ,unsort bin 也會存這一塊。top chunk 分割剩下的部分不會作為 last remainer.

堆操作:

  1. malloc(size_t n)
    返回對應大小位元組的記憶體塊的指標
    當 n=0 時,返回當前系統允許的堆的最小記憶體塊
    當 n 為負數時,由於在大多數系統上,size_t 是無符號數(這一點非常重要),所以程式就會申請很大的記憶體空間,但通常來說都會失敗,因為系統沒有那麼多的記憶體可以分配
    使用brk申請的堆空間會挨著資料段,而使用mmap的堆空間在較高的地址
    這些記憶體在釋放之後一般不會返回系統而是在程式中等待申請

malloc()會使用兩種系統呼叫:
1)brk()或者sbrk()函式
作業系統提供了 brk 函式,glibc 庫提供了 sbrk 函式,sbrk會呼叫brk
初始時,堆的起始地址 start_brk 以及堆的當前末尾 brk 指向同一地址

brk(address) 直接將program break修改到address,返回值 0成功 -1 失敗

sbrk(n) 修改program break向高地址增長n個位元組,返回開闢之前的program break地址
sbrk(0)返回當前program break的地址
但是program break的增長不是無限制的,天花板是Memory Mapping Segment

2)mmap()
當申請的空間大於128k時,malloc()呼叫mmap()

  1. free(void* p)
    會釋放由 p 所指向的記憶體塊
    當 p 為空指標時,函式不執行任何操作
    當 p 已經被釋放之後,再次釋放會出現亂七八糟的效果,這其實就是double free
    除了被禁用 (mallopt) 的情況下,當釋放很大的記憶體空間時,程式會將這些記憶體空間還給系統,以便於減小程式所使用的記憶體空間

申請空間

malloc --> __libc_malloc --> _int_malloc

__libc_malloc(size)

使用者申請的位元組一旦進入申請記憶體函式中就變成了 無符號整數。
尋找鉤子hook ----》 尋找arena ----》 呼叫_int_malloc分配記憶體 -+--》成功,返回記憶體
                                          ↑                 |
                                          |                 ↓
                                          +-----分配失敗,再尋找一個arena
_int_malloc()

--------------------------------------------------------------------------------

將size轉化為對應的chunk大小 ----》 fastbin ----》 遍歷(後進先出),檢查大小是否符合 ----》 符合則計算索引 ----》 chunk轉換為記憶體返回
根據大小選擇bin            ----》 smallbin ----》獲取索引、指標 ----》 檢查該bin是否為空 ----》 不為空 ----》將連結串列中最後一個chunk分配(先進先出)
                                                                                      |           +----》 初始化
                                                                                      +---》 該bin為空
                          ----》 不在fastbin和smallbin中 ----》 malloc_consolidate():處理fastbin ----》 可以合併的合併,然後放 unsorted bin ----》大迴圈
                          
----------------------------------------------------------------------------------

大迴圈 ----》 遍歷unsorted bin ----》 FIFO尋找大小剛好合適的bin ----》若有,bin轉為記憶體後返回
迴圈10000次                                                  ----》若沒有,則將當前的unsorted bin按照大小放至對應的small或large中
      ----》 遍歷large bin ----》對應的 bin 中從小(連結串列尾部)到大(頭部)進行掃描 ----》 找到第一個合適的返回
      ----》 若大小合適的bin都不存在,則在map中找更大的bin遍歷 ----》 找到,返回記憶體
                                                           ----》 找不到,使用top chunk ----》 滿足,分割後返回
                                                                                       ----》 不滿足,使用 sysmalloc 來申請記憶體

------------------------------------------------------------------------------------

//從 fastbin 的頭結點開始取 chunk(LIFO)
sysmalloc()

用於當前堆記憶體不足時,需要向系統申請更多的記憶體
函式內呼叫了brk()和mmap()

釋放空間

free --> __libc_free --> _int_free

_int_free()

檢查 ----》是否fastbin ----》是fastbin,放至fastbin連結串列表頭
                      +---》是否mmap分配 ----》 是,munmap_chunk()
                                        +---》 否,合併chunk ----》 向低地址合併 ----》想高地址合併 ----》 下一個是否是top chunk ----》 是,合併到top chunk
                                                                                                                            +---》 否,合併加入unsorted bin
tcache

ubuntu 17.10中的新資料結構,提升了堆的效能,犧牲了一定的安全效能

堆溢位原理

條件:

  1. 可以寫入堆
  2. 大小無限制

利用策略:

  1. 覆蓋下一chunk的內容
  2. 利用堆機制實現任意地址寫

要點

由低地址向高地址方向增長

堆管理器會對使用者所申請的位元組數進行調整,這也導致可利用的位元組數都不小於使用者申請的位元組數

puts()內部會呼叫 malloc 分配堆記憶體

rwxp中的p許可權是指隱私資料

一個程式一旦編譯好後,text segment ,data segment 和 bss segment 是確定下來的

堆分配的最小單位是機器字長的2倍,32 位系統最小是 8 個位元組,64 位系統最小是 16 個位元組,比如 64 位系統執行malloc(0)會返回使用者區域為 16 位元組的塊。

prev_size欄位為8位元組

strlen 是我們很熟悉的計算 ascii 字串長度的函式,這個函式在計算字串長度時是不把結束符 '\x00' 計算在內的,但是 strcpy 在複製字串時會拷貝結束符 '\x00'

qword是四位元組

申請一個超大的塊,來使用 mmap 擴充套件記憶體。因為 mmap 分配的記憶體與 libc 之前存在固定的偏移因此可以推算出 libc 的基地址

堆分配函式

  1. malloc(size):見上文“堆操作”
  2. calloc(size):calloc 在分配後會自動進行清空,這對於某些資訊洩露漏洞的利用來說是致命的
calloc(0x20);
//等同於
ptr=malloc(0x20);
memset(ptr,0,0x20);
  1. realloc(ptr,size)
    當 realloc(ptr,size) 的 size 不等於 ptr 的 size 時
    1)如果申請 size > 原來 size
    ----如果 chunk 與 top chunk 相鄰,直接擴充套件這個 chunk 到新 size 大小
    ----如果 chunk 與 top chunk 不相鄰,相當於 free(ptr),malloc(new_size)
    2)如果申請 size < 原來 size
    ----如果相差不足以容得下一個最小 chunk(64 位下 32 個位元組,32 位下 16 個位元組),則保持不變
    ----如果相差可以容得下一個最小 chunk,則切割原 chunk 為兩部分,free 掉後一部分
    3)當 realloc(ptr,size) 的 size 等於 0 時,相當於 free(ptr)
    當 realloc(ptr,size) 的 size 等於 ptr 的 size,不進行任何操作

危險函式標誌

輸入
gets,直接讀取一行,忽略 '\x00'
scanf
vscanf

輸出
sprintf

字串
strcpy,字串複製,遇到 '\x00' 停止
strcat,字串拼接,遇到 '\x00' 停止
bcopy

除錯方法

cat /proc/pid/maps 可以檢視該程序記憶體對映

檢視堆:
vmmap:檢視堆地址
x/10gx addr:64位地址寬度以16進位制列印10個地址內容

find xxxx:用來尋找你的輸入內容

具體漏洞型別

  1. off_by_one 單位元組溢位
    1)溢位可控制任意位元組:修改size來洩露或者覆蓋其他資料
    2)溢位NULL:在 size 為 0x100 的時候,溢位 NULL 位元組可以使得 prev_in_use 位被清,這樣前塊會被認為是 free 塊。
    (1) 這時可以選擇使用 unlink 方法(見 unlink 部分)進行處理。
    (2) 另外,這時 prev_size 域就會啟用,就可以偽造 prev_size(間接控制本chunk的size) ,從而造成塊之間發生重疊。此方法的關鍵在於 unlink 的時候沒有檢查按照 prev_size 找到的塊的後一塊(理論上是當前正在 unlink 的塊)與當前正在 unlink 的塊大小是否相等