此文章首发于奇安信攻防社区f29K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6X3L8%4u0#2L8g2)9J5k6h3u0#2N6r3W2S2L8W2)9J5k6h3&6W2N6q4)9J5c8Y4y4Z5j5i4u0W2i4K6u0r3y4o6R3#2x3b7`.`.TheHole New World - how a small leak will sink a great browser (CVE-2021-38003)[V8 Deep Dives] Understanding Map Internals从 0 开始学 V8 漏洞利用系列篇Chaos-me-JavaScript-V8手把手教你入门V8漏洞利用最近在做2026年SUCTF的赛题复现,做到SU_BOX这一题的时候发现是一个v8引擎利用,之前也没有学过v8就一边学一边做了这一题,学习的过程中也踩了很多坑……编译的主要流程参考了从 0 开始学 V8 漏洞利用系列篇这一篇文章,这个文章将编译的流程写成了脚本,方便后续编译不同版本的v8。需要注意的是,编译的参数最好按照官方的来,比如SU_BOX使用的是J2V8,其编译v8的方式是这样的那我们就要在编译参数上尽可能相同,在此基础上添加部分调试参数进行编译所以写成build.sh脚本是这样的,由于我是在docker中编译的,因此很多路径都是绝对路径,需要进行修改如果不按照官方给的参数编译的话,有可能POC无法跑通,就直接影响后续的漏洞利用同时,经过多次尝试,我建议在运行ubuntu 20.04或者ubuntu 22.04且运行python 3.9或者python 3.10的系统环境中构建,过高或者过低的系统/python版本都会导致编译出错。编译完之后的目录是这样子的其中可执行文件d8就是我们攻击的目标文件同时需要将这两个文件导入到gdbinit文件中,这样才能使用v8的调试指令我们将以下内容写在test.js中%SystemBreak()就是断点,程序会断在这里;%DebugPrint(a)就是将a列表的调试数据打印到终端在gdb中调试d8文件,然后运行的时候带上--allow-natives-syntax参数才能使用%SystemBreak() %DebugPrint()两条调试指令,运行效果如下也可以在gdb中使用job指令查看对象需要注意的是,v8为了体现数据和地址的不同采用了不同的策略:地址+1存储,也就是说如果0x41414140作为对象地址存储就会变成0x41414141,这一点非常重要,所以这个对象的真实地址是0x3655bb30ee01-1=0x3655bb30ee00配合x指令打印具体地址信息,可以看到JSArray结构体其实是这样排布的回到刚刚的程序JSArray结构体用示意图来表示是这样的高版本的v8中存在地址压缩,在这个版本中部分字段占8字节,具体每个字段占几个字节需要根据具体版本进行调试分析我们看一下element是如何存储的可以看到数据其实是存储在一个FixedDoubleArray结构体对象里的,同时可以看到这个结构体的存储位置是JSArray结构体的上方,示意图如下:我们调试一下下面的程序,看看其中其他数据类型的存储和浮点类型的数据存储有什么不同这是b对象的信息示意图如下,可以看到在这个数据结构中存储element的结构体和JSArray结构体并不是在内存上相邻的这是c对象的信息示意图如下,可以看到在这个数据结构中存储对象的FixedArray结构体和JSArray结构体在内存上相邻的OK,那么我们可以简单总结一下:如果一个JSArray结构体存储的是浮点数和对象,那么这个结构体存储元素的地址和它本身是相邻的如果我们能通过一个漏洞修改浮点数JSArray的length字段,就可以通过索引来进行越界读写,这其实就是v8漏洞利用的核心了解了v8底层的数据存储就可以正式开始学习v8的漏洞利用了v8是如何判断一个JSArray结构体中存储的是浮点数、整数还是对象的呢,其实就是看JSArray的Map,每一种类型的Map都不一样如果我们将一个存储对象的JSArray结构体的Map修改为浮点数数组对应的Map,那么读取这个结构体的时候就会返回一个浮点数我们拿到的浮点数是什么呢?诶,这就是对象的地址,v8漏洞利用中我们就可以通过这个方式来泄露对象的地址。我们将这个流程封装成函数addressOf,可以这么调用将一个存储浮点数的JSArray结构体的Map修改为对象数组对应的Map,那么我读取这个结构体的时候就能返回一个对象,我们可以通过这个功能构造一个fake Object,将这个流程封装成函数fakeObj(),可以这样调用fake Object有什么用呢,我们可以通过这个fake Object来达到任意地址读和任意地址写的效果获得addressOf和fakeObj原语,基本就是靠我们上一块所讲的修改浮点数JSArray的length字段以达到越界写来实现的由于在v8漏洞中主要利用的还是浮点数的存储,因此需要一些工具函数用于大整数与浮点数之间的互转,函数定义如上,可以直接拿着用首先我们要通过漏洞实现addressOf和fakeObj原语,同时已经泄露出了浮点数JSArray的Map值,将其定义为DOUBLE_MAP常量,随后定义or修改浮点数对象如下:此时内存中是这样存储的然后通过addressOf原语获得标红区域的内存,将其传入fakeObj原语中,就可以拿到fake Object,将其定义为fake_object最后我们可以通过fake_object[0]来进行任意地址读,由于这个fake_object是伪造的存储浮点数的JSArray,因此通过fake_object[0]获取的值并不是addr中存储的数据,而是addr+0x10中存储的数据,原理可以看下面这一张图,因为addr应该是一个FixedDoubleArray结构体的地址,而存储数据的地址是addr+0x10我们可以将其封装成read64函数这里的addr就是我们想泄露的地址,那么写到fake_object中就应该是addr-0x10+1这个1的产生就是我们之前说过的v8存储地址和普通程序的差异任意地址写和任意地址读差不多,无非就是最后的从fake_object获取值改成了修改fake_object的存储的值由于低版本v8中会给WASM一个可读可写可执行的段,因此我们可以考虑通过shellcode替换原有的WASM内容以达到执行shellcode的效果当执行到断点时,vmmap就可以看到出现了一个可读可写可执行段,我们只需要想办法把shellcode写入这个段的开始地址,也就是0x11d80365f000,随后执行f()就可以触发shellcode需要注意的是,在较高版本的v8中,WASM段已经不是可读可写可执行了,而是变成了可读可执行,因此就没有办法通过这个方式来进行利用了我们回头看看之前的任意地址写,如果通过之前的方式写入shellcode会导致以下两个问题因此我们需要一种向某个对象中写入数据不需要经过map和length的方式来实现任意地址写调试结果如下可
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291201.htm
[原创]从POC到EXP:从0基础到v8 CVE-2021-38003复现
488 浏览
0 回复
暂无回复,快来抢沙发吧!