Internet Explorer CVE-2019–1367 Exploitation — part 2

taha aka "lordx64"
Confiant
Published in
23 min readJul 13, 2020

--

In Part1 we explained CVE-2019–1367 vulnerability root cause. In this part we will discuss how this bug was exploited in the wild to achieve code execution.

We will focus on the exploits we found in the wild used by both Magnitude Exploit Kit and DarkHotel APT.

If needed, readers can go directly to Part3 for the Magnitude Exploit KIT shellcode analysis.

CVE-2019–1367 exploitation

The following will present the type confusions and the exploit primitives that were built to achieve code execution in the CVE-2019–1367 exploits found in the wild.

Type confusion

For this bug class, an overlay of fake VARs was allocated on top of the freed areas, with specific objects that were controlled by the attacker.

The creation of this overlay of fake VAR objects was done via a helper function previously documented by Mcafee:

DarkHotel helper function for x32 bits
DarkHotel helper function for x64 bits

Below is the helper function found in Magnitude Exploit KIT, that is the same one used in DarkHotel 32 bits exploit:

Note: Magnitude 32 bits exploit (type confusion + exploit primitives) and the whole exploit is an obfuscated full copy paste of Darkhotel 32 bits exploit, beside the shellcode used and some elements we will talk about later in this part. We will focus on Darkhotel 32 bits exploit analysis (Note2: the 64 bits version of this exploit uses the same type confusion and exploit primitives discussed here, only the code execution is different)

This helper function will basically create a large string, a javascript property name, large enough to replace a whole GC block. This large string is basically composed of fake VARs objects. So what is a VAR Object?

A VAR object is a memory representation of javascript variable. it is basically a VARIANT structure documented by Microsoft.

In 32 bits the VAR is defined as below and have a total size of 16 bytes:

struct VAR {
QWORD type;// 8 bytes
void* obj_ptr; // 4 bytes
VAR next_var; // 4 bytes
}
// total size 16 bytes

A GC Block is composed of an array of VAR objects, and double linked list pointing to next and previous GC Block in the cache:

struct GcBlock {
GcBlock *forward; // 4 bytes
GcBlock *backward; // 4 bytes
VAR storage[100]; // 100*16 bytes
};
// total size 0x648 bytes

The idea of this large property name, is to simulate correctly aligned fake VAR objects and spray them in the same area that got freed.

The property name should have a certain size to cover a whole GC Block, in x32 flavors the property name was of size 0x17a and in x64 the size was 0x230, this is to cover 0x648 and 0x970 GC Block sizes respectively.

This might sound a bit confusing, here’s a diagram that showcases this process:

Diagram 1: type confusion using Fake VARs

Once the freed area was covered with sprayed fake VARs objects, the type confusion took place using a simple trickery that overwrites the VARs type field.

VAR.type field represents the Variable type, and can have different values: 3 for integer, 5 for double, 8 for string etc.

Also an interesting field which is VAR.obj_ptr, which depending on the type above, can be an immediate value or a pointer

The property name is appended with a specific values to cause a type confusion in VAR objects leading to our first infoleak:

  • in x32, the value “\u0003” was used, so the previous VAR.type: 0x81 field will be overwritten with 0x3 (integer), so the VAR.obj_ptr will be treated as an integer value instead of a pointer which can be read from to leak the object pointer.
  • in x64, the value “\u0005” was used , so the previous VAR.type: 0x81 field will be overwritten with 0x5 (double), so the VAR.obj_ptr will be treated as a float value instead of a pointer which can be read from to leak the object pointer

Below are examples taken from the exploits, with the values for both x32 and x64 flavors:

Darkhotel x64 exploit
Darkhotel x32 exploit
Magnitude x32 exploit

If we zoom into the previous Diagram 1, we can see how this type confusion could happen where VAR.type fields are overwritten :

Diagram 2 : VARs type confusion

This type confusion turns out to be extremely powerful when coupled with read primitives as we will see later.

In the exploit code, an array (we named “untracked”) is maintaining collected dangling pointers, each pointing to initial VARs of type RegExp that were previously sprayed.

To confirm if the type confusion worked, and a specific VAR.type was properly overwritten, the exploit parses this array of VAR pointers looking for VARs with the confused type number (VAR.type == 0x3):

We can also confirm this type confusion with the debugger, the VAR at 01c1f758 have a VAR.type of 0x3 (number) while it is a RegExpObj type (0x81) and not a number :

0:000> dd 01c1f760-4-4
01c1f758 00000003 00000000 0410f3a8 0426a038
01c1f768 00000082 00000000 01b90220 005effb0
01c1f778 00000087 005ee898 02c11a78 0000047f
01c1f788 00000087 005ee898 00492ee8 00000432
01c1f798 00000008 00000000 0044d4cc 01c1f7b8
01c1f7a8 00000009 00000000 0410c408 00000000
01c1f7b8 00000082 00000000 01b901c0 005effb0
01c1f7c8 00000087 005ee898 02c11a78 0000047f
0:000> ln poi(0410f3a8)
(69436310) jscript!RegExpObj::`vftable' | (6943640c) jscript!NameList::`vftable'
Exact matches:
jscript!RegExpObj::`vftable' = <no type information>

This way type confusion was confirmed, and the exploit starts building the first read primitives.

The memory location of this RegExpObj is used in a second type confusion — see below.

Building read primitives

In this exploit a read primitive was constructed, involving the type confusion discussed above and String VARs.

String VAR object pointers point directly to a BSTR. BSTRs are just basic string types used by the COM interface and they are managed in a separate heap by OLEAUT32.DLL.

BSTR is a composite data type that consists of a length prefix, a data string, and a terminator:

String Object

Let’s confirm this with a debugger, in the same previous GB block cache, there was a string VAR (with VAR.type == 0x8) at 01c1f798 so let’s inspect it :

0:000> dd 01c1f760-4-4
01c1f758 00000003 00000000 0410f3a8 0426a038
01c1f768 00000082 00000000 01b90220 005effb0
01c1f778 00000087 005ee898 02c11a78 0000047f
01c1f788 00000087 005ee898 00492ee8 00000432
01c1f798 00000008 00000000 0044d4cc 01c1f7b8
01c1f7a8 00000009 00000000 0410c408 00000000
01c1f7b8 00000082 00000000 01b901c0 005effb0
01c1f7c8 00000087 005ee898 02c11a78 0000047f
0:000> du 0044d4cc
0044d4cc "----->Confused I VAR address a"
0044d50c "t :"
0:000> db 0044d4cc-4 L1
0044d4c8 46

As we can see the VAR String object pointer, points directly to the BSTR String content. variables of type String have a Length property that obviously returns the length, but can be abused to read bytes from memory using charCodeAt method.

If we point a String VAR Object pointer to 4 bytes ahead, Length property will point at the first bytes of the String content, and charCodeAt can be used to read this memory content — up to Length*2 bytes of arbitrary memory could be read.

The exploit does this by carefully padding a name property containing VARs pointing at the previous RegExpObj object address + 4.

When interpreted as String, the RegExpObj vftable pointer becomes the BSTR length (which is a large value), and the rest of the RegExpObj memory can be read using charCodeAt, this way a read primitive was created to read arbitrary memory past the RegExpObj vftable pointer:

darkhotel x32 exploit second type confusion

We can check this with a debugger:

0:000> dd 0411e898-4-4-4-4-4-4
0411e880 00000000 ffffffff ffffffff ffffffff
0411e890 00000082 00000000 0410f3ac
00000000
0411e8a0 00000000 00000000 00000000 00000000
0411e8b0 00000000 00000000 00000000 00000000
0411e8c0 00000000 00000000 00000000 00000000
0411e8d0 00000000 00000000 5ea4852e 80000000
0411e8e0 0000029b 00000088 00000000 00000001
0411e8f0 00000000 00000008 00000000 568bed5f
0:000> ln poi(0410f3ac-4)
(69436310) jscript!RegExpObj::`vftable' | (6943640c) jscript!NameList::`vftable'
Exact matches:
jscript!RegExpObj::`vftable' = <no type information>

Building write primitive

At this point of the exploit, it is now possible read memory at addresses defined by spraying of fake VARs, thanks to the read primitive discussed earlier.

For this exploit it seems that a read primitive isn’t enough to achieve code execution, and a write primitive was required to be able to copy data of choice: shellcode bytes or to overwrite pointers like return addresses etc..

The write primitive chosen for this exploit, is a little bit complex and uses RegExpObj objects. The author of this exploit used a neat trick, to have a full control over a fake RegExpObj object, and introduced a Write-what-where condition in the code.

Before introducing the vulnerability, the exploit author needed to get a full control over a fake RegExpObj object and needed to control it from a RegExp variable, this RegExp variable will be used to control the write primitive as we will see later.

The fake RegExpObj was created using a third type confusion, involving this time RegExpObj objects.

RegExpObj type confusion

Let’s go back to the current stage of the exploit code: The exploit is able to read arbitrary memory content past the RegExpObj vftable pointer of a sprayed RegExpObj object.

This read primitive is used to read the RegExpObj object in memory, via charCodeAt, up to 0xc0 bytes were read (corresponding to the size of RegExpObj object in memory) and the content read was stored inside an Array K.

Then a couple of modifications were done on the bytes that were read (corresponding to the RegExpObj object) that are stored in this Array K, the bytes modified correspond to the following offsets:

  • RegExpObj+0x10 was set to 0
  • RegExpObj+0x48 (from 0 to 0x10 bytes) copied into RegExpObj+0x38
  • RegExpObj+0x48 set to 0
  • Then a series of 0xCCCC (size WORD) were appended to this Array K, shellcode.Length times (we assume it is a placeholder for a shellcode later).

We needed to understand the memory addresses corresponding to these offsets and their meaning, which costs us reverse engineering RegExpObj object and a lot of testing. Turns out these offsets correspond to:

  • RegExpObj+0x10: holds the address to the VAR object (of type 0x81) pointing to this RegExpObj. The type 0x81 corresponds to VAR objects of they RegExpObj
  • RegExpObj+0x38: Corresponds to a VAR (of type 0x80) pointing to a VAR of type String(type 0x8), that points to a jscript object called RegExpExec. This object starts with a magic value of 0x4b74614e, and starts with a header, followed by a String that corresponds to the RegExp pattern field (for example when calling new RegExp(“pattern”) or new RegExp.compile(“new pattern”))
  • RegExpObj+0x48: Corresponds to a VAR (of type 0x80) pointing to a VAR of type String(type 0x8), that points to a plain BSTR, this BSTR holds the RegExp pattern text field corresponding String. NOTE: this VAR is created only when calling the RegExp.source() function, otherwise it is not created.

The array K modification, that copied RegExpObj+0x48 into RegExpObj+0x38 is extremely important. We will see later why. For now let’s assume RegExpObj+0x48 holds the value 0x0000000 (from the modification of the Array K).

This Array K, is converted to a String L, and sprayed again via the call below in line 20: S[A].compile(L) followed by a S[A].source()

What will happen here, is we re-set the RegExpObj+0x38 and RegExpObj+0x48 VARs of our fake RegExpObj.

the VAR at RegExpObj+0x38 will point to a RegExpExec object containing the String of Array K. and RegExpObj+0x48 VAR is now pointing to a VAR of type 0x8, pointing to a BSTR representing the String of Array K.

At a higher level, the original RegExp objects that we sprayed initially in the exploit, the exploit set their pattern field (The text of the regular expression) to the string representing the Array K (memory representing a modified RegExpObj object).

Now here is how RegExpObj type confusion took place. The exploit kept a reference to an address in the RegExpObj which is RegExpObj+0x50, (corresponding to VAR.Obj_ptr of the VAR at RegExpObj+0x48, that we know should point to a VAR of type String)

To read this memory location, the exploit used again the previous String type confusion , by creating fake VARs pointing to this memory location (RegExpObj+0x50), and the memory is read via charCodeAt:

For the sake of clarity, let’s repeat the process we described up till now using a debugger.

Let’s pick a fake RegExpObj at address 042d17b8 and see what happens.

The VAR located at 042d17b8+0x48, with VAR.obj_ptr in 042d17b8+0x50 (That’s where the variable M was pointing to, see exploit code above)

If we follow this pointer we will find the corresponding String VAR at 01cfd0e0, with an VAR.obj_ptr pointing to 0423ed44.

0:002> dd 42d17b8 42d17b8+0x50
042d17b8 69516310 00000001 043117c8 01c43390
042d17c8 00000000 01cfbf80 000d0000 00000000
042d17d8 69500408 01c42160 042d1710 042d18a8
042d17e8 042d17b8 6958db88 00000080 00000000
042d17f8 01cfd100 00000000 00000080 00000000
042d1808 01cfd0e0
0:002> dd 01cfd0e0 L3
01cfd0e0 00000008 00000000 0423ed44

At the 0423ed44 we should find a BSTR object since this is a VAR.type of 0x8.

We know this BSTR object should correspond to the pattern text of a RegExpObj Object (Array K bytes), because as we saw earlier a call to RegExp.source() was made.

Indeed, we can confirm that by looking at the memory where this BSTR is located (423ed44), we can find the Array K (and the shellcode placeholders at the end: 0xCCCC ..)

0:002> dd 423ed44 L60
0423ed44 69516310 00000001 043117c8 01c43390
0423ed54 01cfc7d8 01cfbf80 00080000 00000000
0423ed64 69500408 01c42160 042d1710 042d18a8
0423ed74 042d17b8 6958db88 00000080 00000000
0423ed84 01cfd5f8 00000000 00000000 00000000
0423ed94 00000000 00000000 00000000 00000000
0423eda4 00000000 00000000 00000080 00000000
0423edb4 01cfc7c8 00000000 00000000 00000000
0423edc4 00000000 00000000 00000000 00000000
0423edd4 00000000 00000000 00000000 00000000
0423ede4 00000000 00000000 00000000 00000000
0423edf4 00000000 00000000 00000001 00000000
0423ee04 11e5d95e cccccccc cccccccc cccccccc
0423ee14 cccccccc cccccccc cccccccc cccccccc
0423ee24 cccccccc cccccccc cccccccc cccccccc
0423ee34 cccccccc cccccccc ......

Here’s a hint to the next type confusion (the third one..), if we look at the type of the object present at this BSTR (String of array K), we can see indeed it is a RegExpObj, we can confirm that with Windbg

0:002> ln poi(0423ed44)
(69516310) jscript!RegExpObj::`vftable' | (6951640c) jscript!NameList::`vftable'
Exact matches:
jscript!RegExpObj::`vftable' = <no type information>

This Array K string, is now written to an address now known to the exploit, the next step is to create the third type confusion, which is the creation of a fake RegExpObj, using the content of this Array K that we previously modified. This is done by spraying fake VARs of type 0x81 (RegExp) pointing to the location of the Array K String, which now will be interpreted as a RegExpObj Object.

Once the reference for this fake RegExpObj is found, it is stored in a variable Q, which is of type RegExp.

The exploit can now use all the RegExp functions, like RegExp.test() to make sure the corresponding fake RegExpObj object in memory is responding correctly.

For that the method Q.test(“t”) is called to confirm that this new fake RegExpObj object sprayed is working as expected.

Below, exploit code corresponding to what we just explained above:

To recapitulate, the exploit author needed to craft a RegExpObj object in memory (by copying an existing one, modifying it, and injecting it again via RegExp.compile() and RegExp.source()), and control it from a RegExp variable from the javascript code.

This was only made possible thanks to the use after free bug, the type confusions that we documented and the read primitives used until now.

The next question will be: Now that the exploit has full control over a RegExpObj, how could this be used to create a Write-what-where condition?

Write-What-Where condition:

The exploit author did a fourth and final trick, this time involving RegExpExec objects (remember the one that the VAR in RegExpObj+0x38 points to?)

Let’s define what a RegExpExec object is, and when it is used. This object is central to every Regular expression pattern matching operation, and it is involved in every call to RegExp.test(), or RegExp.compile(), etc..

This Object is composed by a header and data representing the RegExp pattern string in unicode. The header starts with a magic value of 0x4b74614e then followed by two indexes (header size, total object size) then the start offset of the BSTR string at offset 0xC, followed by the size of the BSTR string at offset 0x10.

For example here’s what the following javascript code looks like in memory:

0:000> dd 06b63f40 <-- RegExpObj
06b63f40 6a8c6310 00000000 007a6f98 0609dd10
06b63f50 060a5e90 060a5eb0 00000000 00000000
06b63f60 6a8b0408 06097f78 00000000 00000000
06b63f70 06b63f40 6a93db88 c0c00080 c0c0c0c0
06b63f80 060a5ea0 c0c0c0c0 c0c00080 c0c0c0c0
06b63f90 060a5e80 c0c0c0c0 c0c00000 c0c0c0c0
06b63fa0 c0c0c0c0 c0c0c0c0 c0c00000 c0c0c0c0
06b63fb0 c0c0c0c0 c0c0c0c0 c0c00000 c0c0c0c0
0:000> dd 06b63f40+0x48 L3
06b63f88 c0c00080 c0c0c0c0 060a5e80 <-- VAR
0:000> du poi(poi(06b63f40+0x48+4+4)+8) <-- VAR 0x8, *BSTR
09baafe4 "CAFEBABE" <-- BSTR data0:000> dd poi(poi(06b63f40+0x38+4+4)+8) <-- VAR 0x8, *RegExpExec
08a3df74 4b74614e 0000004c 00000024 0000003a
08a3df84 00000010 00000008 00000001 00000000
08a3df94 00000000 00000813 41004300 45004600
08a3dfa4 41004200 45004200 00431100 00460041
08a3dfb4 00420045 00420041 00000045 00690000
08a3dfc4 0064006e 0077006f 00000073 00730077
08a3dfd4 00490020 0074006e 00720065 0065006e
08a3dfe4 00200074 00780045 006c0070 0072006f

Here we find our RegExpExec 08a3df74 as highlighted above, using the string index at RegExpExec+0xc and its size at offset RegExpExec+0x10 we can extract it:

0:000> db 08a3df74+3a L0x10
08a3dfae 43 00 41 00 46 00 45 00-42 00 41 00 42 00 45 00 C.A.F.E.B.A.B.E.

One important aspect of this exploit, that until now we didn’t talk about (for readability), is the initial pattern string that the RegExp was sprayed with:

RegExp pattern text setup

As we will see next, this pattern string represents a fake RegExpExec object, hardcoded in the exploit.

By injecting this specially crafted RegExpExec, a vulnerability will be introduced in the code, that will result in a Write-What-Where condition.

Fake RegExpExec injection

So how this fake RegExpExec was used? Let’s go back to our Array K (that represent a fake RegExpObj object in memory).

If we remember, this Array K was slightly modified, then converted to a String L, that was sprayed via RegExp.compile(L) and RegExp.source().

If we remember there were 4 modifications made on the Array K including these two important ones:

  • RegExpObj+0x48 (from 0 to 0x10 bytes) copied into RegExpObj+0x38
  • RegExpObj+0x48 set to 0

The RegExpObj+0x38 VAR (that points to the previous RegExpExec), was overwritten with the BSTR data string of this RegExpExec, pointed by the VAR at RegExpObj+0x48. Which means, the fake RegExpExec object hardcoded in the exploit, replaced the previous and valid RegExpExec Object.

We can inspect the memory with a debugger, to see when the original RegExpObj+0x38 VAR pointing to a RegExpExec, was holding the BSTR of the fake RegExpExec. We can see these two RegExpExec overlap in memory:

Overlapping RegExpExec Objects

Then looking at the fake RegExpObj object with the fake RegExpExec copied, that came to replace the previous one (After the call to RegExp.compile(L) and RegExp.source(), and the RegExpObj+0x38 VAR overwrite)

Fake RegExpObj with fake RegExpExec both attacker controlled

Now that the exploit have a full control over a RegExpObj and a RegExpExec object, how this can be used to get a Write-what-where condition?

After analysis it seems that this fake RegExpExec introduced a bug inside the parsing routines of RegExpExec Object, in particular inside jscript!RegExpExec::FExecAux function.

Remark: We also noticed that this fake RegExpExec object has an invalid String length, and starts with large bytes, of non printable characters not corresponding to the actual BSTR String that this object holds.

Further more, this fake RegExpExec passes the checks of a function named RegExpBase::FBadHeader(). This function checks the RegExpExec before every pattern matching operation. Below is the function with our annotations:

Function Checking validity of RegExpExec object

This function does nothing much but checking the offsets in the header if they are correct, and that the string length is inferior to the Total size of the object minus String offset. Choosing any value of String length between 0 and (total_size-string_offset) will pass the test.

The Write-what-where (WWW) bug can be triggered by calling specially crafted helper functions below:

Darkhotel exploit x32 bits helper functions

Function S() is the function enabling the WWW condition. For example if the exploit simply calls S(0xABCD, 0xEFGH) the value 0xEFGH will be written to the address 0xABCD in memory.

The other helper functions in the code above V(a), U(a) and T(a) are used to read DWORD, WORD, BYTE respectively at the address a. This is done by calling the function S(a,b) to write a fake VAR of type String at offset 0x48 of the controlled fake RegExp Obj, with a VAR.Obj_ptr pointing to the location to be read+4. As we saw earlier, this can cause the String Length property to be used to read bytes at that location, the content of a byte is read via a call to : (Q.source.length >> 7) & 0xFF;

The function S() triggering the WWW bug, creates a String out of the passed parameters (address, value), and pass it the fake RegExpObj, via the method RegExp.test().

Calling the method RegExp.test() with the specifically crafted RegExpExec will automatically trigger the vulnerable path, particularly at the function jscript!RegExpExec::FExecAux+0x20, where value pointed by [edi+0x44] gets replaced by the String passed to RegExp.test()

Write-What-Where condition, in jscript.dll (with an attacker controlled RegExpExec)

This is ultimately due to a condition that became always false, which is comparing edx if it is lower than 0xffffffff (= -1). edx contains the size of the String passed to RegExp.test(). The value 0xffffffff came from carefully placed values in the metadata of the RegExpExec object controlled by the attacker:

Condition in jscript!RegExpExec::FExecAux that is always False

To confirm this WWW bug in different versions of jscript.dll we wrote a simple POC out of this bug , where we injected this fake RegExpExec object:

The POC above, calls S(0xAAAAAAAA, 0xBBBBBBBB), right after we manually modified the RegExpObj+0x38 pointer, to point to the fake RegExpExec the same way as CVE-2019–1367 exploit did.

Then we got an access violations as expected because the code tries to write 0xBBBBBBBB to an address 0xAAAAAAAA that is not mapped.

Below debugger output of this POC, confirming the write what where condition introduced in our debugger:

Write-What-Where POC

After this WWW bug was introduced, the exploit is now able to Read/Write-what-where.

Finally, this introduced bug turns out to work in every version that exists of jscript.dll for 32bits and 64bits! (we did a test with a jscript from 2010 and from 2020 see below)

IE 11 with KB4556798, on Windows 10 64 bits
IE 8 32 bits Windows 7 SP1 32 bits

Let’s Recapitulate

At hight level, here’s what has been done until now:

  • Spray with RegExp variables (that we don’t control)
  • Use After free bug trigger, references to dangling pointers kept
  • Type confusion with VARs type of Number to get memory location pointed by the references
  • use String VARs spraying to read that memory location content (RegExpObj object)
  • Read the whole content of that RegExpObj, create a fake one.
  • Manipulate the fake RegExpObj to overwrite the RegExpExec pointer, with fake RegExpExec.
  • Get reference to the fake RegExpObj (with the fake RegExpExec), via RegExpObj type confusion
  • Write-what-where condition bug is introduced
  • Get full control of a RegExp variable (represented by the fake RegExpObj)

With the main idea being spraying with RegExp variables -> do magic (steps above) -> full control over RegExp variables with Read/Write-what-where feature.

And that sums up the read and write primitives used in DarkHotel APT in the wild exploit!

Final next step is: Use the primitives above to gain code execution.

Shellcode execution

Shellcode execution in x32/x64 Darkhotel Exploits

To achieve code execution, the exploit needs to use executable modules, and at minimum locate the address of kernel32!VirtualProtect and ROP gadgets that are needed to bypass Data Execution Prevention (DEP).

The exploits from DarkHotel APT and Magnitude EK were tested against Windows 7 SP1 32bits, with IE8 32 bits version 8.0.7601.17514 with jscript.dll 32bits version 5.8.7601.17515, these exploit bypass ALSR and DEP, and stack pivot detection and CFG on windows 10 (as we will see later).

In order to get the address of kernel32, the exploit uses the jscript module. but we need the base address of the jscript as well.

Since the exploit fully controls a fake RegExpObj object. As we saw earlier, if we inspect this object at offset 0 we have a pointer to the vftable:

0:002> ln poi(0423ed44)
(69516310) jscript!RegExpObj::`vftable' | (6951640c) jscript!NameList::`vftable'
Exact matches:
jscript!RegExpObj::`vftable' = <no type information>

This vftable pointer is located somewhere inside the jscript module, and we will use it to get the base address of the jscript module.

The address of the vftable pointer is read (thanks to the specially crafted helper functions discussed earlier), its lower bytes are set to 0x0, and a search search for the DOS header is started.

The search is done by decrementing the current pointer by 0x10000 each time, until finding the DOS header, and from there the jscript module base can be deducted:

Note: the exploit locates the string “is Program canno” which is usually found in the DOS header “This Program cannot be run in DOS mode”

getting jscript module base

At this point we know that this exploit bypasses ASLR, since all the memory addresses of the modules are calculated dynamically, using the leaked vftable pointer.

Once jscript base was located, the import directory is parsed to get the kernel32 base address:

getting kernel32 module base

Note: We noticed at line 17, exploit author made a typo in “KERBEL32”. good indicator to hunt for similar exploits.

Once the base of the kernel32 module is found, any desired function of this module can be located by parsing the Export directory.

This export directory is parsed to locate KERNEL32!VirtualProtect, a function used in the ROP chain later to grant execution privileges to the shellcode:

calculating kernel32!VirtualProtect address

Same process has been repeated to locate ROP gadgets required for the code execution, example is how the gadget pop eax; ret was searched for in kernel32 module:

scanning for ROP gadgets

The previous gadget location is incremented by one, to deduct a second ROP gadget which is ret (0xC359) that is actually used.

Next a ROP chain layout is created in the heap at address pointed by (aC), that will ultimately call VirtualProtect and setup the shellcode memory region protection to 0x40 (PAGE_EXECUTE_READWRITE) as we will see later:

DarkHotel x32 ROP chain

The technique used to change the execution flow, is the same as the one discussed in Google P0 and F-secure, which consists of using the read primitive to get a pointer to the native stack, then use the write primitive to overwrite a return address.

Note: As stated in P0 report, this technique could bypass stack pivot detection, and CFG in Windows 10.

In the exploit we can find the code responsible for overwriting a return address in the native stack, with the variable aB pointing to it (we confirmed this with a debugger):

When a return address in the native stack is overwritten, ESP points to our controlled stack layout, and EIP executes our first ROP gadget. The ROP chain execution take place, to ultimately call the malicious shellcode at the end:

ROP + shellcode execution

And that’s a wrap! We hope we covered enough to help you understand how CVE-2019–1367 bug was exploited in the wild to achieve code execution.

Next are some differences we noticed between DarkHotel exploits and Magnitude EK exploit for CVE-2019–1367.

Darkhotel 64/32 bits vs Magnitude 32 bits exploits:

Memory Protection flags:

There was a small difference we noted in the memory protection flags of KERNEL32!VirtualProtect call that was setup in the ROP chain.

The difference was Magnitude EK used the value 0x20 PAGE_EXECUTION_READ instead of 0x40 PAGE_EXECUTE_READWRITE used by DarkHotel APT:

Magnitude x32 ROP Chain

It seems Magnitude Exploit KIT authors for historical reasons chose the protection value of 0x20 to bypass V3, which is Ahnlab Anti-malware solution (a known security vendor in South Korea), based on their analysis of Magnitude EK.

Use of NtContinue in DarkHotel x64 exploits

A difference noted in the DarkHotel x64 flavor of this exploit is the usage of NtContinue. This is an undocumented function in ntdll.dll that performs a system call to fill registers with given values from a CONTEXT structure. This technique is documented in the F-Secure Blog.

We invite our reader to check part3 of this blog post series, to read about our analysis of Magnitude Exploit KIT & DarkHotel APT shellcode, or continue reading the next section below covering a helper script for debugging.

Exploit analysis Tools

We wrote a helper script to inspect the memory where specific allocations were made (GC blocks), this helped us checking type confusion in memory as well as the fake VAR objects created.

WinDBG Javascript provider

Script providers bridge a scripting language to WinDBG internal object model. The JavaScript debugger scripting provider, allows the use of JavaScript with the debugger, the documentation and the usage are explained by Microsoft here.

We will use Javascript debugger scripting provider of WinDBG to trace calls and track memory allocation of GC Blocks.

Doing so we will be able for example to verify and confirm a couple of things, including how the first type confusion (discussed in the previous section) look like in memory.

All the above code, and addresses were tested for Jscript.dll x32 bits, Window 10 x64 bits and IE 11 32 bits

Breakpoints setup

We will setup breakpoint at 3 different places to :

  • Track GC Block allocations
  • Track when free GC Block are added to the cache
  • Track when GC Block are deallocated (physically freed)

Everything happens under jscript!GcBlockFactory::FreeBlk and jscript!GcBlockFactory::pblkAlloc

Below are the breakpoint we will set :

First breakpoint will enable us to track the allocation of every new GC Block allocated and return its memory address. We do this by setting a breakpoint right after the call to jscript!GcBlockFactory::pblkAlloc, and eax will contains the memory address of the newly allocated GC block:

Second breakpoint is more tricky, we need to separate between freed GC blocks (deallocated) and Free GC Blocks added to the list Free block list.

call below jscript!GcBlockFactory::FreeBlk+0x43 will be followed when a GC Block is added to the free list of blocks. ecx will contains the address of this block

Finally, last breakpoint is where a GC Block is completely freed (deallocated). inside jscript!GcBlockFactory::FreeBlk+0x1e there a call to delete and ecx contains the address of the block that is about to be freed (deallocated)

All can be implemented using the following javascript script we call it GcBlockFactory_monitor.js

PS: We took care of reading x32 bits values from registers.

GcBlockFactory_monitor.js

Running this script we can focus right away on the blocks that got freed (deallocated) as we know that’s where our dangling pointer is pointing to :

GcBlockFactory_monitor.js output

As we saw earlier in the exploitation phase, in32 Bits a GC Block have the exact size of 0x648h (versus 0x970h in x64 bits). This is based on the disassembly of GCBlockFactory::PblkAlloc() in 32bits :

GcBlockFactory::PblkAlloc() disassembly

Let’s pick the 3rd deallocated GC Block at address 0x23ea1d20 to verify its content:

GC Block memory view

Enter WinDBG Time travel debugging

We recorded Magnitude Exploit KIT 32 bits exploit in a Time travel debugging (TTD) session with WinDBG, and we will use WinDBG LINQ queries to evaluate what happened inside this GC Block located at 0x23ea1d20.

Thanks to TTD we can in one query get all the write accesses that happened to our GC block 0x23ea1d20. For readability, we will store the result for now in the variable @$Accesses (this variable will be used in next queries)

dx @$Accesses=@$cursession.TTD.Memory(0x23ea1d20, 0x23ea1d20+0x648, “w”)

As discussed earlier overlay properties names were copied in the same GC Block that got prematurely freed, to cause type confusion:

No we want to confirm the type confusion by looking at the time travel trace for this particular address: 0x23ea2048.

We can show all the write accesses to this address using the following query (using -g for the grind view) that will print the address, the value and the time (from the time travel trace) the write accesses happened:

0:000> dx  -g @$Accesses.Select(p => new {Address = p.Address, Value = p.Value, Time = p.TimeEnd}).Where(p=> p.Address == 0x23ea2048)
TTD trace grind view for write accesses to 0x23ea2048

We can see that the type confusion actually happened and the VAR.type of 0x81 was modified to 0x3 of integer type.

The value 0x881 is a result from calling CollectGarbage() which basically calls GcAlloc::SetMark function that basically did a (Var.Type = 0x800 | Var.Type)

Finally we can travel to that time position 0x85D7B:0x257 to analyze the stack trace:

0:001> dx @$create("Debugger.Models.TTD.Position", 0x85D7B, 0x257).SeekTo()

We can see the creation of a VVAL via a call the NameList::FCreateVval responsible for creating the large property name:

0:001> dx @$create("Debugger.Models.TTD.Position", 0x85D7B, 0x257).SeekTo()
(136c.1b4c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: 85D7B:257
@$create("Debugger.Models.TTD.Position", 0x85D7B, 0x257).SeekTo()
0:001> k
# ChildEBP RetAddr
00 05c3d978 74c7a8e5 msvcrt!_VEC_memcpy+0x7c
01 05c3d990 74c372a0 msvcrt!_VEC_memcpy+0xb4
02 05c3d9a8 6ac0b8d7 msvcrt!malloc+0x90
03 05c3d9d0 6ac1dd77 jscript!NameList::FCreateVval+0xe7
04 05c3da10 6ac0d73e jscript!ArrayObj::SetVal+0x77
05 05c3db00 6ac1f72b jscript!VAR::InvokeByName+0x59e
06 05c3db84 6ac10c60 jscript!VAR::InvokeByVar+0x111
07 05c3def8 6ac12f52 jscript!CScriptRuntime::Run+0x15b0

Conclusion

In this section we provided some elements to understand type confusions caused by this bug that were later used to build exploit primitives.

We also provided some helper scripts to quickly analyze specific regions in memory to monitor jscript garbage collection cache, that helped us look at type confusions.

The next part will focus on the analysis of the shellcode resulting from a successful exploitation by Magnitude EK group!

--

--