1. 程式人生 > 其它 >棧溢位——返回地址

棧溢位——返回地址

淹沒返回地址

覆蓋鄰接變數的方法利用條件太過苛刻,需要原始碼的結構符合漏洞利用才能實行。直接修改EBP或者函式返回地址的攻擊則更為通用。

0x00 原始碼

由於鍵盤能夠直接輸入的字元ASCII範圍有限,無法表達0x110x12等值,所以對程式碼稍作修改,通過讀取文字檔案輸入。

#include <stdio.h>


#define PASSWORD "1234567"


int verify_password (char *password)
{
	int authenticated;
	char buffer[8];
	authenticated=strcmp(password,PASSWORD);
	strcpy(buffer,password);	// Overflow
	return authenticated;
}
main()
{
	int valid_flag=0;
	char password[1024];
	FILE * fp;
	if(!(fp=fopen("./password.txt","rw+")))
	{
		exit(0);
	}
	fscanf(fp,"%s",password);
	valid_flag = verify_password(password);
	if(valid_flag)
	{
		printf("Incorrect password!\n");
	}
	else
	{
		printf("Congratulation! You have passed the verification!\n");
	}
	fclose(fp);
}

使用Visual C++ 6.0編譯,編譯選項預設,Build版本為Debug。務必避開Visual Studio系列的GS編譯選項。

0x01 分析

溢位工作包括:

  1. 瞭解棧中的情況,如函式地址距離緩衝區的偏移量等。雖然可以通過分析程式碼得到,但最好還是通過動態除錯挖掘;
  2. 得到程式通過驗證的指令地址,以便使程式直接跳轉到這個分支;
  3. 在password.txt中的相應偏移處填寫該地址。

首先反彙編得出驗證通過分支的指令地址為0x00401122,函式verify_password0x00401102處被呼叫,在0x0040110A處將EAX中函式返回的值取出,在0x0040110D處與0比較,再決定跳轉到哪個分支。

驗證通過的分支從0x00401122

處的引數壓棧開始,如果把返回地址覆蓋成該地址,那麼0x00401102處的函式呼叫返回後,程式無論如何將跳轉到驗證通過的分支。

0x02 溢位

以4個位元組為單位進行輸入,為方便檢視,每個單位都為“abcd”。

緩衝區buffer容量為8個位元組,需要2個單位填充。按照棧幀結構,向下為變數authenticated,需要1個單位填充。再向下為前棧幀EBP,需要1個單位填充。再向下為函式返回地址,需要1個單位填充。

通過十六進位制編輯方式製作password.txt:

注意:動態除錯時顯示的地址是經過轉換而便於閱讀的,其實際儲存方式為小端儲存。

之後通過OllyDbg除錯執行該程式,最終的棧情況為:

區域性變數名 記憶體地址 偏移3處的值 偏移2處的值 偏移1處的值 偏移0處的值
buffer[0~3] 0x0012FB14 0x61('a') 0x62('b') 0x63('c') 0x64('d')
buffer[4~7] 0x0012FB18 0x61('a') 0x62('b') 0x63('c') 0x64('d')
authenticated(修改前) 0x0012FB1C 0x00 0x00 0x00 0x01
authenticated(修改後) 0x0012FB1C 0x61('a') 0x62('b') 0x63('c') 0x64('d')
前棧幀EBP(修改前) 0x0012FB20 0x00 0x12 0xFF 0x80
前棧幀EBP(修改後) 0x0012FB20 0x61('a') 0x62('b') 0x63('c') 0x64('d')
返回地址(被覆蓋前) 0x0012FB24 0x00 0x40 0x11 0x07
返回地址(被覆蓋後) 0x0012FB24 0x00 0x40 0x11 0x22

執行情況:

由於EBP傳入了無效值,程式崩潰,但成功到達驗證通過的分支。