官术网_书友最值得收藏!

  • 0day安全
  • 王清主編
  • 3045字
  • 2019-01-01 12:26:13

2.4 代碼植入

2.4.1 代碼植入的原理

本章第2節和第3節已經依次展示了淹沒相鄰變量,改變程序流程和淹沒返回地址,改變程序流程的方法。本節將給您介紹一個更有意思的實驗——通過棧溢出讓進程執行輸入數據中植入的代碼。

在上節實驗中,我們讓函數返回到main函數的驗證通過分支的指令。試想一下,如果我們在buffer里包含我們自己想要執行的代碼,然后通過返回地址讓程序跳轉到系統棧里執行,我們豈不是可以讓進程去執行本來沒有的代碼,直接去做其他事情了!

圖2.4.1 利用棧溢出植入可執行代碼的攻擊示意圖

如圖2.4.1所示,在本節實驗中,我們準備向password.txt文件里植入二進制的機器碼,并用這段機器碼來調用Windows的一個API函數MessageBoxA,最終在桌面上彈出一個消息框并顯示“failwest”字樣。

2.4.2 向進程中植入代碼

為了完成在棧區植入代碼并執行,我們在上節的密碼驗證程序的基礎上稍加修改,使用如下的實驗代碼。

   #include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
   int authenticated;
   char buffer[44];
   authenticated=strcmp(password,PASSWORD);
   strcpy(buffer,password);//over flowed here!
   return authenticated;
}
main()
{
   int valid_flag=0;
   char password[1024];
   FILE * fp;
   LoadLibrary("user32.dll");//prepare for messagebox
   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);
}

這段代碼在2.3節溢出代碼的基礎上修改了3處。

(1)增加了頭文件windows.h,以便程序能夠順利調用LoadLibrary函數去裝載user32.dll。

(2)verify_password函數的局部變量buffer由8字節增加到44字節,這樣做是為了有足夠的空間來“承載”我們植入的代碼。

(3)main函數中增加了LoadLibrary("user32.dll")用于初始化裝載user32.dll,以便在植入代碼中調用MessageBox。

實驗環境如表2-4-1所示。

表2-4-1 實驗環境

說明:即便完全采用所推薦的實驗環境,函數返回地址、MessageBoxA函數的入口地址等也需要重新確定,因為這些地址可能依賴于操作系統的補丁版本等。這些地址的確定方法在實驗指導中均給出了詳細的說明。

用VC6.0將上述代碼編譯(默認編譯選項,編譯成debug版本),得到有棧溢出的可執行文件。在同目錄下創建password.txt文件用于程序調試。

我們準備在password.txt文件中植入二進制的機器碼,在password.txt攻擊成功時,密碼驗證程序應該執行植入的代碼,并在桌面上彈出一個消息框顯示“failwest”字樣。

讓我們在動手之前回顧一下我們需要完成的幾項工作。

(1)分析并調試漏洞程序,獲得淹沒返回地址的偏移。

(2)獲得buffer的起始地址,并將其寫入password.txt的相應偏移處,用來沖刷返回地址。

(3)向password.txt中寫入可執行的機器代碼,用來調用API彈出一個消息框。

本節驗證程序里verify_password中的緩沖區為44個字節,按照前邊實驗中對棧結構的分析,我們不難得出棧幀中的狀態。

如果在password.txt中寫入恰好44個字符,那么第45個隱藏的截斷符null將沖掉authenticated低字節中的1,從而突破密碼驗證的限制。我們不妨就用44個字節作為輸入來進行動態調試。

出于字節對齊、容易辨認的目的,我們把“4321”作為一個輸入單元。

buffer[44]共需要11個這樣的單元。

第12個輸入單元將authenticated覆蓋;第13個輸入單元將前棧幀EBP值覆蓋;第14個輸入單元將返回地址覆蓋。

分析過后,我們需要進行調試驗證分析的正確性。首先,在password.txt中寫入11組“4321”,共44個字符,如圖2.4.2所示

圖2.4.2 制作溢出文件

如我們所料,authenticated被沖刷后,程序將進入驗證通過的分支,如圖2.2.3所示。

用OllyDbg加載這個生成的PE文件進行動態調試,字符串復制函數過后的棧狀態如圖2.4.4所示。時的棧區內存如表2-4-2所示。

圖2.4.3 驗證通過

圖2.4.4 調試棧的布局

表2-4-2 棧幀數據

動態調試的結果證明了前邊分析的正確性。從這次調試中,我們可以得到以下信息。

(1)buffer數組的起始地址為0x0012FAF0。

(2)password.txt文件中第53~56個字符的ASCII碼值將寫入棧幀中的返回地址,成為函數返回后執行的指令地址。

也就是說,將buffer的起始地址0x0012FAF0寫入password.txt文件中的第53~56個字節,在verify_password函數返回時會跳到我們輸入的字串開始取指執行。

我們下面還需要給password.txt中植入機器代碼。

讓程序彈出一個消息框只需要調用Windows的API函數MessageBox。MSDN對這個函數的解釋如下。

int MessageBox(
   HWND 錯誤!超級鏈接引用無效。,  // handle to owner window
   LPCTSTR 錯誤!超級鏈接引用無效。, // text in message box
   LPCTSTR 錯誤!超級鏈接引用無效。, // message box title
   UINT 錯誤!超級鏈接引用無效。 // message box style
);

·hWnd [in] 消息框所屬窗口的句柄,如果為NULL,消息框則不屬于任何窗口。

·lpTex[in] 字符串指針,所指字符串會在消息框中顯示。

·lpCaption [in] 字符串指針,所指字符串將成為消息框的標題。

·uType [in] 消息框的風格(單按鈕、多按鈕等),NULL代表默認風格。

我們將給出調用這個API的匯編代碼,然后翻譯成機器代碼,用十六進制編輯工具填入password.txt文件。

題外話:熟悉MFC的程序員一定知道,其實系統中并不存在真正的MessagBox函數,對MessageBox這類API的調用最終都將由系統按照參數中字符串的類型選擇“A”類函數(ASCII)或者“W”類函數(UNICODE)調用。因此,我們在匯編語言中調用的函數應該是MessageBoxA。多說一句,其實MessageBoxA的實現只是在設置了幾個不常用參數后直接調用MessageBoxExA。探究API的細節超出了本書所討論的范圍,有興趣的讀者可以參閱其他書籍。

用匯編語言調用MessageboxA需要3個步驟。

(1)裝載動態鏈接庫user32.dll。MessageBoxA是動態鏈接庫user32.dll的導出函數。雖然大多數有圖形化操作界面的程序都已經裝載了這個庫,但是我們用來實驗的consol版并沒有默認加載它。

(2)在匯編語言中調用這個函數需要獲得這個函數的入口地址。

(3)在調用前需要向棧中按從右向左的順序壓入MessageBoxA的4個參數。

為了讓植入的機器代碼更加簡潔明了,我們在實驗準備中構造漏洞程序的時候已經人工加載了user32.dll這個庫,所以第一步操作不用在匯編語言中考慮。

MessageBoxA的入口參數可以通過user32.dll在系統中加載的基址和MessageBoxA在庫中的偏移相加得到。具體的我們可以使用VC6.0自帶的小工具“Dependency Walker”獲得這些信息。您可以在VC6.0安裝目錄下的Tools下找到它,如圖2.4.5所示。

圖2.4.5 使用Depends

運行Depends后,隨便拖拽一個有圖形界面的PE文件進去,就可以看到它所使用的庫文件了。在左欄中找到并選中user32.dll后,右欄中會列出這個庫文件的所有導出函數及偏移地址;下欄中則列出了PE文件用到的所有的庫的基地址。

如圖2.4.6所示,user32.dll的基地址為0x77D40000,MessageBoxA的偏移地址為0x000404EA?;刂芳由掀频刂肪偷玫搅薓essageBoxA在內存中的入口地址0x77D804EA。

圖2.4.6 計算相關API的虛擬內存地址

提示:user32.dll的基地址和其中導出函數的偏移地址與操作系統版本號、補丁版本號等諸多因素相關,故您用于實驗的計算機上的函數入口地址很可能與這里不一致。請您一定注意要在當前實驗的計算機上重新計算函數入口地址,否則后面的函數調用會出錯。能夠適應于各種操作系統版本的通用的代碼植入方法將在第5章進行詳細介紹。

有了這個入口地址,就可以編寫進行函數調用的匯編代碼了。這里我們先把字符串“failwest”壓入棧區,消息框的文本和標題都顯示為“failwest”,只要重復壓入指向這個字符串的指針即可;第1個和第4個參數這里都將設置為NULL。寫出的匯編代碼和指令所對應的機器代碼如表2-4-3所示。

表2-4-3 機器代碼

題外話:從匯編指令到機器碼的轉換可以有很多種方法。調試匯編指令,從匯編指令中提取出二進制機器代碼的方法將在第5章集中討論。由于這里僅僅用了11條指令和對應的26個字節的機器代碼,如果您一定要現在就弄明白指令到機器碼是如何對應的話,直接查閱Intel的指令集手工翻譯也是可以的。

將上述匯編指令對應的機器代碼按照上一節介紹的方法以十六進制形式逐字寫入password.txt,第53~56字節填入buffer的起址0x0012FAF0,其余的字節用0x90(nop指令)填充,如圖2.4.7所示。

圖2.4.7 將機器代碼寫入文件

換回文本模式可以看到這些機器代碼所對應的字符,如圖2.4.8所示。

圖2.4.8 ASCII編碼下的機器代碼

這樣構造了password.txt之后再運行驗證程序,程序執行的流程將如圖2.4.9所示。

圖2.4.9 棧溢出利用示意圖

程序運行情況如圖2.4.10所示。

圖2.4.10 輸入文件中的代碼植入成功

成功地彈出了我們植入的代碼。

但是在單擊“OK”按鈕之后,程序會崩潰,如圖2.4.11所示。

圖2.4.11 被破壞的棧在程序退出時引起程序崩潰

這是因為MessageBoxA調用的代碼執行完成后,我們沒有寫用于安全退出的代碼的緣故。

您會在后面的章節中見到更深入的代碼植入討論,包括編寫通用的植入代碼,在植入代碼中安全地退出,甚至在植入代碼結束后修復堆棧和寄存器,讓程序重新回到正常的執行流程。

主站蜘蛛池模板: 循化| 天祝| 宜川县| 杭锦后旗| 松潘县| 宜良县| 奈曼旗| 太和县| 宁陵县| 云浮市| 黄陵县| 马龙县| 常宁市| 正阳县| 色达县| 凯里市| 建始县| 淮北市| 秦皇岛市| 辛集市| 游戏| 昌吉市| 潞城市| 库车县| 年辖:市辖区| 宝坻区| 得荣县| 甘南县| 基隆市| 松潘县| 乌拉特前旗| 柏乡县| 平舆县| 西和县| 绍兴市| 同仁县| 扎鲁特旗| 枣强县| 泉州市| 博兴县| 柳州市|