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

前言

我在之前的课程中讨论过W32Dasm这款软件中的漏洞分析与利用的方法,由于使用该软件的人群毕竟是小众群体,因此该漏洞的危害相对来说还是比较小的。但是如果漏洞出现在Windows系统中,那么情况就会很不一样了。毕竟Windows统治了全球九成以上的计算机操作系统,因此如果该系统中出现了漏洞,而这个漏洞又被别有用心者所利用,那么就必然会出现数以亿计的受害者。

MS06-040漏洞基本信息

这次我们讨论的漏洞是在2006年8月8日,由微软公布的MS06-040这个漏洞(https://technet.microsoft.com/en-us/library/security/ms06-040.aspx)。在微软的安全技术中心,可以看到关于这个漏洞的报告,它被定义为“严重”的级别:

图1

其实,微软的安全技术中心所公布的漏洞是所有安全工作者和黑客们最为感兴趣的地方。每当微软发布一个补丁,修复了一个漏洞,总会有许多的攻击者通宵达旦地去研究这些补丁都修补了哪些漏洞,并写出漏洞利用程序。因为在补丁刚刚发布的一段时间内并非所有用户都能及时修复,所以这种新公布的漏洞会很有利用价值。对于这些攻击者而言,虽说他们的行为很不光彩,但是他们这种去挖掘漏洞的毅力,还是值得钦佩的。毕竟微软的漏洞公告,仅仅给出了一个大概的方向,而漏洞的具体细节,比如漏洞出现在哪个函数,该如何利用这个漏洞等等细节,是根本就没有透露的。这就和之前我们讨论的W32Dasm的漏洞报告很不一样,后者已经具体到了一个特定的函数,因此我们只要找到究竟是哪个位置的函数出现了问题即可。而通过微软的漏洞报告,我们一般至多只能够知道漏洞出现在哪一个动态链接库中,具体到究竟是该动态链接库中的哪一个函数,就需要我们自己去寻找了。由于动态链接库中可能包含有众多的导出函数,所以很多时候定位漏洞出现的位置就如同大海捞针,是需要极大的耐心甚至还包含有运气成分的。

依据微软的漏洞报告,我们知道出现问题的是Netapi32.dll这个动态链接库:

图2

在该动态链接库中有一个名为NetpwPathCanonicalize()的导出函数:

图3

这个函数存在着缓冲区溢出的缺陷,也正是我们重点关注的对象。

静态分析漏洞出现的原因

既然已经知道了出现漏洞的函数,那么下一步可以使用IDA Pro来分析一下漏洞出现的原因。事实上,在NetpwPathCanonicalize()函数中有一个名为sub_7517FC68的函数,出现溢出隐患的正是在这个函数内部:

图4

下面就是对这个函数反汇编代码的分析:

.text:7517FC68 ; Attributes: bp-based frame
.text:7517FC68 ; int __stdcall sub_7517FC68(wchar_t *Source, wchar_t *Str, int, int, int)
.text:7517FC68 sub_7517FC68    proc near               ; CODE XREF: NetpwPathCanonicalize+74p
.text:7517FC68 var_416         = word ptr -416h
.text:7517FC68 Dest            = word ptr -414h
.text:7517FC68 Source          = dword ptr  8
.text:7517FC68 Str             = dword ptr  0Ch
.text:7517FC68 arg_8           = dword ptr  10h
.text:7517FC68 arg_C           = dword ptr  14h
.text:7517FC68 arg_10          = dword ptr  18h
; 本函数中一共有五个参数和两个局部变量
.text:7517FC68                 push    ebp
.text:7517FC69                 mov     ebp, esp
.text:7517FC6B                 sub     esp, 414h
; 开辟0x414字节大小的空间,用于存放生成的字符串
.text:7517FC71                 push    ebx
.text:7517FC72                 push    esi
.text:7517FC73                 xor     esi, esi
.text:7517FC75                 push    edi
.text:7517FC76                 cmp     [ebp+Source], esi
; 验证参数Source是否为0
.text:7517FC79                 mov     edi, ds:__imp_wcslen
.text:7517FC7F                 mov     ebx, 411h
; 在本函数中,ebx的值不变,恒为0x411,用于检查是否越界
.text:7517FC84                 jz      short loc_7517FCED
; 依据上面的Source与0的比较结果来判定是否跳转
.text:7517FC86                 push    [ebp+Source]       ; Str
.text:7517FC89                 call    edi ; __imp_wcslen
; 计算参数Source的长度,注意这里是Unicode形式的长度
.text:7517FC8B                 mov     esi, eax
; esi保存的是参数Source的Unicode形式的长度
.text:7517FC8D                 pop     ecx
.text:7517FC8E                 test    esi, esi
.text:7517FC90                 jz      short loc_7517FCF4
; 验证参数Source的长度是否为0
.text:7517FC92                 cmp     esi, ebx
; 验证参数Source的长度是否大于0x411
.text:7517FC94                 ja      loc_7517FD3E
; 如果大于,则退出程序
.text:7517FC9A                 push    [ebp+Source]       ; Source
.text:7517FC9D                 lea     eax, [ebp+Dest]
.text:7517FCA3                 push    eax                ; Dest
.text:7517FCA4                 call    ds:__imp_wcscpy
; 将参数Source字符串拷贝到新分配的空间中。需要注意的是,这里开辟的是0x414字节的空间,
; 但是上述代码检测的却是宽字符,宽字符的0x411相当于0x822字节的空间。看似这是可以
; 利用的漏洞,但是在本函数调用之前,程序已经使用了NetpwPathType()函数用于检测参
; 数Source的长度,使其不能超过0x206个字节,因此这里是不能被当做漏洞利用的。
.text:7517FCAA                 mov     ax, [ebp+esi*2+var_416]
; 取出字符,检查是否为斜杠
.text:7517FCB2                 pop     ecx
.text:7517FCB3                 cmp     ax, 5Ch                  ; 0x5C表示的是“\”
.text:7517FCB7                 pop     ecx
.text:7517FCB8                 jz      short loc_7517FCD5
.text:7517FCBA                 cmp     ax, 2Fh                  ; 0x2F表示的是“/”
.text:7517FCBE                 jz      short loc_7517FCD5
.text:7517FCC0                 lea     eax, [ebp+Dest]
.text:7517FCC6                 push    offset asc_751717B8      ; "\\"
.text:7517FCCB                 push    eax                      ; Dest
.text:7517FCCC                 call    ds:__imp_wcscat
; 将斜杠与字符串进行连接
.text:7517FCD2                 pop     ecx
.text:7517FCD3                 inc     esi
; 由于加入了斜杠,所以字符串的长度增加1
.text:7517FCD4                 pop     ecx
.text:7517FCD5
.text:7517FCD5 loc_7517FCD5:                           ; CODE XREF: sub_7517FC68+50j
.text:7517FCD5                                         ; sub_7517FC68+56j
.text:7517FCD5                 mov     eax, [ebp+Str]
.text:7517FCD8                 mov     ax, [eax]
.text:7517FCDB                 cmp     ax, 5Ch         ; 0x5C表示的是“\”
.text:7517FCDF                 jz      short loc_7517FCE7
.text:7517FCE1                 cmp     ax, 2Fh         ; 0x2F表示的是“/”
.text:7517FCE5                 jnz     short loc_7517FCF4
.text:7517FCE7
.text:7517FCE7 loc_7517FCE7:                           ; CODE XREF: sub_7517FC68+77j
.text:7517FCE7                 add     [ebp+Str], 2
.text:7517FCEB                 jmp     short loc_7517FCF4
.text:7517FCED ; ------------------------------------------------------------
.text:7517FCED loc_7517FCED:                           ; CODE XREF: sub_7517FC68+1Cj
.text:7517FCED                 mov     [ebp+Dest], si
.text:7517FCF4
.text:7517FCF4 loc_7517FCF4:                           ; CODE XREF: sub_7517FC68+28j
.text:7517FCF4                                         ; sub_7517FC68+7Dj ...
.text:7517FCF4                 push    [ebp+Str]       ; Str
.text:7517FCF7                 call    edi ; __imp_wcslen
; 计算参数Str的长度,注意这里是Unicode形式的长度
.text:7517FCF9                 add     eax, esi
; 这里计算的是加上了斜杠的参数Source与Str长度的总和,该长度保存在eax中
.text:7517FCFB                 pop     ecx
.text:7517FCFC                 cmp     eax, ebx
.text:7517FCFE                 ja      short loc_7517FD3E
; 对连接后的字符串长度进行检查,经过之前的分析可以知道,仅仅依靠参数Source是不能够
; 实现漏洞的利用的,因为它的长度被限定在了0x206个字节,但是程序并没有对参数Str的
; 长度进行足够的限定,利用这一点就可以实现漏洞的利用。
.text:7517FD00                 push    [ebp+Str]          ; Source
.text:7517FD03                 lea     eax, [ebp+Dest]
.text:7517FD09                 push    eax                ; Dest
.text:7517FD0A                 call    ds:__imp_wcscat
; 字符串连接,将参数Str连接到缓冲区中,也正是这个函数,导致了缓冲区的溢出
.text:7517FD10                 pop     ecx
.text:7517FD11                 lea     eax, [ebp+Dest]
.text:7517FD17                 pop     ecx
.text:7517FD18                 push    eax
.text:7517FD19                 call    sub_7518AE95
.text:7517FD1E                 lea     eax, [ebp+Dest]
.text:7517FD24                 push    eax             ; Str
.text:7517FD25                 call    sub_7518AEB3
.text:7517FD2A                 test    eax, eax
.text:7517FD2C                 jnz     short loc_7517FD43
.text:7517FD2E                 lea     eax, [ebp+Dest]
.text:7517FD34                 push    eax
.text:7517FD35                 call    sub_7518AFE2
.text:7517FD3A                 test    eax, eax
.text:7517FD3C                 jnz     short loc_7517FD43
.text:7517FD3E
.text:7517FD3E loc_7517FD3E:                           ; CODE XREF: sub_7517FC68+2Cj
.text:7517FD3E                                         ; sub_7517FC68+96j
.text:7517FD3E                 push    7Bh
.text:7517FD40                 pop     eax
.text:7517FD41                 jmp     short loc_7517FD7A
.text:7517FD43 ; ------------------------------------------------------------
.text:7517FD43 loc_7517FD43:                           ; CODE XREF: sub_7517FC68+C4j
.text:7517FD43                                         ; sub_7517FC68+D4j
.text:7517FD43                 lea     eax, [ebp+Dest]
.text:7517FD49                 push    eax             ; Str
.text:7517FD4A                 call    edi ; __imp_wcslen
.text:7517FD4C                 lea     eax, [eax+eax+2]
.text:7517FD50                 pop     ecx
.text:7517FD51                 cmp     eax, [ebp+arg_C]
.text:7517FD54                 jbe     short loc_7517FD66
.text:7517FD56                 mov     ecx, [ebp+arg_10]
.text:7517FD59                 test    ecx, ecx
.text:7517FD5B                 jz      short loc_7517FD5F
.text:7517FD5D                 mov     [ecx], eax
.text:7517FD5F
.text:7517FD5F loc_7517FD5F:                           ; CODE XREF: sub_7517FC68+F3j
.text:7517FD5F                 mov     eax, 84Bh
.text:7517FD64                 jmp     short loc_7517FD7A
.text:7517FD66 ; ------------------------------------------------------------
.text:7517FD66 loc_7517FD66:                           ; CODE XREF: sub_7517FC68+ECj
.text:7517FD66                 lea     eax, [ebp+Dest]
.text:7517FD6C                 push    eax             ; Source
.text:7517FD6D                 push    [ebp+arg_8]     ; Dest
.text:7517FD70                 call    ds:__imp_wcscpy
.text:7517FD76                 pop     ecx
.text:7517FD77                 xor     eax, eax
.text:7517FD79                 pop     ecx
.text:7517FD7A
.text:7517FD7A loc_7517FD7A:                           ; CODE XREF: sub_7517FC68+D9j
.text:7517FD7A                                         ; sub_7517FC68+FCj
.text:7517FD7A                 pop     edi
.text:7517FD7B                 pop     esi
.text:7517FD7C                 pop     ebx
.text:7517FD7D                 leave
.text:7517FD7E                 retn    14h
.text:7517FD7E sub_7517FC68    endp
.text:7517FD7E

如上所述,程序对字符串的长度限定是unicode的0x411个字符,也就是0x822个字节,而程序仅仅分配了0x414个字节的空间,于是就出现了缓冲区溢出的隐患。

对于函数sub_7517FC68而言,它有两个参数是需要我们特别留意的,一个是Source,另一个是Str。由于前者已经被限定了长度,最多只能0x206个字节,所以我们只能拿后者做文章。那么这两个参数在NetpwPathCanonicalize()函数中是什么形态呢?经过分析可知,Str这个参数正是NetpwPathCanonicalize()函数的第一个参数,而Source则是第四个参数:

图5

也就是说,我们可以构造一个程序,让它加载Netapi32.dll这个动态链接库,并调用其中的NetpwPathCanonicalize()函数,精心构造这个函数的第一和第四个参数的内容,那么就可以实现漏洞的利用了。

函数NetpwPathCanonicalize()说明

其实经过上述分析,我们就可以知道NetpwPathCanonicalize()函数的大概功能了。其实关于这个函数,微软是没有给我们任何介绍的,现在对于这个函数功能的讲解,都是前辈们通过类似于我们上述的逆向分析而总结出来的。由此可见,逆向工程确实是计算机领域中最富挑战性的工作之一。

函数NetpwPathCanonicalize()用于格式化网络路径字符串,其定义如下:

void NetpwPathCanonicalize(
_in_       dword Str,                  // +8h
_out_      dword lpWideCharStr,        // +0Ch
_in_       dword arg_8,                // +10h
_in_       dword Source,               // +14h
_in_out_  dword arg_10,                // +18h
_in_       dword arg_14                // +1Ch
 );

参数说明:

Str:指向Unicode字串的指针,用于生成路径字串的第二部分。

lpWideCharStr:指向buffer的指针,用于接收格式化后的字符串。

arg_8:表明lpWideCharStr的大小

Source:指向Unicode字串的指针,用于生成路径字串的第一部分。

arg_10:指向long的指针。

arg_14:标志位,一般为0。

函数的功能是把Source指的字符串,加上‘\’,再加上Str指的字符串,放在lpWideCharStr的buffer里。即

Source+ ‘\’ + Str => lpWideCharStr[arg_8]

小结

经过上述一系列的分析,我们已经弄清楚了漏洞出现的原因以及利用方法。那么对于这个漏洞的实际利用,我会在下一次的讲解中,结合动态调试,编程实现。

时间: 2024-11-05 07:24:37

缓冲区溢出分析第07课:MS06-040漏洞研究——静态分析的相关文章

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

前言 经过上次的分析,我们已经知道了MS06-040漏洞的本质,那么这次我们就通过编程实现漏洞的利用. 编写漏洞利用程序的框架 这里我使用的是VC++6.0进行编写,需要将包含有漏洞的netapi32.dll文件与工程文件放置在同一个目录下.程序如下: #include <windows.h> typedef void (*MYPROC)(LPTSTR, ...); int main() { char Str[0x320]; char lpWideCharStr[0x440]; int arg

缓冲区溢出分析第09课:MS06-040漏洞研究——深入挖掘

前言 经过前两次的分析,我们已经对Netapi32.dll文件中所包含的漏洞成功地实现了利用.在系统未打补丁之前,这确实是一个非常严重的漏洞,那么打了补丁之后,这个动态链接库是不是就安全了呢?答案是否定的.即便是打了补丁,虽说我们之前所分析的漏洞已被补上,但是这个程序中依旧存在着其它的问题. 对漏洞函数进行静态分析 我们之前所研究的Netapi32.dll的大小为309,008 字节,补丁后的文件大小为309,760 字节.我们用IDA Pro载入打过补丁后的DLL文件,找到之前出现问题的函数位

缓冲区溢出分析第11课:整数溢出的原理

<缓冲区溢出分析>这一系列的内容是我为"i春秋"(www.ichunqiu.com)所录制的同名视频课程的讲稿汇总.每次我都是在写完课程的文档后,再依据文档内容进行课程的讲解.而本系列的内容也是从零开始,来给大家由浅入深地进行缓冲区溢出漏洞的讲解.整个课程是理论与实践相结合,每讲完几个基础理论后,都会配以实际的软件中的漏洞进行分析,以帮助大家更好地理解漏洞的原理.有兴趣的朋友可以结合本文与配套视频进行学习. 前言 我们之前所研究的漏洞,都是非常经典的栈溢出漏洞,也是最为常见

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

<缓冲区溢出分析>这一系列的内容是我为"i春秋"(www.ichunqiu.com)所录制的同名视频课程的讲稿汇总.每次我都是在写完课程的文档后,再依据文档内容进行课程的讲解.而本系列的内容也是从零开始,来给大家由浅入深地进行缓冲区溢出漏洞的讲解.整个课程是理论与实践相结合,每讲完几个基础理论后,都会配以实际的软件中的漏洞进行分析,以帮助大家更好地理解漏洞的原理.有兴趣的朋友可以结合本文与配套视频进行学习. 前言 Winamp是一款非常经典的音乐播放软件,它于上世纪九十年代

缓冲区溢出分析第01课:缓冲区溢出分析导论

前言 <缓冲区溢出分析>这一系列的内容是我为"i春秋"所录制的同名视频课程的讲稿汇总.每次我都是在写完课程的文档后,再依据文档内容进行课程的讲解.而本系列的内容也是从零开始,来给大家由浅入深地进行缓冲区溢出漏洞的讲解.整个课程是理论与实践相结合,每讲完几个基础理论后,都会配以实际的软件中的漏洞进行分析,以帮助大家更好地理解漏洞的原理. 课程导论 漏洞指的是在硬件.软件.协议的具体实现或系统安全策略上存在的缺陷,通常是由程序的编写者在编写时的疏忽造成的.漏洞的存在使攻击者能够

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

漏洞报告分析 学习过破解的朋友一定听说过W32Dasm这款逆向分析工具.它是一个静态反汇编工具,在IDA Pro流行之前,是破解界人士必然要学会使用的工具之一,它也被比作破解界的"屠龙刀". 但是即便是这么一款破解界的"神器",竟然也是存在着缓冲区溢出的漏洞的.可见,它在破解无数程序的同时,其自身也存在着被"黑"的风险.那么我们可以首先分析一下漏洞报告: ##############################################

缓冲区溢出分析第03课:缓冲区溢出的利用

前言 上次我们已经讨论了缓冲区溢出的原理,而这次我们需要利用这个原理来构造条件,从而利用这个漏洞. 其实缓冲区溢出漏洞的利用主要是需要解决以下三个问题: 1.精确定位返回地址的位置 2.寻找一个合适的地址,用于覆盖原始返回地址 3.编写Shellcode到相应的缓冲区中 这次我们就结合实验程序,来解决上述三个问题,实现漏洞的利用. 精确定位返回地址的位置 缓冲区溢出利用的第一步,就是需要我们精确定位返回地址的位置.由于我们这次的程序比较简单,所以通过错误提示对话框,我们就能够判定返回地址的位置了

缓冲区溢出分析第02课: 缓冲区溢出的原理

创建含有缓冲区溢出隐患的程序 这里我们为了达到实验的要求,先来编写一个最简单的存在缓冲区溢出隐患的程序.这个程序我使用VC++6.0进行编写,并在Windows XP下执行.(这里请大家注意的是,如果你使用的是新版本的VC,由于微软加入了GS机制来防止缓冲区溢出情况的出现,那么本实验就无法实现.) 首先新建一个Win32控制台应用程序,然后输入以下C语言代码: #include "stdio.h" #include "string.h" char name[] =

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

前言 ShellCode究竟是什么呢,其实它就是一些编译好的机器码,将这些机器码作为数据输入,然后通过我们之前所讲的方式来执行ShellCode,这就是缓冲区溢出利用的基本原理.那么下面我们就来编写ShellCode.为了简单起见,这里我只想让程序显示一个对话框: 图1 获取相关函数的地址 那么我们下面的工作就是让存在着缓冲区溢出漏洞的程序显示这么一个对话框.由于我在这里想要调用MessageBox()这个API函数,所以说首先需要获取该函数的地址,这可以通过编写一个小程序来获取: #inclu