天天看點

棧溢出——傳回位址

淹沒傳回位址

覆寫鄰接變量的方法利用條件太過苛刻,需要源代碼的結構符合漏洞利用才能實行。直接修改EBP或者函數傳回位址的攻擊則更為通用。

0x00 源碼

由于鍵盤能夠直接輸入的字元ASCII範圍有限,無法表達

0x11

0x12

等值,是以對代碼稍作修改,通過讀取文本檔案輸入。

#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_password

0x00401102

處被調用,在

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

0x62

0x63

0x64

authenticated(修改前)

0x0012FB1C

0x00

0x00

0x00

0x01

authenticated(修改後)

0x0012FB1C

0x61

0x62

0x63

0x64

前棧幀EBP(修改前)

0x0012FB20

0x00

0x12

0xFF

0x80

前棧幀EBP(修改後)

0x0012FB20

0x61

0x62

0x63

0x64

傳回位址(被覆寫前)

0x0012FB24

0x00

0x40

0x11

0x07

傳回位址(被覆寫後)

0x0012FB24

0x00

0x40

0x11

0x22