最近闲来无事,就对之前一个非常有名的漏洞进行了分析,该漏洞就是大名鼎鼎的MS08-067。说实话,初次分析这个漏洞,确实让我费了点功夫。
我使用的netapi32.dll的版本号是5.1.2600.2976,存在问题的函数是NetpwPathCanonicalize,最终存在问题的函数是ConPathMacros,漏洞触发函数调用关系如图1所示。
图1
MS08-067的问题出在一个格式化路径的函数上,函数名为ConvertPathMacros,函数的任务就是处理掉路径字符串中的“.”和“..”,例如传入路径为“\A\B\.\C\..\D\..\..\E”,经过它的处理后会变成“\A\E”,就是把路径做简化操作。
漏洞函数分析
ConvertPathMacros函数对路径字符串的识别过程主要依靠ESI指针。当识别出存在“\..\”字符串时,ESI将指向该字符串,并且EDI指向“\..\”字符串前的“\”处,EAX指向“\..\”中的后一个“\”,之后启动字符串复制过程。该复制过程说来简单,例如在\bbb\..\aaa中,就是将aaa开始(包括aaa)的数据覆盖掉bbb开始(包括bbb)的数据,达到bbb和\..\相抵消的效果。复制操作的反汇编代码如下:
push push call eax ;wchar_t* ;wchar_t* edi ds:__imp_wcscpy
程序执行到上面的复制代码后,将开始执行复制操作,EAX指向复制操作的源串,EDI指向复制操作的目的串。
第一次复制过程
复制前,EAX指向“\..\AAAA„„”,而EDI指向“\BBBBB\..\..\AAAA„„”,__imp_wcscpy函数执行时将用EAX指向的字符串\..\AAA„„覆盖掉EDI指向的字符串\BBBBB\..\..\AAA„„所在空间。如图2和图3所示。
第二次复制过程中,eax指向“\AAAA„„”,而edi指向原有字符串之前的一个“\”,如图3所示位置,__imp_wcscpy函数执行时将用EAX指向的字符串\AAA„„覆盖掉EDI指向的字符串所在空间。由于edi始终指向“\..\”字符串前的第一个“\”,那么可能这时的“\”已不再是路径中的一部分,可能是其他堆栈数据,这将导致覆盖其他堆栈重要数据的结果,如图4所示。
内存中的关键“\”为什么在字符串“\..\”的前方会存在“\”呢?就是因为“\”的存在,才导致了覆盖关键数据,最终导致跳转到恶意代码中。关键在于,在执行第二次__imp_wcscpy函数时,在堆栈中字符串“\..\”之前的适当位置存在一个“\”符号,ASCII码为5C00。之所以在该位置存在“\”字符,是因为在调用ConPathMacros函数之前,调用了函数RtlInitUnicodeString函数。RtlInitUnicodeString函数的功能为初始化一个Unicode字符串。
VOIDRtlInitUnicodeString(PUNICODE_STRINGDestinationString//指向一个Unicode字符串的缓冲,如果SourceString未被指定,初始化的长度为0。PCWSTRSourceString//可选的指向一个NULL结尾的Unicode字符串的指针。);
RtlInitUnicodeString函数的传入参数如下:
0012F7E8 0012F7EC 0012F7FC 0012F866 (DestinationString) ( SourceString , 其值为 UNICODE "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA“) RtlInitUnicodeString函数的反汇编代码如下: 7C9212D6 7C9212D7 7C9212DB 7C9212DF 置0 push mov mov mov edi edi,dwordptr[esp+C]//SourceString edx,dwordptr[esp+8]//DestinationString dwordptr[edx],0//DestinationString的开头4个字节被7C9212E5movdwordptr[edx+4],edi//DestinationString开头后的4个字节被赋值为SourceString的地址 7C9212E8 7C9212EA 7C9212EC 7C9212EF 7C9212F1 or edi,edi//判断edi是否为0,为0说明SourceString为空 short7C92130E je or ecx,FFFFFFFF//将ecx置为FFFFFFFF xor eax,eax //将eax置为0 repnescaswordptres:[edi]//扫描SourceString中的字符串, ecx为长度的反。计算的是45个A..A的长度 7C9212F4 7C9212F6 7C9212F8 7C9212FE not shl cmp jbe ecx //将ecx取反,该值是A..A的宽字节长度 //该值乘2,将是字节长度 ecx,1 ecx,0FFFE //0FFFE可能是字符串长度的上限
short7C921305//jbe是不高于跳转,如果ecx小于等于上限,那么直接去7C921305执行,如果7C9213007C921305movmovecx,0FFFE//ecx大于上限,那么ecx将被置为0FFFEwordptr[edx+2],cx//将把长度和长度减2,分别放入edx+2和edx指针指向的内存中。
7C921309dececx 7C92130A 7C92130B 7C92130E 7C92130F dec mov pop retn ecx wordptr[edx],cx edi 8
第一次复制完成后,图5下面的蓝色区域(除\..以外)覆盖了绿色区域,图6为复制
后的效果图。
对复制完后的堆栈进行描述,图7、图8、图9和图10是第2次复制完后的效果图。
结语
之前在研究MS08-067漏洞时碰到了许多问题,其中一个最大的问题就是忽略了RtlInitUnicodeString函数而导致的溢出利用失败。后来经过逆向跟踪发现,该函数在溢出成功利用中起了很大的作用。就是因为该函数的存在,才导致堆栈中会多出来一个“\”字符,才能让畸形数据覆盖返回地址。