MS08-067的稳定利用方法

MS08-067已经出来三个月了,大部分服务器已经打上了补丁,看来利用价值不大。但是做为一个学习的材料还是挺好的。

这个漏洞的原理在很多文章中都解释的非常详细了,但是关于漏洞利用的文章却比较少(可能是我没找到),我自己摸索了一整子,参考了不少高手们写的exploit,终于摸索出一个还算稳定的利用方法,将摸索的过程整理成本文。


如果您发现有什么不足之处,或者有更好的方法,请联系我:

LittleWallE#yahoo.cn.(#替换为@)

或者在博客上留言:http://blog.csdn.net/littlewalle

交流才能进步,很高兴能成为你的朋友。

 

1.   概述


MS08-067的问题出在一个格式化路径的函数上,暂命名为ConvertPathMacros,它的工作就是去掉路径中的./../ 例如传入路径为:”/A/B/./C/../D/../../E”,则经过它的处理后会变成”/A/E”。如果给出一个恶意路径,例如”/A/../../../E”,则它会越过目录A去寻找上层目录,从而造成错误的发生。


2.   目标

根据分析,知道了稳定利用的关键是反斜杠的位置,所以目标就是能在栈上找一个相对固定的位置写入一个反斜杠。而且为了保证溢出,反斜杠的位置还必须在发生溢出时esp位置之上。

为了达到这个目标,有两个方法:

1.                  看看调用NetpwPathCanonicalize函数时,参数能否影响到栈上数据。有可能很长的参数能够在ConvertPathMacros函数之前在栈上写下数据。

2.                  调用另一个RPC函数,在栈上留下一大堆数据后再调用NetpwPathCanonicalize函数,这样找到反斜杠的概率较大。

 

第一种方法经过测试发现行不通,ConvertPathMacros函数在调用堆栈的最顶端,其他函数没有机会向栈顶写数据。

而第二种方法也需要经过测试才能验证是否可行,以另一个流行的RPC函数NetpwNameCompare函数为例(其实也不流行,是参考了一位高手的exploit,他的邮箱是[email protected]),进行下面的实践。

 

3.   实践

1.      准备工作

在虚拟机上安装了windows2003 sp0 中文版, 本机上操作系统是windows xp sp2 英文版。在本机上写好客户端,客户端功能是先后调用虚拟机上的RPC函数NetpwNameCompareNetpwPathCanonicalize。在服务端装上OLLYDBG, Process Explorer

启动虚拟机,启动完毕后,建立一个快照(snapshot),便于以后问题重现,也避免调试过程中系统崩溃。

 

2.      验证想法

首先得看看NetpwNameCompare函数能否在栈上留下足够的数据供使用。不能的话就换函数。操作如下:

1.       开启Process Explorer,找到提供Computer Browser服务的进程(svchost),记下进程号,在OLLYDBG中附加,如图:
MS08-067的稳定利用方法_第1张图片

2.       在下面几个关键函数的入口,返回地址处加断点。
NetpwNameCompare
NetpwPathCanonicalize
ConvertPathMacros (7C941084)
断点设置完毕后,按F9,让svchost进程继续运行。

3.       使用客户端调用虚拟机上的服务:
NetpwNameCompare(L"LittleWallE",L"123456789",L"123456789",4,0);
NetpwPathCanonicalize(arg_0,(unsigned short *)arg1,(unsigned char *)arg2,
arg3,arg4,(long *)Buff3,1); //
暂时使用正常参数
可以看到svchost进程断在了NetpwNameCompare函数入口,记录下当时esp的值。按F9继续运行,断在NetpwPathCanonicalize函数入口时,同样记录下esp的值,依此类推,有下面的记录:

esp

function

015ff47c

wscpy位置

      

问题函数(7C941084)

015ff8f8

NetpwPathCanonicalize(71BA9511)

015ff908

NetpwNameCompare(71BABE87)

4.       由于溢出时,必须覆盖wscpy位置才能利用成功,而且根据以前的分析,传入的恶意路径的长度要小于0x207,所以我们关心的内存位置为wscpy位置以上的0x207大小的区域(015ff276~ 015ff47c), 只要NetpwNameCompare函数能在这个区域中留下数据,则稳定利用就有希望了。

5.       再次使用客户端同时调用虚拟机上的服务如下:
NetpwNameCompare(L"LittleWallE",L"123456789",L"123456789",4,0);
NetpwPathCanonicalize(arg_0,(unsigned short *)arg1,(unsigned char *)arg2,
arg3,arg4,(long *)Buff3,1); //
暂时使用正常参数
svchost进程断在NetpwNameCompare函数入口时,将esp上的内存全部清空,如图:
MS08-067的稳定利用方法_第2张图片
F9继续运行,当进程断在NetpwNameCompare函数返回地址时,查看区域(015ff276~ 015ff47c)的值。谢天谢地,有值了,而且是函数的参数"123456789",如图:
MS08-067的稳定利用方法_第3张图片
根据这几步验证,证实了NetpwNameCompare函数可以用来向栈上写数据。

3.      分析

1.       由于堆栈里的数据是ASCII码的”123456789”, 而函数的参数是UNICODE”123456789”,所以中间肯定进行了一些转换。为了查看数据的来源,在堆栈上的”123456789”上下内存写入断点。并让程序继续运行,如图:
MS08-067的稳定利用方法_第4张图片

2.       再次使用客户端调用服务,按几次F9后,进程被内存写入断点断下来了。如图:
MS08-067的稳定利用方法_第5张图片

3.       查看调用堆栈,可以看到当前的函数是NTDLL.DLL中的RtlUpcaseUnicodeToOemN. 剩下的分析只能靠IDA了。用IDA打开后界面如下(流程好长):

MS08-067的稳定利用方法_第6张图片

4.       刚开始吓一跳,这么长的流程从哪分析起。后来耐心看了看,并结合OLLYDBG的动态调试,其实一大半的流程是可以砍掉的。我们只关心能向区域(015ff276~ 015ff47c)写入数据的分支,其他分支可以忽略掉。在这个函数里面,刚开始有条命令
cmp     NlsMbOemCodePageTag, 0
jnz     loc_7C92C964
在动态调试中发现,NlsMbOemCodePageTag的值肯定不为0,于是直接用二进制编辑器将将命令更改为:
jmp     loc_7C92C964

5.       更改完命令后,再次用IDA分析,OK, 这次分支少多了:
MS08-067的稳定利用方法_第7张图片

6.       剩下的任务就是逆向工程了,花了点时间将汇编写成C代码,如下。代码的大概意思是将入参L”123456789”经过几个编码表映射后,写入栈上的缓冲区,而这个缓冲区中的数据就是我们所要关心的。只要我们能正确控制参数,使缓冲区上的数据能出现一个反斜杠,那么溢出就成功了。

#define BYTE_HIGH(var) ((var&0xf0)>>4)

#define BYTE_LOW(var) (var&0xf)

#define WORD_HIGH(var) ((var&0xff00)>>8)

#define WORD_LOW(var) (var&0xff)

#define DWORD_HIGH(var) ((var&0xffff0000)>>16)

#define DWORD_LOW(var) (var&0xffff)

/**********************************************************************

*function:将argc_SrcBuffer的字符逐个进行编码表转换后放入栈上的缓冲区中

*author: LittleWallE#yahoo.cn

***********************************************************************/

RtlUpcaseUnicodeToOemN(char * arg0_DstBuffer, //栈上的缓冲区
int arg4_DstBufferLength,
//栈上的缓冲区长度
int * arg8_NoUse,
WCHAR *argc_SrcBuffer, //
入参
int arg10_SrcBufferLength //
入参长度)

{

       char *MatrixA;//mark:dword_7c9b8e94=,一个表格,内容是0000 0100 0200 0300..一直继续下去,可能是某种编码表

       char *MatrixB;//mark:dword_7C9B8A04 另一个编码表。

 

       char *MatrixUnknownA;//mark:word_7c9b8c90; 映射区域,不确定其功能;

       char *MatrixUnknownB;//mark:dword_7C9B8E90;映射区域,不确定其功能;

       char *MatrixUnknownC;//mark:dword_7C9B779C;映射区域,不确定功能;

 

       int var_8_SrcRealLength=arg10_SrcBufferLength/2;

 

       if(var_8_SrcRealLength==0)

              return 0;

 

       while(1)

       {

              if(arg4_DstBufferLength==0)

                     break;

              WCHAR wcSrcChar_1=*argc_SrcBuffer;

              argc_SrcBuffer++;

 

              WCHAR wcSrcChar_2=MatrixA[wcSrcChar_1*2];

 

              wcSrcChar_2_high=WORD_HIGH(wcSrcChar_2);

              wcSrcChar_2_high=MatrixUnknownB[wcSrcChar_2_high*2];

              wcSrcChar_2_low=WORD_LOW(wcSrcChar_2);

              WCHAR wcSrcChar_3;

              if(wcSrcChar_2_high==0)

              {

                     wcSrcChar_3=MatrixB[wcSrcChar_2_low*2];

              }else

              {

                     int index=wcSrcChar_2_high+wcSrcChar_2_low;

                     wcSrcChar_3=MatrixUnknownB[index*2];

              }

              if(wcSrcChar_3>='a'&&wcSrcChar_3<='z')

              {

                     wcSrcChar_3+=0x0ffe0;

              }else{

                     char wcSrcChar_3_high=WORD_HIGH(wcSrcChar_3);

                     wcSrcChar_3_high=MatrixUnknownC[wcSrcChar_3_high*2];

                     char wcSrcChar_3_byte_high=BYTE_HIGH(wcSrcChar_3);

                     wcSrcChar_3_high+=wcSrcChar_3_byte_high;

                     wcSrcChar_3_high=MatrixUnknownC[wcSrcChar_3_high*2];

                     char wcSrcChar_3_byte_low=BYTE_LOW(wcSrcChar_3);

                     wcSrcChar_3_high+=wcSrcChar_3_byte_low;

                     wcSrcChar_3+=wcSrcChar_3_high;

              }//上面这段类似不可逆的加密算法

              WCHAR wcSrcChar_4=MatrixA[wcSrcChar_3*2];

              char wcSrcChar_4_high=WORD_HIGH(wcSrcChar_4);

              char wcSrcChar_4_low=WORD_LOW(wcSrcChar_4);

              if(wcSrcChar_4_high!=0)

              {

                     if(arg4_DstBufferLength<2)

                            break;

                     *arg0_DstBuffer=wcSrcChar_4_high; //这里写入栈上的缓冲区

                     arg0_DstBuffer++;

                     arg4_DstBufferLength--;

              }

              *arg0_DstBuffer=wcSrcChar_4_low;  //这里写入栈上的缓冲区

              arg0_DstBuffer++;

              arg4_DstBufferLength--;

              var_8_SrcRealLength--

              if(arg4_DstBufferLength==0||var_8_SrcRealLength==0)

                     break;

       }

}

 

 

然而实际上却没这么简单,从代码上看,有点像不可逆加密算法。无法知道什么样的参数最后能被转换为反斜杠。事已至此,只好用个笨办法,穷举吧。写了个小程序,调用ntdll.dll中的RtlUpcaseUnicodeToOemN函数,传入/x0000/xffff,看看哪个字符会输出反斜杠。得到的结果如下:
0x2010, 0x2011, 0x2012……

根据上面的分析,知道了只要调用NetpwNameCompare函数,传入参数中含有0x2010之类的字符就能使栈上出现反斜杠。

另外要注意这个反斜杠必须是个UNICODE的反斜杠,即”/x5c/x00”,反斜杠后面的必须跟一个”/x00”,这个”/x00”用字符串的0结尾来代替即可。在字符串在前面加上一个ASCII字符的UNICODE,如"/x4c/x00","/x4c/x00/x10/x20"入参,则在栈上得到的结果为"/x4c/xXX/x5c/x00"正好包含一个UNICODE反斜杠

 

4.      利用

分析完毕后就可以利用了。稍微调试下,整理出如下的表格:

esp

function

015ff3ce

反斜杠位置

015ff478

Wscpy函数里的返回位置.

015ff47c

Wscpy调用时

      

问题函数(7C941084)

015ff8f8

NetpwPathCanonicalize入口
(71BA9511)

015ff908

NetpwNameCompare入口
(71BABE87)

 

计算出反斜杠与Wscpy函数里的返回位置之间相距0xA8(xp sp2 0xA0)个字节。

构造字符串如下:

 

然后相继调用下面两个函数:
NetpwNameCompare

NetpwPathCanonicalize

 

具体代码我发在博客里了:http://littlewalle.download.csdn.net/

5.      另一个小BUG

windows2000windows xp中,CanonPathName这个函数有一个另外的问题。 可以简单把流程想象成这样:

CanonPathName(WCHAR *A, WCHAR B)

{

       WCHAR buffer[414];

       if(A!=0)

       {

              if(wcslen(A)!=0)

              {

                     wcscpy(buffer,A);

              }

       }else

       {

              buffer[0]=0;

       }

       wcscat(buffer,B);

    ……….

}

如果参数A不是0,而是一个指向空字符串的指针,则该函数的作用就是直接将B连接到缓冲buffer上,而buffer是一片没有初始化的区域,从而造成后面的字符串出错。

 

相应的,如果以下面的方式调用函数 (第四个参数是空字符串)
NetpwPathCanonicalize(“LittleWallE”,(unsigned short *)buffer,(unsigned char *)arg2, arg3,L"",(long *)Buff3,1);

则偶尔会利用不成功。

解决方法也很简单,将第四个参数设置为L"."即可.

4.   后记

MS08-067应用范围并不广,主要有以下障碍:

1>     windows xp sp2下默认是开启了DEP保护的,即栈上代码不可执行。Shellcode发过去之会让系统弹出一个错误对话框“xxxx地址不可写/执行
不过windows2003的DEP却仿佛没有效果,在网上也没搜索到答案,希望高手指点迷津。

2>     系统开了防火墙就无法利用。智能一点的防火墙发现RPC调用时会进行提示或者直接阻止。

3>     系统没有开server, workstation, computer browser这几个服务,RPC调用就会受阻。

 

所以MS08-067没有想象中的强大。不过研究它的过程还是挺有意思的。

你可能感兴趣的:(MS08-067的稳定利用方法)