Winesap's Blog

0ctf 2015 Freenote Write Up

| Comments

Double free 漏洞利用,順便來試試之前想到的某個好用的 unlink() 利用法,建議對 heap exploitation 先有一些瞭解。

unlink()

狀況: 存在一個指標 long *p&p 已知且 p 指向 heap 上某處,p[2]p[3] 內容可控制,且能以某種方式觸發 unlink(p, BK, FD)。那麼就能改寫指標指向的位址為 p = &p - 3

通常情況是, p 是某個全域變數,且因為程式本身沒 ASLR,所以 p 存放在哪裡本來就知道,即 &p 已知。而這題的指標是放在 stack 上,位址不固定。但也存在一個 information leakage 漏洞,使得 heap 的基底位址可以被計算出來,所以只要是在 heap 上的指標,位址也都是已知的。

那麼方法很簡單,假設 p 指向某個存放字串的 buffer,所以控制 p[] 的內容沒問題。現在先設好 p[2] = &p - 3; p[3] = &p - 2,接著若因為有 heap overflow 或 double free,可以偽造 chunk 並觸發 unlink(p, BK, FD),此時會發生如下的動作:

  1. FD = p->fd = &p - 3, BK = p->bk = &p - 2
  2. 檢查 FD->bk != p || BK->fd != p,不過因為 *(FD + 3) == p && *(BK + 2) == p 所以順利通過
  3. FD->bk = BK,即 p = &p - 2
  4. BK->fd = FD,即 p = &p - 3

最後 p = &p - 3,該指標被改成指向其存放位址前面一點點。之後若能有對這個指標的寫入,就可以再覆蓋掉指標,再寫一次就是任意位址寫入了。

                                                                          heap
                                                                    |                |
                                                                    +----------------+
                                                          +-----> p |                | <- unlink()
                                                          |         +----------------+    ^
                                                          |         |                |    |
                                    .bss or .data         |         +----------------+    |
                                                          |   p->fd |  p[2] = &p - 3 |    |
  +----------------+               +----------------+     |         +----------------+    |
  |   prev_size    |    FD: &p - 3 |                |     |   p->bk |  p[3] = &p - 2 |    |
  +----------------+               +----------------+     |         +----------------+    |
  |     size       |    BK: &p - 2 |                |     |         |      .         |    |
  +----------------+               +----------------+     |                .              |
  |      fd        |               |                |     |         |      .         |   /
  +----------------+               +----------------+     |         +----------------+ /
  |      bk        |            &p |       p        | ----+         |    prev_size   | <- _int_free
  +----------------+               +----------------+               +----------------+
                                                                    |                |

洩漏 Heap 基底位址

初始化存放 note 的內容的 note[i]->str 時,會 malloc() 一塊大小為 128-byte 對齊的空間。而這個內容字串在輸入時,結尾是不會補 '\0' 的,也沒有多開額外的空間。因此在輸出時可以一併把後面接續的內容印出來。如果巧妙的讓某個非使用中 chunk 的 fd 欄位指向另一個 chunk,並且讓 note->str 剛好接上,就可以把 chunk 的位址洩漏出來。

這裡用 note[8]->fd (offset = (128+16)*8 = 1152),剛好和輸入大小為 1152 (128-byte aligned) 的字串切齊。

任意位址讀寫

接下來要利用上述的 unlink() 修改 note[0]->str。首先建立 2 個 notes X, Y (size=128),刪掉 X, Y 後再建一個 note Z (size>128),Z 的內容可以覆掉 Y 的 chunk prev_size 和 size。因為刪除操作並沒有做任何檢查,可以再刪除一次 Y,造成 double free 並且根據偽造的 Y chunk 內容,觸發 unlink(note[0]->str)

 
----------------------------------------------------------------------------------------------------
                | X                       | Y                      |            fake next_chunk of Y
<< note[0]->str |-----------------------------------------------------------------------------------
                | Z                         prev_size  size                     prev_size  size
----------------------------------------------------------------------------------------------------
      

改寫 note[0]->str 後,對 note 0 做一次編輯,長度不要變。這樣會覆蓋掉 note 總數和 note[0] 的內容,可以把 note[0]->str 指向任意位址。再讀取 note 0 時就可以洩漏任意位址的內容。如果再對 note 0 做一次編輯,就可以修改該位址的內容 (或者第一次編輯 note 0 的長度足夠,就可以寫掉 note[1]->str,之後修改 note 1 就可以重覆使用這個任意讀寫漏洞)。

洩漏 free@got.plt 後,計算 libc.so.6 的基底位址。將 free() 修改為 system() 後,只要建立一個 note 內容為 "sh" 再刪除,就可以執行 system("sh")

Exploit: freenote.py

Comments

comments powered by Disqus