根据eeye的一些公开的信息来看,漏洞是出在 wkssvc.dll的 vsprintf调用。推断应该是没有检查输入缓冲的长度。利用函数NetValidateName 可以直接攻击。
下面的环境是:
客户端:win2k,和服务端建立 ipc$连接,然后,用 NetValidateName 进行交互,触发溢出。具体的sample代码不贴出来了,packetstorm和其他站点已经公布了不少。。。
服务端:被攻击端(简体中文win2k + sp3)
打开windbg,跟踪相关的函数,开始是 RPCRT4.dll的 NdrServerCall2调用,这个短时间内没有办法细看和消化,不理会,继续跟踪进去。。。
然后又是一些调用。包括 NdrServerInitializeNew的调用, NdrPointerUnmarshell和 NdrConformantStringUnmarshall的调用,这些也可以不理会,继续跟踪,呵呵,机器其实已经重新启动了N次,没有关系,虚拟机。:)
下面是出错函数的分析,上面的一些初始化动作不理会,总之,错误是出在这里面,利用也是在这个函数返回的时候利用。。
.text:76724CD7 ; int __stdcall sub_76724CD7(HANDLE hFile,int,int)
.text:76724CD7 sub_76724CD7 proc near ; CODE XREF: sub_76724DB5+20p
.text:76724CD7
.text:76724CD7 var_81A = byte ptr -81Ah
.text:76724CD7 var_819 = byte ptr -819h
.text:76724CD7 Buffer = byte ptr -818h
.text:76724CD7 var_817 = byte ptr -817h
.text:76724CD7 NumberOfBytesWritten= dword ptr -14h
.text:76724CD7 SystemTime = _SYSTEMTIME ptr -10h
.text:76724CD7 hFile = dword ptr 8
.text:76724CD7 arg_4 = dword ptr 0Ch
.text:76724CD7 arg_8 = dword ptr 10h
.text:76724CD7
.text:76724CD7 push ebp
.text:76724CD8 mov ebp, esp
.text:76724CDA sub esp, 818h ; //!!这里只分配了 0x818=2072个字节的空间给全部变量
.text:76724CE0 cmp [ebp+hFile], 0 ; //判断是否无效的文件句柄
.text:76724CE4 jz locret_76724DB1 ; //如果是,则返回
.text:76724CEA push edi
.text:76724CEB mov edi, offset unk_76727C60
.text:76724CF0 push esi
.text:76724CF1 push edi ; lpCriticalSection
.text:76724CF2 call ds:EnterCriticalSection ; //进入临界空间
.text:76724CF8 xor esi, esi
.text:76724CFA cmp dword_76727A3C, esi ; 判断是否需要打印时间信息
.text:76724D00 jz short loc_76724D3C
.text:76724D02 lea eax, [ebp+SystemTime] ; 下面进行时间信息字符串的输出。
.text:76724D05 push eax ; lpSystemTime
.text:76724D06 call ds:GetLocalTime
.text:76724D0C movzx eax, [ebp+SystemTime.wSecond]
.text:76724D10 push eax
.text:76724D11 movzx eax, [ebp+SystemTime.wMinute]
.text:76724D15 push eax
.text:76724D16 movzx eax, [ebp+SystemTime.wHour]
.text:76724D1A push eax
.text:76724D1B movzx eax, [ebp+SystemTime.wDay]
.text:76724D1F push eax
.text:76724D20 movzx eax, [ebp+SystemTime.wMonth]
.text:76724D24 push eax
.text:76724D25 lea eax, [ebp+Buffer]
.text:76724D2B push offset a02u02u02u02u02 ; "%02u/%02u %02u:%02u:%02u "
.text:76724D30 push eax
.text:76724D31 call ds:sprintf ; //at first, format the time string...
.text:76724D37 add esp, 1Ch
.text:76724D3A mov esi, eax
.text:76724D3C
.text:76724D3C loc_76724D3C: ; CODE XREF: sub_76724CD7+29j
.text:76724D3C push [ebp+arg_8]
.text:76724D3F lea eax, [ebp+esi+Buffer] ; 得到输出缓冲的地址,这里是 esi-0x818
.text:76724D3F ; 其中,esi是调整的输出指针。如果打印了时间信息,
.text:76724D3F ; 则=时间字符串的长度。否则,=0。
.text:76724D46 push [ebp+arg_4] ; 这里的格式是:
.text:76724D46 ; NetpValidateName: checking to see if '%ws' is valid as type %d name.
.text:76724D46 ;
.text:76724D46 ; *** 注意,是 %ws 和 %d 的参数。
.text:76724D46 ; %ws。。。。比较麻烦的转换。嘿嘿,还是有办法的。
.text:76724D46 ;
.text:76724D49 push eax
.text:76724D4A call ds:vsprintf ; 这里发生了溢出
.text:76724D50 add esp, 0Ch
.text:76724D53 add esi, eax ; 这里判断是否 esi+eax = 0。如果没有输出,则做个标记=0
.text:76724D55 jz short loc_76724D6D
.text:76724D57 cmp [ebp+esi+var_819], 0Ah ; ...搞不懂为什么这里要判断。如果没有回车,也做个标记。。:(
.text:76724D57 ;
.text:76724D5F jnz short loc_76724D6D
.text:76724D61 mov dword_76727A3C, 1
.text:76724D6B jmp short loc_76724D78 ; 增加一个回车到输出缓冲的开头,很好玩,
.text:76724D6B ;
.text:76724D6D ;
.text:76724D6D
.text:76724D6D loc_76724D6D: ; CODE XREF: sub_76724CD7+7Ej
.text:76724D6D ; sub_76724CD7+88j
.text:76724D6D xor eax, eax
.text:76724D6F test eax, eax
.text:76724D71 mov dword_76727A3C, eax
.text:76724D76 jz short loc_76724D91
.text:76724D78
.text:76724D78 loc_76724D78: ; CODE XREF: sub_76724CD7+94j
.text:76724D78 mov [ebp+esi+var_819], 0Dh ; 增加一个回车到输出缓冲的开头,很好玩,
.text:76724D78 ;
.text:76724D80 mov [ebp+esi+Buffer], 0Ah
.text:76724D88 and [ebp+esi+var_817], 0
.text:76724D90 inc esi
.text:76724D91
.text:76724D91 loc_76724D91: ; CODE XREF: sub_76724CD7+9Fj
.text:76724D91 lea eax, [ebp+NumberOfBytesWritten]
.text:76724D94 push 0 ; lpOverlapped
.text:76724D94 ; 这里进行写文件的动作。
.text:76724D94 ; 注意,WriteFile的第4个参数
.text:76724D94 ; lpNumberOfBytesWritten 是在
.text:76724D94 ; ebp-14的位置,会改写buffer,所以,
.text:76724D94 ; 如果有shellcode放到那里,就要小心
.text:76724D94 ; 这个位置的数据了。。
.text:76724D96 push eax ; lpNumberOfBytesWritten
.text:76724D97 lea eax, [ebp+Buffer]
.text:76724D9D push esi ; nNumberOfBytesToWrite
.text:76724D9E push eax ; lpBuffer
.text:76724D9F push [ebp+hFile] ; hFile
.text:76724DA2 call ds:WriteFile
.text:76724DA8 push edi ; lpCriticalSection
.text:76724DA9 call ds:LeaveCriticalSection ; 这里 LeaveCriticalSection。还好,参数edi没有被改掉。
.text:76724DA9 ; 否则,进行攻击的时候又多了很多麻烦了。
.text:76724DAF pop esi
.text:76724DB0 pop edi
.text:76724DB1
.text:76724DB1 locret_76724DB1: ; CODE XREF: sub_76724CD7+Dj
.text:76724DB1 leave
.text:76724DB2 retn 0Ch ; ok,函数返回,嘿嘿,处理好了,就会执行我们的shellcode。
.text:76724DB2 sub_76724CD7 endp
.text:76724DB2
.text:76724DB5
如上分析,程序在vsprintf中,的参数 %ws进行格式化,将NetValidateName的第2个参数作为输入,格式化后,输出数据到堆栈中去,当内容太长的时候,就会发生堆栈溢出。
现在分析被攻击的可能性。
1. 这个函数开始就检查文件句柄的合法性,如果没有办法打开 %windir%\debug\netsetup.log的话,则这个函数没有办法被执行。所以,当触发该服务器执行文件记录时,连接的账号如果没有权限打开该文件,则不能进行以后的攻击。除非,服务器权限设置不正确,或者是fat32的文件格式,没有办法进行权限限制。嘿嘿。。。
2. 输入的长度不长的时候,会发生堆栈溢出,只要在溢出点(大概是 0x818-12)的位置,填入 jmp esp的内容,然后,在下一个地址开始的地方,写入shellcode,就可以运行代码了。
3. 输入的长度很长的时候,会触发windows的结构化异常