├── cve-2017-5121-code-injection.html
├── cve-2017-5121-ret-addr-corruption.html
├── cve-2017-5121-spilled-register.html
└── README.md
/cve-2017-5121-code-injection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
315 |
316 |
317 |
--------------------------------------------------------------------------------
/cve-2017-5121-ret-addr-corruption.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
316 |
317 |
318 |
--------------------------------------------------------------------------------
/cve-2017-5121-spilled-register.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
366 |
367 |
368 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Clang Control Flow Integrity (CFI) Bypass Techniques
2 | In the following we demonstrate three exploitation techniques to bypass Clang's CFI implementation [0] when applied to Chromium [1]. Clang implements fine-grained CFI, hence, unlike Microsoft's/Intel's CFI implementation CFGuard/CET, it cannot be bypassed using COOP [3]. The first two proof-of-concept exploits do not attack the CFI implementation itself but exploit the absence of other mitigation, i.e., JIT region protection and return address protection. Both techniques are well known, and we include them for completeness. Our third attack exploits a weakness in Clang's CFI that exists because the compiler tries to optimize the CFI run-time check. We implement our proof-of-concept exploits for an older version of the Chromium browser, which is vulnerable to cve-2017-5121 [4].
3 |
4 | ## Setup
5 | We used our server for compilation and a VM for testing, hence, two different versions. We provide the binaries for easier testing.
6 |
7 | ### Compilation
8 | System: Ubuntu 14.04.5 LTS
9 | Follow Google's instructions [2] and check out / compile a vulnerable version
10 | ```
11 | fetch --nohooks chromium
12 | cd src/
13 | git fetch --tags
14 | git checkout -b cve-2017-5121 61.0.3163.100
15 | gclient sync --with_branch_heads
16 | ./build/install-build-deps.sh
17 | gclient runhooks
18 | # enable "turbo_escape" by setting it to" "true" in v8/src/flag-definitions.h
19 | gn gen out/cfi '--args=is_debug=false is_cfi=true use_cfi_cast=true use_thin_lto=true' --check
20 | ninja -C out/cfi chrome
21 | ```
22 |
23 | ### Execution
24 | System: Ubuntu 16.04.3 LTS
25 | Binaries: https://drive.google.com/file/d/1Ik8q_nOdq-pdRqTgEfVVT7rO-e9V2vs2/ (150mb)
26 | Run it with `./chrome --no-sandbox --single-process --disable-gpu `. For our proof-of-concept exploits we focus on hijacking the instruction pointer (= bypassing CFI), hence, their effectiveness can only be observed with an attached debugger.
27 |
28 | ## Proof-of-concept Exploits
29 | cve-2017-5121 is a bug in the escape analysis of Chrome's JIT engine. The vulnerability allows to disclose memory, as well as, corrupt arbitrary data. Details can be found in the corresponding blog post by Microsoft [5]. We exploit this vulnerability to gain arbitrary read-write to the render process.
30 |
31 | ### PoC-1: [code injection into JIT region](cve-2017-5121-code-injection.html)
32 | Chromium/V8 keeps its JIT region mapped as RWX. Hence, by disclosing its address the attacker can inject a malicious payload, and invoke the payload by calling the corresponding JavaScript function. This is the most common attack technique to achieve arbitrary code execution in Chromium. This techniques is also described by Microsoft [5], and explains why Chrome does not include CFI by default despite its very low impact on the overall performance.
33 |
34 | ### PoC-2: [corrupting return address](cve-2017-5121-ret-addr-corruption.html)
35 | Assuming that the JIT region is no longer writable, e.g., by adapting a similar strategy as Chakra [6], the attacker needs to leverage traditional code-reuse attack techniques like Return-oriented Programming (ROP). Since Clang-CFI focuses on protecting forward branches, the attacker can hijack the control flow by corrupting a return address. Therefore, the attacker first discloses a stack address, e.g., the `isolate` struct which contains stack addresses (e.g., `isolate->thread_local_top_->c_entry_fp_`), and then leverages the arbitrary read/write primitive to overwrite a return address of an active stackframe.
36 |
37 | ### PoC-3: [corrupting stack-spilled registers](cve-2017-5121-spilled-register.html)
38 | The previous two attacks do not directly bypass Clang-CFI but exploit the absence of the strict enforcement of w^x memory and return address protection. However, both are feasible in practice [6, 7]. Hence, for our third attack we assume that these mitigations are in place.
39 |
40 | Due to how Clang-CFI is implemented, the compiler generates code that in some cases spills registers, which contain CFI-related values, temporarily to writable memory (stack). This is a problem because these CFI-values are supposed to be immutable as they are used to determine whether a call/jump destination address is valid or not. Hence, the attacker can modify the enforced CFI policy by corrupting these spilled registers.
41 |
42 | An analysis of the Chrome binary yields a surprisingly large number of instances where this happens. However, it is unlikely that all of these cases are exploitable, because this technique requires the attacker to reliably corrupt stack values without a stack-based memory-corruption vulnerability. In a browser setting this can be achieved either by spawning separate threads, or JavaScript callback functions. We refer to our previously published paper [8] for more details.
43 |
44 | For our PoC we use JavaScript callbacks: first, we trigger the execution of native code, which is subject to CFI run-time checks, by invoking a function member function of a JavaScript object. If defined, this code will trigger the execution of a JavaScript callback function, hence, switching the execution back to JavaScript. As a consequence, the stackframe of the native code can be reliably manipulated while executing JavaScript. We leverage our read/write primitive within the callback function (see `xhr_delay()`) to then corrupt the spilled register.
45 |
46 | We trigger the execution of the native code/callback by creating a `XMLHttpRequest` (line 310) JavaScript object and setting `onreadystatechange` to `xhr_delay()` (line 362). Calling the `open()` function of the `XMLHttpRequest` object will invoke the native `blink::EventTarget::FireEventListeners()` function.
47 | ```
48 | .text:00000000068E3970 ; _QWORD __cdecl blink::EventTarget::FireEventListeners(blink::EventTarget *__hidden this, blink::Event *)
49 | .text:00000000068E3970
50 | .text:00000000068E3970 var_40 = qword ptr -40h
51 | .text:00000000068E3970 var_38 = qword ptr -38h
52 | .text:00000000068E3970
53 | .text:00000000068E3970 push rbp
54 | .text:00000000068E3971 push r15
55 | .text:00000000068E3973 push r14
56 | .text:00000000068E3975 push r13
57 | .text:00000000068E3977 push r12
58 | .text:00000000068E3979 push rbx
59 | .text:00000000068E397A sub rsp, 18h
60 | .text:00000000068E397E mov r14, rsi
61 | .text:00000000068E3981 mov r13, rdi
62 | .text:00000000068E3984 mov rax, [r13+0]
63 | .text:00000000068E3988 lea r15, __typeid__ZTSN5blink4NodeE_global_addr ; <---- should stay read-only
64 | .text:00000000068E398F mov rcx, rax
65 | .text:00000000068E3992 sub rcx, r15
66 | .text:00000000068E3995 ror rcx, 3
67 | .text:00000000068E3999 cmp rcx, 41418h
68 | .text:00000000068E39A0 ja loc_68E3C1B
69 | .text:00000000068E39A6 lea rdx, __typeid__ZTSN5blink11EventTargetE_byte_array
70 | .text:00000000068E39AD test byte ptr [rcx+rdx], 80h
71 | .text:00000000068E39B1 jz loc_68E3C1B
72 | .text:00000000068E39B7 mov rdi, r13
73 | .text:00000000068E39BA call qword ptr [rax+0C8h]
74 | [...]
75 | .text:00000000068E3A9E mov rdi, r13
76 | .text:00000000068E3AA1 mov rsi, r14
77 | .text:00000000068E3AA4 mov rdx, rbx
78 | .text:00000000068E3AA7 mov rcx, r12
79 | .text:00000000068E3AAA call blink::EventTarget::FireEventListeners(blink::Event *,blink::EventTargetData *,blink::HeapVector &)
80 | .text:00000000068E3AAF test al, al
81 | ```
82 | This snippet is taken from the function prologue. Later the another `FireEventListeners()` (different arguments) which eventually will change the context to execute the JavaScript callback function:
83 | ```
84 | .text:00000000068E3DA0 blink::EventTarget::FireEventListeners(blink::Event *, blink::EventTargetData *, blink::HeapVector &) proc near
85 | ; [...]
86 | .text:00000000068E3DA0 push rbp
87 | .text:00000000068E3DA1 push r15 ; <---- not so read-only anymore
88 | .text:00000000068E3DA3 push r14
89 | .text:00000000068E3DA5 push r13
90 | .text:00000000068E3DA7 push r12
91 | ; [...]
92 | ; Switch to JavaScript execution, in our case xhr_delay() is executed.
93 | ; Note, r13 and r15 are temporarily spilled on the stack.
94 | ; [...]
95 | .text:00000000068E486D add rsp, 1D8h
96 | .text:00000000068E4874 pop rbx
97 | .text:00000000068E4875 pop r12
98 | .text:00000000068E4877 pop r13
99 | .text:00000000068E4879 pop r14
100 | .text:00000000068E487B pop r15 ; <---- loaded from writable memory
101 | .text:00000000068E487D pop rbp
102 | .text:00000000068E487E retn
103 | ```
104 | After returning from `FireEventListeners()`, the execution continues with the following code:
105 | ```
106 | .text:00000000068E3AAA call blink::EventTarget::FireEventListeners(blink::Event *,blink::EventTargetData *,blink::HeapVector &)
107 | .text:00000000068E3AAF test al, al
108 | .text:00000000068E3AB1 jnz loc_68E3B5D
109 | .text:00000000068E3AB7 jmp loc_68E3BDF
110 | [...]
111 | .text:00000000068E3B5D loc_68E3B5D:
112 | ;
113 | ; Not vulnerable CFI check, because rdx is freshly loaded
114 | ;
115 | .text:00000000068E3B5D mov rax, [r14]
116 | .text:00000000068E3B60 lea rdx, __typeid__ZTSN5blink5EventE_global_addr
117 | .text:00000000068E3B67 mov rcx, rax
118 | .text:00000000068E3B6A sub rcx, rdx
119 | .text:00000000068E3B6D ror rcx, 7
120 | .text:00000000068E3B71 cmp rcx, 0BCh
121 | .text:00000000068E3B78 lea rbx, __typeid__ZTSN5blink11EventTargetE_byte_array
122 | .text:00000000068E3B7F ja loc_68E3C1B
123 | .text:00000000068E3B85 lea rdx, __typeid__ZTSN5blink5EventE_byte_array
124 | .text:00000000068E3B8C test byte ptr [rcx+rdx], 40h
125 | .text:00000000068E3B90 jz loc_68E3C1B
126 | .text:00000000068E3B96 mov rdi, r14
127 | .text:00000000068E3B99 call qword ptr [rax+40h]
128 | ;
129 | ; Vulnerable CFI check:
130 | ;
131 | ; r13 and r15 are loaded at the beginning of the function and then pushed/popped
132 | ; to/from the stack during nested function calls. Note, that the following CFI check
133 | ; is similar to the one in the beginning of the function. This is probably the reasons the
134 | ; compiler decided to load the values once and keep them in a callee-save register.
135 | ;
136 | .text:00000000068E3B9C mov rax, [r13+0]
137 | .text:00000000068E3BA0 mov rcx, rax
138 | .text:00000000068E3BA3 sub rcx, r15 ; <---- r15 is corrupted
139 | .text:00000000068E3BA6 ror rcx, 3
140 | .text:00000000068E3BAA cmp rcx, 41418h ; <---- will pass
141 | .text:00000000068E3BB1 ja short loc_68E3C1B
142 | .text:00000000068E3BB3 test byte ptr [rcx+rbx], 80h ; <---- will pass
143 | .text:00000000068E3BB7 jz short loc_68E3C1B
144 | .text:00000000068E3BB9 mov rdi, r13
145 | .text:00000000068E3BBC call qword ptr [rax+48h] ; <---- set RIP to arbitrary address
146 | ```
147 |
148 |
149 |
150 | ## References
151 | [0] https://clang.llvm.org/docs/ControlFlowIntegrity.html
152 | [1] https://www.chromium.org/developers/testing/control-flow-integrity
153 | [2] https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md
154 | [3] http://syssec.rub.de/media/emma/veroeffentlichungen/2015/03/28/COOP-Oakland15.pdf
155 | [4] https://bugs.chromium.org/p/chromium/issues/detail?id=765433
156 | [5] https://cloudblogs.microsoft.com/microsoftsecure/2017/10/18/browser-security-beyond-sandboxing
157 | [6] https://blogs.windows.com/msedgedev/2017/02/23/mitigating-arbitrary-native-code-execution
158 | [7] https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf
159 | [8] https://www.informatik.tu-darmstadt.de/fileadmin/user_upload/Group_TRUST/PubsPDF/ccs15.stackdefiler.pdf
--------------------------------------------------------------------------------