十一月底,Immunitysec公开了一个WINS的远程安全漏洞,入侵者可利用该漏洞完全控制运行着WINS服务的系统,
冷眼Wins远程溢出漏洞
。其实这个漏洞早就被发现而且在地下流传已久,不过是最近才公布而已。就其公布的原因,估计是因为这个漏洞有趣,而且造成的影响不是很大吧。漏洞本身的特性决定了这个漏洞的利用可以有多种路子,让攻击者使用不同的方法来获得远程控制权,也许就是Immunitysec公开这个漏洞的初衷。不过微软对这个漏洞很敏感,没有发布补丁,因为说自己没有收到攻击报文,所以,本文只是详细的分析了整个漏洞的构造、利用,并未给出具体的利用程序和代码,相信读过我们系列文章“菜鸟版Expliot指南”的朋友应该都有能力写上一段自己的代码吧?冷眼Wins远程溢出漏洞
文/图 马木留克
关于这个漏洞的描述,比较官方的描述是这样的:WINS服务支持一个称之为“WINS 复制”的特性,不同的WINS服务器可以靠这个功能交换信息。WINS复制使用的也是监听在TCP 42端口上的标准WINS协议。在WINS复制的会话过程中,服务器端会发送一个内存指针给客户端,客户端用这个指针进行后续的会话。如果客户端在发送的数据中自己修改这个指针,使之指向用户控制的数据,最终就可以向任意地址写入16个字节的数据,通过覆盖特殊地址可以执行任意代码。
对于攻击者而言,了解其中的细节非常必要。通过这一个描述,大致上我们已经可以了解到,这不是一个典型的堆或者栈上的溢出,而是WINS用我们指定的一个数据作为指针,进行了一些操作,最终导致可以写16个字节的数据到任意地址——对于大多数情况而言,能写16个字节的数据就已经足够了,典型的堆溢出甚至只需要写4个字节的数据,这个条件还是很宽松的。通过进一步的分析,我们还可以知道,这里的“16个字节”是指连续的16个字节,更为重要的是,WINS服务处理异常的机制非常的强劲,在你提交恶意的代码造成漏洞利用之后,WINS服务不会挂掉,而是继续的运行,这就意味着我们可以反复地进行“写连续的16字节”。试想一下,我们可以反复的尝试直到成功为止,这个过程中WINS服务不会因为我们的恶意攻击而出现停止的情况。
在反汇编WINS服务的守护进程之前,我们可以想象一下各种可能的利用方式。按照Immunitysec的说法,恶意用户提交的数据是在堆上的,这就非常类似于远程堆溢出,加上可以写任意值到任意的地址,可以想象,几乎所有的堆溢出利用方式都可以应用到这上面来。最常用的,写top seh,然后看能不能通过寄存器来定位;其次,我们可以尝试写Lookaside表,通过多次发包来强制定位自己的ShellCode地址,然后通过改写RtlEnterCriticalSection(0x7ffdf020)等固定的函数指针来获得控制权;再直接一点,既然可以多次触发而服务不崩溃,那反复利用这个漏洞,直接写ShellCode到一块固定的内存(比如0x7ffdf222)去,继而通过改写固定的函数指针来获得控制权也是一条路子。总之,漏洞本身的特性决定了这个漏洞的利用可以有多种路子,让攻击者使用不同的方法来获得远程控制权,也许就是Immunitysec公开这个漏洞的初衷吧。
Immunity在公布漏洞的同时,公布了触发漏洞的报文格式,如表1所示:
报文长度(除头部4字节)
XX XX FF XX
四字节指针(绝对地址)
…… …… …… ……
(表1)
其中报文的长度要小于0x2f87f8。报文本身没有什么其它特殊的要求,满足上述格式的都可以触发漏洞。报文中的第三个DOWRD就是一个我们可以控制的指针,因此,我们可以设定这个指针指向我们发送的报文本身所在的地方,进而控制随后依据这个指针的内存读写。
在一台Windows 2000 Server SP3 + MS04006补丁的机器上,反汇编Wins.exe可以得到如下的代码:
.text:0101FE34 sub_101FE34 proc near ; CODE XREF: sub_101FD13+2A p
.text:0101FE34
.text:0101FE34 var_84 = dword ptr -84h
... ..
.text:0101FE34 arg_4 = dword ptr 0Ch
.text:0101FE34 arg_8 = dword ptr 10h
.text:0101FE34 arg_C = dword ptr 14h
... ...
.text:0101FE64 mov ebx, [ebp+arg_4]; 指向提交数据的指针
.text:0101FE67 mov al, [ebx+2] ; Flag的第三个字节
.text:0101FE6A mov cl, al
.text:0101FE6C and cl, 78h
.text:0101FE6F cmp cl, 78h ; 和0x78与操作,这里必须跳转
.text:0101FE72 jz loc_101FF07
... ...(中间三个跳转)
.text:010200F6 push eax ; 可以控制的指针A
.text:010200F7 lea ecx, [eax+50h]
.text:010200FA push ecx ; 指针A + 50h
.text:010200FB push dword ptr [eax+44h]
.text:010200FE push [ebp+arg_8] ; 数据的长度(报文头)
.text:01020101 push [ebp+arg_4] ; 指向提交数据的指针
.text:01020104 call sub_1020610
... ...
.text:01020638 xor ebx, ebx ; ebx置零
.text:01020640 mov eax, [ebp+arg_10] ; 指针A
.text:01020643 cmp eax, ebx ; 是否为合法地址(not NULL)
.text:01020645 jnz loc_10206CC ; 合法
... ...
.text:010206CC mov ecx, [eax+2Ch] ; 指针A+2C地址的内容
.text:010206CF mov [ebp+var_20], ecx
.text:010206D2 lea edi, [ecx+48h] ; 拷贝的源地址
.text:010206D5 mov esi, [ebp+arg_C]; 目的地址A+44地址的内容
.text:010206D8 movsd ; 十六字节的写操作,源/目的可控
.text:010206D9 movsd
.text:010206DA movsd
.text:010206DB movsd
可以看到提交报文中第三个DOWRD指针指向的内容最终进行了一系列的写操作,
电脑资料
《冷眼Wins远程溢出漏洞》(https://www.unjs.com)。如果以Where表示写操作的目的(esi),What表示写操作的数值(edi),简化后这个指针指向如下所示的结构如表2所示:where – 48h (lea edi, [ecx+48h]) offset:0h
… … … … … … … … … … … …
what * 4 (4个DOWRD) offset:24h
(表2)
精心控制发送报文中的指针的值,最后可以写四个DWORD。我们可以猜测我们发送的报文的大概位置,然后设定这个指针,那么,WINS就会把我们报文中的数字写到报文中指定的位置去。提交报文的长度可以很长(前面说了,只要小于0x2f87f8都是可以的),所以我们猜测起来还是比较方便,关键是这个指针指向的内容必须符合一定结构(见上表),一个比较好的方式是按照如下的结构指定我们发送报文中的内容(如表3所示)。
Where – 48h (DWORD) * 9
What * 4 (4个DWORD) * 9
(表3)
这样,我们只要大量的填充上述结构,就可以提高成功率——只要有一次猜测的指针指向数据块的位置是Where(连续九个为一组)就算成功。内存中大量的上述结构,加上这里堆分配的一些特性,最差情况下猜37次就可以猜到一个(连须36次都指针都猜到了What上),这个成功率还是可以接受的。从测试的情况来看,触发这个漏洞后极有可能导致堆内存没有正确释放,一个可能的利用办法就是先发送由一个大量上述结构填充的报文,设定指针指向一个会造成异常的地址(比如0xffffffff,但不能是0x0,WINS里面判断过这种情况),让这块内存不释放,然后后面都发小包来暴力猜测地址,从而准确的控制写入的源和目的地址。
好不容易才解决了写16字节到任意地址的方式,那么要写什么东西到哪里去呢?前面说过一些类似于堆利用的方法,可以去试试看,写出来的东西通用性应该很好,下面尝试的是另外一种方法。
要获得控制权就要想办法让程序执行到我们所指定的位置去。一般来说写一个程序会调用的函数指针是最好,如果不行的话也可以考虑写栈上的返回地址。上面的反汇编中,sub_1020610是导致写16字节的函数,这个函数并没有异常,我们可以考虑在这个地方先下一个断点,继续跟踪一直到有Ret的出现。Ret的意义在于把栈顶的数据作为返回值,也就是[ESP]->EIP,我们要是能够找准当时栈顶的位置然后覆盖掉,就可以准确地控制程序的流向。
那么,让程序流向何处呢?当然是我们的ShellCode。我们的ShellCode可以随报文一块儿送过去,所以在堆上一定能找到一份拷贝。报文已经有了大量的如表3所定义的结构,然后在报文的最后写入ShellCode,中间可用大量的NOPs来填充。在猜指针的时候我们就大致的猜到了堆所在的地址,所以根据猜测的地址来定位ShellCode也是可以的。这样子,把堆上ShellCode的地址写到栈上函数的返回地址就可以获得控制权了。这一步可能也需要暴力猜解,因为栈上的返回地址可能并不确定。
在测试的过程中,我们发现栈上返回地址的位置几乎是固定的,都是0x53df4c4,在对近70台机器的测试中,约有半数以上这个地址都是有效的。所以如果不打算写复杂的程序暴力猜测,这个地址就足够了。测试中涉及到了中文版几乎所有SP和MS04006补丁的情况,可见这个还是有一定的通用性的。倘若只是玩票性质的写写EXP,可以直接设置Where的值为0x53df4c4 – 48h。
同样的,在上述的测试过程中,我们发现第一个发送过去的报文总是拷贝到一个固定的位置,如果用表三所示的数据结构来填充报文,那么这个数据结构第一次出现的位置几乎都固定在0x05391eac。这个几率大概是八成左右,偶尔的失败情况不详,但是有一点可以肯定的是,如果你造成了异常而导致堆没有被正确释放,那么后续报文被拷贝到的地址肯定在这之后。
有了上述两个地址,要写一个成功率一般的利用程序已经不难,按照上面的报文格式,设定四字节的指针为0x05391eac,然后填充一个图三所示的结构,里面Where都填充为固定的栈上的函数返回地址0x53df4c4 – 48h,所有的What都填上我们猜测的一个大概的ShellCode的位置(比如0x05392000),接下来填充大约0x200个NOPs也就是0x90,然后加上一个任意的ShellCode,把这个报文发送到目标主机的TCP 42口,就有很大的可能性获得对方的控制权了。如果你的ShellCode不是以ExitProcess(0)结束的,那么你还可以反复的利用这个漏洞来获得控制权,只是每次的结果用的都是第一次发送的ShellCode——堆内存没有被释放而且我们猜测的是第一次发送的地址,如果你用的是Bind port的,那还好,如果是Reverse过来的那就麻烦了,别人要是抢先一步,用这种方法就算成功了你也没有机会的。
至于其它的利用方式,说实话写RtlEnterCriticalSection不太稳定,暴力猜解可是可以,还是麻烦了一点,不知道有没有其他的利用方式。表面上看起来难以利用的漏洞公布出来,没有0day的利用方法怎么也说不过去,只有看看以后是否有好的方法公布出来了。
关于这个漏洞的危害,报告也说明了,理论上来说对于所有开放了WINS服务(简单的用TCP端口42来判断)的Windows NT、Windows 2000、Windows 2003都有危险,在这个补丁没有出来的时候,最好还是把WINS服务关掉,或者用防火墙一类滤掉所有指向TCP42的报文。也就是因为漏洞没有出补丁就公开了这件事情,据说微软搞得很恼火(微软12月1日报告称没有收到受此漏洞攻击的报告,这简直是笑话,EXP是很容易写的,至少可以弄个不怎么通用的出来,我才不相信没有受到攻击,最多是没有报告罢了),顶着风头外面谁也不敢给出利用程序来。同样的,老独物WTF虽然也很期盼,但估计没这个胆把利用程序放到光盘里面去,大家要爽估计只有等到微软出了补丁后再放出利用程序来了,据说一月份就会出补丁,那么最快二月份可能就可以得到EXP。至于稍微懂行一点的朋友,想必都已经写出来了,有兴趣的的朋友也可以按照上面说的方法来写一个出来,我已经说得很清楚很明显了,不是么?