Hello fellow hackers,

I hope you guys had a blast at Defcon partying it up and hacking all the things, because ready or not, here's more work for you.  During the second day of the conference, I noticed a reddit post regarding some Mozilla Firefox 0day possibly being used by the FBI in order to identify some users using Tor for crackdown on child pornography. The security community was amazing: within hours, we found more information such as brief analysis about the payload, simplified PoC, bug report on Mozilla, etc. The same day, I flew back to the Metasploit hideout (with Juan already there), and we started playing catch-up on the vulnerability.

Brief Analysis

The vulnerability was originally discovered and reported by researcher "nils". You can see his discussion about the bug on Twitter. A proof-of-concept can be found here.

We began with a crash with a modified version of the PoC:

eax=72622f2f ebx=000b2440 ecx=0000006e edx=00000000 esi=07adb980 edi=065dc4ac
eip=014c51ed esp=000b2350 ebp=000b2354 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
xul!DocumentViewerImpl::Stop 0x58: 014c51ed 8b08            mov     ecx,dword ptr [eax]  ds:0023:72622f2f=???????? 

EAX is a value from ESI. One way to track where this allocation came from is by putting a breakpoint at moz_xmalloc:

...
bu mozalloc!moz_xmalloc 0xc "r $t0=poi(esp c); .if (@$t0==0xc4) {.printf \"Addr=0xx, Size=0xx\",eax, @$t0; .echo; k; .echo}; g"
...
Addr=0x07adb980, Size=0x000000c4
ChildEBP RetAddr
0012cd00 014ee6b1 mozalloc!moz_xmalloc 0xc [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\memory\mozalloc\mozalloc.cpp @ 57]
0012cd10 013307db xul!NS_NewContentViewer 0xe [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\layout\base\nsdocumentviewer.cpp @ 497]

The callstack tells us this was allocated in nsdocumentviewer.cpp, at line 497, which leads to the following function. When the DocumentViewerImpl object is created while the page is being loaded, this also triggers a malloc() with size 0xC4 to store that:

nsresult
NS_NewContentViewer(nsIContentViewer** aResult)
{
    *aResult = new DocumentViewerImpl();
    NS_ADDREF(*aResult);
    return NS_OK;
}

In the PoC, window.stop() is used repeatedly that's meant to stop document parsing, except they're actually not terminated, just hang.  Eventually this leads to some sort of exhaustion and allows the script to continue, and the DocumentViewerImpl object lives on.  And then we arrive to the next line: ownerDocument.write().

The ownerDocument.write() function is used to write to the parent frame, but the real purpose of this is to trigger xul!nsDocShell::Destroy, which deletes DocumentViewerImpl:

Free DocumentViewerImpl at: 0x073ab940
ChildEBP RetAddr
000b0b84 01382f42 xul!DocumentViewerImpl::`scalar deleting destructor' 0x10
000b0b8c 01306621 xul!DocumentViewerImpl::Release 0x22 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\layout\base\nsdocumentviewer.cpp @ 548]
000b0bac 01533892 xul!nsDocShell::Destroy 0x14f [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\docshell\base\nsdocshell.cpp @ 4847]
000b0bc0 0142b4cc xul!nsFrameLoader::Finalize 0x29 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src\nsframeloader.cpp @ 579]
000b0be0 013f4ebd xul!nsDocument::MaybeInitializeFinalizeFrameLoaders 0xec [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src\nsdocument.cpp @ 5481]
000b0c04 0140c444 xul!nsDocument::EndUpdate 0xcd [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src\nsdocument.cpp @ 4020]
000b0c14 0145f318 xul!mozAutoDocUpdate::~mozAutoDocUpdate 0x34 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src\mozautodocupdate.h @ 35]
000b0ca4 014ab5ab xul!nsDocument::ResetToURI 0xf8 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src\nsdocument.cpp @ 2149]
000b0ccc 01494a8b xul!nsHTMLDocument::ResetToURI 0x20 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src\nshtmldocument.cpp @ 287]
000b0d04 014d583a xul!nsDocument::Reset 0x6b [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src\nsdocument.cpp @ 2088]
000b0d18 01c95c6f xul!nsHTMLDocument::Reset 0x12 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src\nshtmldocument.cpp @ 274]
000b0f84 016f6ddd xul!nsHTMLDocument::Open 0x736 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src\nshtmldocument.cpp @ 1523]
000b0fe0 015015f0 xul!nsHTMLDocument::WriteCommon 0x22a4c7 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src\nshtmldocument.cpp @ 1700]
000b0ff4 015e6f2e xul!nsHTMLDocument::Write 0x1a [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src\nshtmldocument.cpp @ 1749]
000b1124 00ae1a59 xul!nsIDOMHTMLDocument_Write 0x537 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\obj-firefox\js\xpconnect\src\dom_quickstubs.cpp @ 13705]
000b1198 00ad2499 mozjs!js::InvokeKernel 0x59 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\js\src\jsinterp.cpp @ 352]
000b11e8 00af638a mozjs!js::Invoke 0x209 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\js\src\jsinterp.cpp @ 396]
000b1244 00a9ef36 mozjs!js::CrossCompartmentWrapper::call 0x13a [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\js\src\jswrapper.cpp @ 736]
000b1274 00ae2061 mozjs!JSScript::ensureRanInference 0x16 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\js\src\jsinferinlines.h @ 1584]
000b12e8 00ad93fd mozjs!js::InvokeKernel 0x661 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\js\src\jsinterp.cpp @ 345]

What happens next is after the ownerDocument.write() finishes, one of the window.stop() calls that used to hang begins to finish up, which brings us to xul!nsDocumentViewer::Stop. This function will access the invalid memory, and crashes. At this point you might see two different racy crashes: Either it's accessing some memory that doesn't seem to be meant for that CALL, just because that part of the memory happens to fit in there. Or you crash at mov ecx, dword ptr [eax] like the following:

0:000> r
eax=41414141 ebx=000b4600 ecx=0000006c edx=00000000 esi=0497c090 edi=067a24ac
eip=014c51ed esp=000b4510 ebp=000b4514 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
xul!DocumentViewerImpl::Stop 0x58:
014c51ed 8b08            mov     ecx,dword ptr [eax]  ds:0023:41414141=????????

0:000> u . L3
014c51ed 8b08            mov     ecx,dword ptr [eax]
014c51ef 50              push    eax
014c51f0 ff5104          call    dword ptr [ecx 4]

However, note the crash doesn't necessarily have to end in xul!nsDocumentViewer::Stop, because in order to end up this in code path, it requires two conditions, as the following demonstrates:

DocumentViewerImpl::Stop(void)
{
    NS_ASSERTION(mDocument, "Stop called too early or too late");
    if (mDocument) {
        mDocument->StopDocumentLoad();
    }
    
    if (!mHidden && (mLoaded || mStopped) && mPresContext && !mSHEntry)
    mPresContext->SetImageAnimationMode(imgIContainer::kDontAnimMode);
    
    mStopped = true;
    
if (!mLoaded && mPresShell) {  // These are the two conditions that must be met
    // If you're here, you will crash
    nsCOMPtrshellDeathGrip(mPresShell);
    mPresShell->UnsuppressPainting();
}

    return NS_OK;
}

We discovered the above possibility due to the exploit in the wild using a different path to "call dword ptr [eax 4BCh]" in function nsIDOMHTMLElement_GetInnerHTML, meaning that it actually survives in xul!nsDocumentViewer::Stop.  It's also using an information leak to properly craft a NTDLL ROP chain specifically for Windows 7. The following example based on the exploit in the wild should demonstrate this, where we begin with the stack pivot:

eax=120a4018 ebx=002ec00c ecx=002ebf68 edx=00000001
esi=120a3010 edi=00000001 eip=66f05c12 esp=002ebf54 ebp=002ebf8c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
xul!xpc_LocalizeContext 0x3ca3f:
66f05c12 ff90bc040000    call    dword ptr [eax 4BCh] ds:0023:120a44d4=33776277

We can see that the pivot is a XCHG EAX,ESP from NTDLL:

0:000> u 77627733 L6
ntdll!__from_strstr_to_strchr 0x9b:
77627733 94              xchg    eax,esp
77627734 5e              pop     esi
77627735 5f              pop     edi
77627736 8d42ff          lea     eax,[edx-1]
77627739 5b              pop     ebx
7762773a c3              ret

After pivoting, it goes through the whole NTDLL ROP chain, which calls ntdll!ZwProtectVirtualMemory to bypass DEP, and then finally gains code execution:

0:000> dd /c1 esp L9
120a4024  77625f18 ; ntdll!ZwProtectVirtualMemory
120a4028  120a5010
120a402c  ffffffff
120a4030  120a4044
120a4034  120a4040
120a4038  00000040
120a403c  120a4048
120a4040  00040000
120a4044  120a5010

Note: The original exploit does not seem to go against Mozilla Firefox 17 (or other buggy versions) except for Tor Browser, but you should still get a crash.  We figured whoever wrote the exploit didn't really care about regular Firefox users, because apparently they got nothing to hide :-)

Metasploit Module

Because of the complexity of the exploit, we've decided to do an initial release for Mozilla Firefox for now. An improved version of the exploit is already on the way, and hopefully we can get that out as soon as possible, so keep an eye on the blog and msfupdate, and stay tuned.  Meanwhile, feel free to play FBI in your organization, excise that exploit on your next social engineering training campaign.

![](/content/images/post-images/29324/Screen Shot 2013-08-07 at 2.10.40 AM.png)

Mitigation

Protecting against this exploit is typically straightforward: All you need to do is upgrade your Firefox browser (or Tor Bundle Browser, which was the true target of the original exploit). The vulnerability was patched and released by Mozilla back in late June of 2013, and the TBB was updated a couple days later, so the world has had a little over a month to get with the patched versions. Given that, it would appear that the original adversaries here had reason to believe that at least as of early August of 2013, their target pool had not patched.

If you're at all familiar with Firefox's normal updates, it's difficult to avoid getting patched; you need to go out of your way to skip updating, and you're more likely than not to screw that up and get patched by accident. However, since the people using Tor services often are relying on read-only media, like a LiveCD or a RO virtual environment, it's slightly more difficult for them to get timely updates. Doing so means burning a new LiveCD, or marking their VM as writable to make updates persistent. In short, it looks we have a case where good security advice (don't save anything on your secret operating system) got turned around into a poor operational security practice, violating the "keep up on security patches" rule. Hopefully, this is a lesson learned.