CVE-2021-38001 :V8 IC 中的类型混淆漏洞
信息收集
该 CVE
就是 TFC
上昆仑打的,issue
页面暂未公开,patch 如下:
可以看出来漏洞应该和 CVE-2021-30517
十分类似,为 receiver
和 holder
的类型混淆,更确切来说是把 holder
所对应的 handle
给了 receiver
而导致的类型混淆。
在 [github](https://github.com/vngkv123/articles/blob/main/CVE-2021-38001.md)
上可以找到验证的 poc
,分为两个文件,首先是 1.mjs
:
1 | export let x = {}; |
随后是 2.mjs
:
1 | import * as module from "1.mjs"; |
当运行 2.mjs
的时候会发生以下错误:
1 | # |
可以从错误信息中得知本来是需要一个 module
对象的,结果却得到了一个 Smi
的值。
调用栈如下:
1 | #3 0x00007f707760a1bb in v8::internal::CheckObjectType (raw_value=0x30a242424242, raw_type=0xda, raw_location=0x30a20800bded) at ../../src/objects/object-type.cc:45 |
漏洞分析
该漏洞与 CVE-2021-30517
十分类似,故不重复分析之前分析过的内容。我们直接从 LdaNamedPropertyFromSuper
开始看:
1 | // LdaNamedPropertyFromSuper <receiver> <name_index> <slot> |
其调用了 kLoadSuperIC
1 | void AccessorAssembler::LoadSuperIC(const LoadICParameters* p) { |
第一次会没有 feedback
,随后走到 LoadSuperIC_NoFeedback
:
1 | void AccessorAssembler::i(const LoadICParameters* p) { |
随后到 miss
并走到 kLoadWithReceiverNoFeedbackIC_Miss
:
1 | RUNTIME_FUNCTION(Runtime_LoadWithReceiverNoFeedbackIC_Miss) { |
然后是 load
:
1 | MaybeHandle<Object> LoadIC::Load(Handle<Object> object, Handle<Name> name, |
调试一下可以发现此时的 receiver
和 lookup_start_object
(也就是 object
)分别为 c
和 zz
,key
( 也就是 name
)为 y
:
1 | gef➤ jh receiver |
随后会走到 UpdateCaches
1 | void LoadIC::UpdateCaches(LookupIterator* lookup) { |
最关键的来了,它会走到 ComputeHandler
:
1 | Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) { |
分别查看 receiver,lookup_start_object
和 holder
,可以知道他们分别为 c,zz,module
:
1 | gef➤ jh receiver |
最终会用 holder
来生成对应的 handle
当有了该 handle
的时候,接下来 LoadSuperIC
就会走到有 handle
的地方去使用 handle
:
1 | BIND(&if_handler); |
接下来依次 HandleLoadICHandlerCase → HandleLoadICSmiHandlerCase → HandleLoadICSmiHandlerLoadNamedCase
,并最终走到这个分支:
1 | BIND(&module_export); |
可以看到 module
是直接使用 p->receiver
来加上 kModuleOffset
进行获取的,而并非 holder
,这样便会造成一个类型混淆的问题
漏洞利用
和 CVE-2021-30517
一样,我们可以得知 value
是通过以下过程获得
1 | // module |
然而由于是 ObjectHashTable
, y
的位置也不是固定的,只要我们填满这块区域就能绕过该限制。
但是经过尝试,在不知道地址的情况下很难构造假的对象,其中的一种方法是堆喷出来固定的 map
等值伪造一个对象( Basic maps
都用有静态的低 32
位值,由于指针压缩的原因使得它更不随机了。不过该值因机器、版本等因素而异)。
另外也可以参考 p4nda
师兄在第五届看雪安全开发者峰会的演讲:
使用字符串进行没有泄漏的类型混淆利用,并通过修改 string
的 length
来进行地址泄漏:
首先我们先得到伪造的 double array
,并使其 element
指针指向 oobStr
并修改其长度:
1 | var oobStr = "Ver"; |
随后使用 fakeArr
和 oobStr
构造一系列的原语:
1 | for(let i = 0; i < 0x8000; i++){ |
这里手动用 ArrayBuffer 调了一下堆布局,这里似乎写代码会一定程度影响堆布局,同时用的 1.8457939563e-314 而非 i2f(0xdeadbeef) 是因为如果用后者的话 lastIndexOf 就会找到别的地方去(猜测是因为我们自己也使用了一个相同的 SMI )
最后写 shellcode
并执行:
1 | var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); |
如果用 arbWrite 方法一个字节一个字节写会走到 ic 的地方然后 crash 掉,可能和我们前面的操作有关,暂时还没弄清楚具体的原因,不过十分神奇2333
EXP
1 | /* 1.mjs |
Reference
https://github.com/Peterpan0927/TFC-Chrome-v8-bug-CVE-2021-38001-poc
https://github.com/vngkv123/aSiagaming/tree/master/Chrome-v8-1260577
https://github.com/vngkv123/articles/blob/main/CVE-2021-38001.md