prettify code

2016年12月21日 星期三

[Write-up] WhiteHat Grand Prix 2016 - pwn500 Bun bo Nam Bo / note (中文)


Basic Info

附加檔案有兩個 32bit ELF notenote_client 以及 libc-2.19




環境是一般程度的保護,PIE 沒開以及 Partial RELRO

直接執行 note會看到一堆 Hex:



 IDA 打開看起來是正常的選單題:



那為何執行起來看到的是一堆 Hex 呢?


仔細分析後 note 中所有 I/O 都被實作會經過某種 encode/decode


先跳過這邊,看看 note_client 會做什麼

直接執行:


IDA 分析可以知道他想要開檔 config.txt,並連線 config.txt 中寫的 host:port

因此 config.txt 內容寫 127.0.0.1 31337,並使用 ncat note bind 在 127.0.0.1:31337 後執行 note_client



成功執行了!

note_client 基本上就是把 I/O 做 encode/decode 後跟 note 溝通,因此可以先省去逆向 note 當中的 IO encoding ,直接透過 note_client 來互動。

Info of note
  • Initialize: pool = (Pool*)malloc(0x1000), 結構為:
    struct Pool
    {
      int limit;
      int alive_amount
      Note note[255];
    };
    struct Note
    {
      int inuse;
      int sz;
      char *content;
    };
    

    其中 limit, alive_amount 不重要,用來限制 note 數量不會超過 255 而已。

  • list: 印出所有 inusetrue 的 note 
  • read: 指定 index
    1. 驗證 index 合法
    2. 驗證 note[index]->inuse 為 true
    3. 顯示 note 的 date/name/body
  • add: 新增一個 note,步驟為:
    1. 找 pool 裡第一個 inusefalse 的 note
    2. 輸入一個 size, 規定 32 <= size <= 1024
    3. note->content = malloc(size+48)
    4. 輸入 name:readn(note->content, 31)
    5. 輸入 date:readn(note->content+32, 15)
    6. 輸入 body: readn(note->content+48, size)
    7. note->inuse = true
    8. note->sz = size
  • free: 指定 index
    1. 驗證 index 合法
    2. 驗證 note[index]->inusetrue
    3. free(note[index]->content)
    4. note->inuse = false
  • edit: 指定 index
    1. 驗證 index 合法
    2. 驗證 note[index]->inuse 為 true
    3. 編輯 name:readn(note->content, 31)
    4. 編輯 date:readn(note->content+32, 15)
    5. 編輯 body: readn(note->content+48, note->sz)
Vulnerability

note 中有很多邊界檢查有多餘等號或不正確的檢驗,但事實上都不會有直接危害。唯一可以利用的洞是在讀輸入時有 overflow。邏輯大約是這樣(因為原本還有一些 decode 功能,這是簡化後的版本):

void readn(char *output, int len) {
  int i=0, c;
  while((c = getchar()) != '\n') {
    output[i++] = c;
    if(i >= len) break;
  }
  output[i] = c;
}

只要輸入剛好的長度且最後一個字元不是換行,就會造成 one null-byte overflow。

Exploit

接下來就是正常的 heap exploit 了。


Information Leak

因為只能 overflow 一個 null-byte,肯定想要 overflow 下一塊 chunk 的 size。首先利用 add 功能構造 heap layout:
add(36); add(36); add(316); add(36); add(36) 之後 heap 長這個樣子:

+---------------+---------+---------+------------------+---------+---------+-----------+
| pool: 0x1009  | 0: 0x59 | 1: 0x59 |     2: 0x171     | 3: 0x59 | 4: 0x59 | top_chunk |
+---------------+---------+---------+------------------+---------+---------+-----------+

接著刪除第一塊(free(0)),並且在編輯第二塊 (edit(1)) 時觸發 overflow:

           |------- 0xb0 ------|
+----------+---------+---------+----------------+-+---------+---------+-----------+
|  0x1009  | 0: 0x59 | 1: 0x58 | 2: 0x100(fake) | | 3: 0x59 | 4: 0x59 | top_chunk |
+----------+---------+---------+----------------+-+---------+---------+-----------+
           |  freed  |         | prev_size(0xb0)| |
           +---------+         +----------------+-+



size 壓上 null-byte 會因為 prev_inuse bit 為 0 而認為前一塊 chunk 已經被 free 了,此時 free(2) 就會將 0 與 2 號 chunk 合併:

+----------+---------+---------+-----------+------+---------+---------+-----------+
|  0x1009  |            0x1b1              | 0x70 | 3: 0x59 | 4: 0x59 | top_chunk |
+----------+---------+---------+-----------+------+---------+---------+-----------+
                     | 1: 0x58 | <-overlap!   ^ no use
                     +---------+

因此做出了一塊 0x1b0 的大 chunk,並且跟 1 號 chunk 是重疊的。
接著再 add(36):

+----------+---------+---------------------+------+---------+---------+-----------+
|  0x1009  | 0: 0x59 |     0x159(freed)    | 0x70 | 3: 0x59 | 4: 0x59 | top_chunk |
+----------+---------+---------+-----------+------+---------+---------+-----------+
                     | 1: 0x58 | 
                     +---------+

此時 read(1) 就能 leak 出被 free 的 small chunk 的 fdbk。並且只要在 read 之前先 free(3),就能同時 leak libc base 與 heap base (unsorted bin double linked list).

                                       +----------+
                                       |          v
+----------+---------+-----------------|---|------+---------+---------+-----------+
|  0x1009  | 0: 0x59 |  fwd: in libc, bck  | 0x70 |  freed  | 4: 0x58 | top_chunk |
+----------+---------+---------+-----------+------+---------+---------+-----------+
                     | 1: 0x58 | 
                     +---------+


Overwrite GOT

Information leak 完後,目標是能改寫到前面 pool 那邊的 char *content,如果能改掉 pool 上的 pointer 就能利用編輯功能改寫 got 從而控制 eip。

利用 smallbin chunk 合併時的 unlink 達到目的。

free(0); add(108); add(52)

+----------+-----------------+---------+------+------+---------+---------+-----------+
|  0x1009  |    0: 0xa1      | 2: 0x69 | 0xa8 | 0x70 |  freed  | 4: 0x58 | top_chunk |
+----------+---------+-------+-+-------+------+------+---------+---------+-----------+
                     | 1: 0x58 | 
                     +---------+

注意此時 1 號 note 可以亂改 note 2 的 size。我們在 1 號 note 當中假造一個被 free 的 chunk,並把 fd, bk 指向 pool 中 1 號 note 附近,如此可以使假造的 free chunk 成功被 unlink,並改掉 pool 中的 1 號 content 指標。

圖文並茂一下:

edit(1): 在 note1 構造假 chunk 並改寫 note2 的 prev_sizesize

heap           +----------+----------+
0x0000       : |   0x00   |  0x1009  |
0x0008(pool) : |   0xff   |   0x04   |
                         ...
0x0020       : |   0x24   | h+0x1068 |
                         ...
0x1008       : |   0x00   |   0xa1   |
0x1010(note0): |          |          |
                         ...
0x1068(note1): |   0x00   |   0x41   | <- fake chunk
0x1070       : |  h+0x18  |  h+0x1c  | <- fake fd|bk
                         ...
0x10a8       : |   0x40   |   0x68   |
0x10b0(note2): |          |          |
                         ...

free(2):

heap           +----------+----------+
0x0000       : |   0x00   |  0x1009  |
0x0008(pool) : |   0xff   |   0x04   |
                         ...
0x0020       : |   0x24   | h+0x1068 |
                         ...
0x1008       : |   0x00   |   0xa1   |
0x1010(note0): |          |          |
                         ...
0x1068(note1): |   0x00   |   0x41   |<-+
0x1070       : |  h+0x18  |  h+0x1c  |  | merge
                         ...            | 
0x10a8       : |   0x40   |   0x68   +--+
0x10b0(note2): |          |          |
                         ...

此時觸發 unlink(0x1068),unlink 的結果就是 *(h+0x1c+8)=h+0x18
h+0x24 從原本 h+0x1068 變為 h+0x18

因此再次 edit(1) 就可以改寫掉 note[1] 自己的 char *content,再一次 edit(1) 即可改掉 GOT。

exploit 中將 got_atoi 改成 libc_system,這樣下次輸入 index 時輸入 "sh" 即可開啟 shell。



BTW,因為 note_client 本身有在檢查輸入長度之類的資訊,所以必須要看懂並實作原本 IO 的加解密。基本上照 decompile 的結果寫一遍就可以了,細節可見 script 。

BTW2,這題即使開啟 PIE 與 Full RELRO 也一樣作法,最後一步改為改寫 libc 中的 free_hook 函式即可。

Exploit Script 

Flag:  WhiteHat{24b3e07a4494d4cd3ad973ee7d5fadca390df5bb}