pwnable.kr題解之uaf

前言:
這道題目反應了uaf的基本原理,pwn入門必做的題目,如果這一塊瞭解的不夠透徹,直接去打現在涉及各種奇技淫巧的pwn,肯定會被繞暈掉。所以爲了照顧萌新(其實是我自己菜)把這道題目單獨拿出來寫一下。

這是一道uaf的題目,把二進制文件拉到本地來研究
在這裏插入圖片描述
在分析前,先簡單說一下c++的虛函數和uaf的前置知識
在c++中,如果類中有虛函數,那麼這個類就會有一個虛函數表的指針vfptr,而子類會繼承。
在這裏插入圖片描述
uaf的原理:
在釋放內存後未將指向原內存的指針置爲null,use after free的意思就是在釋放以後進行use。
舉個簡單的例子:
在這裏插入圖片描述
原來的p指針指向一個結構體,當結構體沒釋放之後沒有將p置爲null,如果我們重新分配原結構體大小的空間,則指針p可以繼續使用。但是再次使用時,因爲p指針指向的內存包含的數據不是原來的數據了,此時的引用對於正常程序來說是有風險的,不過對於黑客來說,反而方便其進行控制。比如可以進行這些攻擊:
任意地址讀:puts(p->name)—————>puts(char*(addr2))
任意地址寫:strcpy(p->name,data);——>strcpy((char *)(addr2),data)
控制流劫持:p->func()———————>call addr3

這次的uaf題目基本相當於任意地址寫
先看源碼
uaf@pwnable:~$ cat uaf.cpp
#include <fcntl.h>
#include
#include
#include
#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;	

}
在這裏插入圖片描述
human父類,man和woman兩個子類繼承自human父類
human父類存在虛函數,會創建有虛表指針指向的一個虛表。Man和woman子類會繼承,子類的虛表中會繼承父類的所有項(並且當子類存在同名虛函數時,會修改vtable表項,指向自己的函數的地址。如果父類有私有函數,但是這個私有函數是虛函數,那麼子類的vtable中同樣會有這個函數的表項。每個虛表只有一個vptr,就算有多個虛函數也一樣。但是當多重繼承的時候,就會有多個vptr。

再看main
在這裏插入圖片描述
可以知道程序運行後供我們輸入
在這裏插入圖片描述
1會分配內存,2會寫內存,3會釋放內存。
根據uaf的原理,程序運行後會自動分配內存,我們需要先釋放內存,然後將exp寫入data,這樣當輸入1時就會被我們劫持了。
這裏需要注意幾點:
1.輸入2,也就是case2時,需要確定要分配多少內存給我們寫,我們知道原程序申請了int age爲4字節,string name爲16字節,加上一個虛函數指針4字節,共24字節。
2.這裏程序自動申請分配給了man,woman,以24字節爲單位。而case3是先delete m,在delete w,所以我們這裏需要分配兩個24字節的內存,即按兩次2才能得到m所指向的空間
3.case2需要制定24字節的長度,以及要從哪個文件中讀內容來覆蓋原先分配的空間,這個文件可以隨意指定,關鍵是文件的內容是什麼,這就是我們接下來要研究的地方

注意到在我們use after free的use步驟,也就是輸入case1的時候,按照程序邏輯而言執行的是introduce
其實這裏調用的是父類human::introduce,而我們想要的是giveshell。
由前面虛函數的知識我們可以知道,這兩個虛函數是在一張表上的,那麼我們只要在調用human::introduce之前將其地址改爲giveshell的,這樣在輸入case1的時候就可以拿到shell了。
虛表裏面一共就兩項,第一項是giveshell,第二項是introduce,關鍵就是找到兩者間的偏移,以及虛表指針
在這裏插入圖片描述
可以看到introduce和giveshell差了8
在這裏插入圖片描述
上圖是case1的彙編
可以看到執行了add rax,8後會執行introduce,那麼我們給rax的值-8,這樣執行了該指令後就會執行giveshell
虛表原地址我們知道是0x401570
所以我們現在要把它覆蓋成0x401570-8=0x401568
也就是說我們case2,在分配24字節,寫0x401568來覆蓋原內容,根據內存佈局,其實就是相當於覆蓋了虛表指針vfptr

所以pwn的步驟就很簡單了,如圖所示
在這裏插入圖片描述

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