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

sdocumentviewer.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

sdocumentviewer.cpp @ 548] 000b0bac 01533892 xul!nsDocShell::Destroy 0x14f [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\docshell\base

sdocshell.cpp @ 4847] 000b0bc0 0142b4cc xul!nsFrameLoader::Finalize 0x29 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src

sframeloader.cpp @ 579] 000b0be0 013f4ebd xul!nsDocument::MaybeInitializeFinalizeFrameLoaders 0xec [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src

sdocument.cpp @ 5481] 000b0c04 0140c444 xul!nsDocument::EndUpdate 0xcd [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src

sdocument.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

sdocument.cpp @ 2149] 000b0ccc 01494a8b xul!nsHTMLDocument::ResetToURI 0x20 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src

shtmldocument.cpp @ 287] 000b0d04 014d583a xul!nsDocument::Reset 0x6b [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\base\src

sdocument.cpp @ 2088] 000b0d18 01c95c6f xul!nsHTMLDocument::Reset 0x12 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src

shtmldocument.cpp @ 274] 000b0f84 016f6ddd xul!nsHTMLDocument::Open 0x736 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src

shtmldocument.cpp @ 1523] 000b0fe0 015015f0 xul!nsHTMLDocument::WriteCommon 0x22a4c7 [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src

shtmldocument.cpp @ 1700] 000b0ff4 015e6f2e xul!nsHTMLDocument::Write 0x1a [e:\builds\moz2_slave\rel-m-rel-w32-bld\build\content\html\document\src

shtmldocument.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.