首页
社区
课程
招聘
[原创] Chrome v8 Issue 1307610漏洞及其利用分析
发表于: 2022-12-24 20:48 17905

[原创] Chrome v8 Issue 1307610漏洞及其利用分析

2022-12-24 20:48
17905

环境:Ubuntu 18.04

GDB

V8 9.9.115

由于接触v8的时间的原因,导致对v8漏洞熟悉的大部分为Turbofan,IC模块的,类型大部分为混淆或者检测绕过类型,

对v8出现的传统的UAF漏洞类型反而不太熟悉,最近就通过这个case对这种类型漏洞进行学习。

1:写在前面

1.1:v8在引入指针压缩之后,在索引对象的时候,使用的是短指针,也就是对象地址的后4个字节指向需要指向的对象。


    其实原理是预先跟系统申请4GB地址空间,然后在这个空间上开始初始化各种对象。

1.2: v8在对象初始化的时候,通常顺序是非常非常的固定(其顺序跟v8版本有关系),这就导致在特定版本的v8之中,v8默认类型的map(v8对象的原型),地址的前面4个字节是随机的(因为是系统分配的),而后面的4字节一般是固定的(因为是v8自己按照固定顺序加载的)。

假设有个对象var array=[1.1],他的map是在v8加载你写的js之前就已经确定,所以如果你第一次调试得到map为0x08203af9,重新调试地址还是为0x08203af9。

完整的地址为0xXXXXXXXX0x08203af9

只有前面的4个字节地址实现了随机化,而v8引入指针压缩后是采用后4个字节地址表示对象地址的,所以在这个版本的v8中,var array=[1.1]的map就一直为为0x08203af9。

1.3:同样的,如果我们在js中自定义了一个var array=[1.1]实例,只要不改变前面的内容,v8就会在一个固定的地址创建这个array数组实例,比如这次加载的地址为0x0804ad61,那么只要不改变var array=[1.1]前面的js加载顺序,就算重新加载这个js文件,v8还是会在0x0804ad61地址创建这个实例,而通过简单的运算elements则会指向0x0x0804ad51。

在实际的漏洞利用之中,如果知道v8的版本,利用v8的这种堆分配特性,不需要泄露出我们申请的对象的地址,就可以通过调试和简单的计算得到我们需要的所有对象地址。


下面是调试说明:

               

                                                        图:1.3.1系统环境

       

  待测试的js代码

                 

                                                        图 :1.3.2 第一次加载数组的地址

                 

                                                           图 :1.3.3 第一次加载数组的内存结构

               

                                                         图 :1.3.4 第二次加载数组的地址

               

                                                         图 :1.3.5 第二次加载数组的内存结构

                      

                                                               图 :1.3.6 重启后加载数组的地址

                     

                                                           图 :1.3.7 重启后加载数组的地址

                    通过调试我们可以知道var array=[1.1]的地址后4字节始终没有变,指向的结构内容也始终没有变。


2:Issue 1307610


2.1 Issue 1307610 root case


2.1.1:使用RegExp对象调用’re[Symbol.replace]’函数时,如果RegExp对象不再是fast mode或者初始的RegExp对象已经被修改,v8就会用Runtime::kRegExpReplaceRT函数来处理这个过程。

2.1.2:如果该RegExp为一个全局对象,就会调用’RegExpUtils::SetAdvancedStringIndex’来设置lastIndex,这个函数最终会调用’RegExpUtils::AdvanceString’,在这里,last_index将会增加,然后将新的last_index传递给’SetLastIndex’


2.1.3:在RegExpUtils::SetLastIndex处理中,会进入NewNumberFromInt64’,将会转换当前的’last_index’设置为一个integer或者一个HeapNumber(具体看传入的值是什么,如果大于SMI,就会用HeapNumber,否则就会使用Interger)。

然后如果满足条件HasInitialRegExpMap(isolate, *recv),也就是这个对象为fast的情况下。’last_index’将会用’SKIP_WRITE_BARRIER’标志位创建一个对象。

2.1.3:在使用’SKIP_WRITE_BARRIER’标志位set_last_index的情况下,如果’last_index’为一个HeapNumber对象的情况,我们可以将regexp通过垃圾回收进入了old-space,HeapNumber因为用了’SKIP_WRITE_BARRIER’标志位,所以可以通过一定的内存处理手段,在新的内存空间NewSpace中申请空间创建HeapNumber对象,由于regexp对象和这个HeapNumber对象并不在一块v8的内存管理空间,根据v8的垃圾回收机制,v8并不能通过regexp跟踪到HeapNumber对象的生命周期。所以当此时出发垃圾回收,由于v8不能跟踪这个HeapNumber对象实例的重新创建,因此regexp.last_index会变成悬垂的指针。



2.2 POC的构造

需要满足的条件:

第一:创建全局对象RegExp,修改全局对象RegExp,使得其满足能进入Runtime::kRegExpReplaceRT函数。


第二:将re.lastIndex设置为1073741823;,在随后的’RegExpUtils::SetAdvancedStringIndex’中会将其+1,结果为1073741824。因为超过了SMI的范围,因此re.lastIndex会申请一个带有SKIP_WRITE_BARRIER标志的堆创建HeapNumber()对象,来存储这个re.lastIndex。


第三:触发write_barrier,使得re对象和re.lastIndex在不同过的CG管理域,让v8无法随后通过re对象追踪re.lastIndex所指向的HeapNumber()对象地址变化。


第四:触发垃圾回收,让前面的HeapNumber()所在的空间被回收,re对象由于没有办法追踪这个对象,导致re.lastIndex没有再跟新,从而变为悬垂的指针。

2.3:补丁

这里的补丁也非常简单,只要修改

JSRegExp::cast(*recv).set_last_index(*value_as_object, SKIP_WRITE_BARRIER);

的SKIP_WRITE_BARRIER标志位为UPDATE_WRITE_BARRIER,让v8可以一直通过re对象追踪到re.lastIndex就可以。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-12-24 20:59 被苏啊树编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (1)
雪    币: 181
活跃值: (621)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
树哥牛逼,收下我的膝盖
2022-12-24 22:01
0
游客
登录 | 注册 方可回帖
返回
//