bool MemoryProtectionExceptionHandler::install() { MOZ_ASSERT(!sExceptionHandlerInstalled); // If the exception handler is disabled, report success anyway. if (MemoryProtectionExceptionHandler::isDisabled()) return true; kern_return_t ret; mach_port_t task = mach_task_self(); // Allocate a new exception port with receive rights. sMachExceptionState.current = {}; MachExceptionParameters& current = sMachExceptionState.current; ret = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, ¤t.port); if (ret != KERN_SUCCESS) return false; // Give the new port send rights as well. ret = mach_port_insert_right(task, current.port, current.port, MACH_MSG_TYPE_MAKE_SEND); if (ret != KERN_SUCCESS) { mach_port_deallocate(task, current.port); current = {}; return false; } // Start the thread that will receive the messages from our exception port. if (!sMachExceptionState.handlerThread.init(MachExceptionHandler)) { mach_port_deallocate(task, current.port); current = {}; return false; } // Set the other properties of our new exception handler. current.mask = EXC_MASK_BAD_ACCESS; current.behavior = exception_behavior_t(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES); current.flavor = THREAD_STATE_NONE; // Tell the task to use our exception handler, and save the previous one. sMachExceptionState.previous = {}; MachExceptionParameters& previous = sMachExceptionState.previous; mach_msg_type_number_t previousCount = 1; ret = task_swap_exception_ports(task, current.mask, current.port, current.behavior, current.flavor, &previous.mask, &previousCount, &previous.port, &previous.behavior, &previous.flavor); if (ret != KERN_SUCCESS) { TerminateMachExceptionHandlerThread(); mach_port_deallocate(task, current.port); previous = {}; current = {}; return false; } // We should have info on the previous exception handler, even if it's null. MOZ_ASSERT(previousCount == 1); sExceptionHandlerInstalled = true; return sExceptionHandlerInstalled; }
static void DisableMachExceptionHandler( MachExceptionHandlerData *saved_handlers) { kern_return_t kr = task_swap_exception_ports( mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, THREAD_STATE_NONE, (exception_mask_array_t) &saved_handlers->masks, &saved_handlers->count, (exception_handler_array_t) &saved_handlers->ports, (exception_behavior_array_t) &saved_handlers->behaviors, (exception_flavor_array_t) &saved_handlers->flavors); CHECK(kr == KERN_SUCCESS); }
bool xnu_create_exception_thread(RDebug *dbg) { #if __POWERPC__ return false; #else kern_return_t kr; mach_port_t exception_port = MACH_PORT_NULL; mach_port_t req_port; // Got the mach port for the current process mach_port_t task_self = mach_task_self (); task_t task = pid_to_task (dbg->pid); if (!task) { eprintf ("error to get task for the debuggee process" " xnu_start_exception_thread\n"); return false; } if (!MACH_PORT_VALID (task_self)) { eprintf ("error to get the task for the current process" " xnu_start_exception_thread\n"); return false; } // Allocate an exception port that we will use to track our child process kr = mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &exception_port); RETURN_ON_MACH_ERROR ("error to allocate mach_port exception\n", false); // Add the ability to send messages on the new exception port kr = mach_port_insert_right (task_self, exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND); RETURN_ON_MACH_ERROR ("error to allocate insert right\n", false); // Atomically swap out (and save) the child process's exception ports // for the one we just created. We'll want to receive all exceptions. ex.count = (sizeof (ex.ports) / sizeof (*ex.ports)); kr = task_swap_exception_ports (task, EXC_MASK_ALL, exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE, ex.masks, &ex.count, ex.ports, ex.behaviors, ex.flavors); RETURN_ON_MACH_ERROR ("failed to swap exception ports\n", false); //get notification when process die kr = mach_port_request_notification (task_self, task, MACH_NOTIFY_DEAD_NAME, 0, exception_port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &req_port); if (kr != KERN_SUCCESS) { eprintf ("Termination notification request failed\n"); } ex.exception_port = exception_port; return true; #endif }
static tb_bool_t it_inject(pid_t pid, tb_char_t const* path) { // check tb_assert_and_check_return_val(pid && path, tb_false); // trace tb_trace_d("inject: pid: %lu, path: %s: ..", (tb_size_t)pid, path); #ifdef TB_ARCH_ARM64 // uses libsubstrate first? if (tb_file_info("/usr/lib/libsubstrate.dylib", tb_null)) { // init library tb_bool_t ok = tb_false; tb_handle_t library = tb_dynamic_init("/usr/lib/libsubstrate.dylib"); if (library) { // trace tb_trace_d("library: %p", library); // the func it_MSHookProcess_t pMSHookProcess = tb_dynamic_func(library, "MSHookProcess"); if (pMSHookProcess) { // trace tb_trace_d("MSHookProcess: %p", pMSHookProcess); // hook process ok = pMSHookProcess(pid, path)? tb_true : tb_false; } // exit library tb_dynamic_exit(library); // trace tb_trace_i("%s", ok? "ok" : "no"); // ok? return ok; } } #endif // pid => task task_t task = 0; if (task_for_pid(mach_task_self(), (tb_int_t)pid, &task)) { tb_trace_i("task_for_pid: %lu failed, errno: %d", (tb_size_t)pid, errno); return tb_false; } // trace tb_trace_d("task: %u", task); // stuff cpu_type_t cputype; it_addr_bundle_t addrs; if (!it_stuff(task, &cputype, &addrs)) return tb_false; // trace tb_trace_d("dlopen: %p", addrs.dlopen); tb_trace_d("syscall: %p", addrs.syscall); // alloc stack mach_vm_address_t stack_address = 0; if (mach_vm_allocate(task, &stack_address, it_stack_size, VM_FLAGS_ANYWHERE)) return tb_false; // write path mach_vm_address_t stack_end = stack_address + it_stack_size - 0x100; if (mach_vm_write(task, stack_address, (vm_offset_t)it_address_cast(path), strlen(path) + 1)) return tb_false; /* the first one is the return address * * syscall(SYS_bsdthread_create, 0xdeadbeef, 0xdeadbeef, 128 * 1024, 0, 0) */ tb_uint32_t args_32[] = {0, 360, 0xdeadbeef, 0xdeadbeef, 128 * 1024, 0, 0}; tb_uint64_t args_64[] = {0, 360, 0xdeadbeef, 0xdeadbeef, 128 * 1024, 0, 0}; // init thread state union { it_arm_thread_state_t arm; it_arm_thread_state64_t arm64; it_x86_thread_state32_t x86; it_x86_thread_state64_t x64; natural_t nat; }state; thread_state_flavor_t state_flavor; mach_msg_type_number_t state_count; memset(&state, 0, sizeof(state)); // init thread state for the cpu type switch (cputype) { case CPU_TYPE_ARM: { tb_trace_i("cputype: arm"); memcpy(&state.arm.r[0], args_32 + 1, 4 * sizeof(tb_uint32_t)); if (mach_vm_write(task, stack_end, (vm_offset_t)it_address_cast(args_32 + 5), 2 * sizeof(tb_uint32_t))) return tb_false; state.arm.sp = (tb_uint32_t) stack_end; state.arm.pc = (tb_uint32_t) addrs.syscall; state.arm.lr = (tb_uint32_t) args_32[0]; state_flavor = ARM_THREAD_STATE; state_count = sizeof(state.arm) / sizeof(state.nat); // trace tb_trace_d("init: pc: %x", state.arm.pc); tb_trace_d("init: lr: %x", state.arm.lr); tb_trace_d("init: sp: %x", state.arm.sp); } break; case CPU_TYPE_ARM64: { tb_trace_i("cputype: arm64"); memcpy(&state.arm64.x[0], args_64 + 1, 6 * sizeof(tb_uint64_t)); state.arm64.sp = (tb_uint64_t) stack_end; // state.arm64.fp = (tb_uint64_t) stack_end; state.arm64.pc = (tb_uint64_t) addrs.syscall; state.arm64.lr = (tb_uint64_t) args_64[0]; state_flavor = ARM_THREAD_STATE64; state_count = sizeof(state.arm64) / sizeof(state.nat); // trace tb_trace_d("init: pc: %llx", state.arm64.pc); tb_trace_d("init: lr: %llx", state.arm64.lr); tb_trace_d("init: sp: %llx", state.arm64.sp); } break; case CPU_TYPE_X86: { tb_trace_i("cputype: x86"); if (mach_vm_write(task, stack_end, (vm_offset_t)it_address_cast(args_32), 7 * 4)) return tb_false; state.x86.esp = state.x86.ebp = (tb_uint32_t) stack_end; state.x86.eip = (tb_uint32_t)addrs.syscall; state_flavor = x86_THREAD_STATE32; state_count = sizeof(state.x86) / sizeof(state.nat); } break; case CPU_TYPE_X86_64: { tb_trace_i("cputype: x64"); state.x64.rdi = args_64[1]; state.x64.rsi = args_64[2]; state.x64.rdx = args_64[3]; state.x64.rcx = args_64[4]; state.x64.r8 = args_64[5]; state.x64.r9 = args_64[6]; state.x64.rsp = state.x64.rbp = stack_end; state.x64.rip = addrs.syscall; state_flavor = x86_THREAD_STATE64; state_count = sizeof(state.x64) / sizeof(state.nat); } break; default: tb_trace_i("cputype: unknown: %lx", (tb_size_t)cputype); return tb_false; } // init a remote thread thread_act_t thread = 0; if (thread_create(task, &thread)) return tb_false; // trace tb_trace_d("init: thread: %x", thread); // alloc port mach_port_t exc = 0; mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exc); if (mach_port_insert_right(mach_task_self(), exc, exc, MACH_MSG_TYPE_MAKE_SEND)) return tb_false; // swap port exception_mask_t em[2]; exception_handler_t eh[2]; exception_behavior_t eb[2]; thread_state_flavor_t ef[2]; mach_msg_type_number_t em_count = 2; if (task_swap_exception_ports(task, EXC_MASK_BAD_ACCESS, exc, EXCEPTION_STATE_IDENTITY, state_flavor, em, &em_count, eh, eb, ef)) return tb_false; tb_assert_and_check_return_val(em_count <= 1, tb_false); // resume thread, done: syscall(SYS_bsdthread_create, 0xdeadbeef, 0xdeadbeef, 128 * 1024, 0, 0) if (thread_set_state(thread, state_flavor, &state.nat, state_count)) return tb_false; if (thread_resume(thread)) return tb_false; // we expect three exceptions: one from thread when it returns, one from the new thread when it calls the fake handler, and one from the new thread when it returns from dlopen. tb_bool_t started_dlopen = tb_false; while (1) { // recv msg it_exception_message_t msg; if (mach_msg_overwrite(tb_null, MACH_RCV_MSG, 0, sizeof(msg), exc, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, (tb_pointer_t) &msg, sizeof(msg))) return tb_false; // trace tb_trace_d("recv: msg: from thread: %x", msg.thread.name); // check tb_assert_and_check_return_val((msg.Head.msgh_bits & MACH_MSGH_BITS_COMPLEX), tb_false); tb_assert_and_check_return_val((msg.msgh_body.msgh_descriptor_count != 0), tb_false); tb_assert_and_check_return_val((msg.Head.msgh_size >= offsetof(it_exception_message_t, old_state)), tb_false); tb_assert_and_check_return_val((msg.old_stateCnt == state_count), tb_false); tb_assert_and_check_return_val((msg.Head.msgh_size >= offsetof(it_exception_message_t, old_state) + msg.old_stateCnt * sizeof(natural_t)), tb_false); // the msg state memcpy(&state, msg.old_state, sizeof(state)); // dump // tb_dump_data((tb_byte_t const*)&state, sizeof(state)); // done if (msg.thread.name == thread) { tb_trace_d("terminate"); if (thread_terminate(thread)) return tb_false; } else { // init cond tb_bool_t cond = tb_false; switch(cputype) { case CPU_TYPE_ARM: { // trace tb_trace_d("recv: pc: %x", state.arm.pc); tb_trace_d("recv: lr: %x", state.arm.lr); tb_trace_d("recv: sp: %x", state.arm.sp); // cond cond = ((state.arm.pc & ~1) == 0xdeadbeee)? tb_true : tb_false; } break; case CPU_TYPE_ARM64: { // trace tb_trace_d("recv: pc: %llx", state.arm64.pc); tb_trace_d("recv: lr: %llx", state.arm64.lr); tb_trace_d("recv: sp: %llx", state.arm64.sp); // cond cond = ((state.arm64.pc & ~1) == 0xdeadbeee)? tb_true : tb_false; } break; case CPU_TYPE_X86: cond = (state.x86.eip == 0xdeadbeef)? tb_true : tb_false; break; case CPU_TYPE_X86_64: cond = (state.x64.rip == 0xdeadbeef)? tb_true : tb_false; break; } tb_trace_d("cond: %d, started_dlopen: %d", cond, started_dlopen); if (!cond) { // let the normal crash mechanism handle it task_set_exception_ports(task, em[0], eh[0], eb[0], ef[0]); tb_assert_and_check_return_val(0, tb_false); } else if (started_dlopen) { tb_trace_d("terminate"); if (thread_terminate(msg.thread.name)) return tb_false; break; } else { // done: dlopen(path, RTLD_LAZY) switch(cputype) { case CPU_TYPE_ARM: { state.arm.r[0] = (tb_uint32_t) stack_address; state.arm.r[1] = RTLD_LAZY; state.arm.pc = (tb_uint32_t) addrs.dlopen; state.arm.lr = 0xdeadbeef; } break; case CPU_TYPE_ARM64: { state.arm64.x[0] = (tb_uint64_t) stack_address; state.arm64.x[1] = RTLD_LAZY; state.arm64.pc = (tb_uint64_t) addrs.dlopen; state.arm64.lr = 0xdeadbeef; } break; case CPU_TYPE_X86: { tb_uint32_t stack_stuff[3] = {0xdeadbeef, (tb_uint32_t)stack_address, RTLD_LAZY}; if (mach_vm_write(task, (mach_vm_address_t)state.x86.esp, (vm_offset_t)it_address_cast(&stack_stuff), sizeof(stack_stuff))) return tb_false; } state.x86.eip = (tb_uint32_t) addrs.dlopen; break; case CPU_TYPE_X86_64: { tb_uint64_t stack_stuff = 0xdeadbeef; if (mach_vm_write(task, (mach_vm_address_t)state.x64.rsp, (vm_offset_t)it_address_cast(&stack_stuff), sizeof(stack_stuff))) return tb_false; state.x64.rip = addrs.dlopen; state.x64.rdi = stack_address; state.x64.rsi = RTLD_LAZY; } break; } it_exception_reply_t reply; memcpy(&reply.Head, &msg.Head, sizeof(mach_msg_header_t)); reply.Head.msgh_bits &= ~MACH_MSGH_BITS_COMPLEX; reply.Head.msgh_size = offsetof(it_exception_reply_t, new_state) + state_count * sizeof(natural_t); reply.Head.msgh_id += 100; memcpy(&reply.NDR, &msg.NDR, sizeof(NDR_record_t)); reply.RetCode = 0; reply.flavor = state_flavor; reply.new_stateCnt = state_count; memcpy(&reply.new_state, &state, sizeof(state)); if (thread_set_state(msg.thread.name, state_flavor, &state.nat, state_count)) return tb_false; if (mach_msg(&reply.Head, MACH_SEND_MSG, reply.Head.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL)) return tb_false; started_dlopen = tb_true; } } } // exit if (stack_address) vm_deallocate(task, stack_address, it_stack_size); if (thread) { thread_terminate(thread); mach_port_deallocate(mach_task_self(), thread); } if (task) mach_port_deallocate(mach_task_self(), task); if (exc) mach_port_deallocate(mach_task_self(), exc); // ok tb_trace_i("ok"); return tb_true; }
//No synchronization issue so far, need to use synchronize if we run into any issues int stop(mach_port_t task) { MachExceptionHandlerData old_handler; thread_act_port_array_t threadList; mach_msg_type_number_t threadCount; unsigned int count; kern_return_t kret; interface *face; threadCount=0; task_threads(current_task(), &threadList, &threadCount); DEBUG_PRINT("[+stop] Thread count before detaching %d\n", threadCount); face = find_interface(task); close(face->kq); //close kqueue count = 1; kret = task_swap_exception_ports(current_task(), EXC_MASK_ALL, MACH_PORT_NULL, EXCEPTION_DEFAULT|MACH_EXCEPTION_CODES, THREAD_STATE_NONE, (exception_mask_array_t) old_handler.masks, (mach_msg_type_number_t *) &old_handler.count, (exception_handler_array_t) old_handler.ports, (exception_behavior_array_t) old_handler.behaviors, (exception_flavor_array_t) old_handler.flavors); kret = mach_port_mod_refs(mach_task_self(), face->server_port, MACH_PORT_RIGHT_RECEIVE, -1); if (kret != KERN_SUCCESS) { RETURN_ON_MACH_ERROR("[-stop] mach_port_mod_refs failed", kret); } kret = mach_port_get_refs(mach_task_self(), face->server_port, MACH_PORT_RIGHT_RECEIVE, &count ); RETURN_ON_MACH_ERROR("[-stop] mach_port_get_refs failed", kret); if (face->server_port) { kret = mach_port_deallocate(current_task(),face->server_port); RETURN_ON_MACH_ERROR("[-stop] mach_port_deallocate failed", kret); } if (count) { DEBUG_PRINT("[-stop] failed to reset server port ref count exp:0 actual: %d\n", count); return 0; } task_threads(task, &threadList, &threadCount); DEBUG_PRINT("[+stop] Thread count after detaching %d\n", threadCount); face->registered_exception_handler = 0; count = 0; //REMOVE PID FROM BAD LIST TO ALLOW REATTACHING while(count < bad_list.x) { if(bad_list.y[count]->task == task) { break; } count++; } DEBUG_PRINT("TASK IS NUMBER: %d\n", count); int c; for(c = count; c < bad_list.x; c++) { bad_list.y[c] = bad_list.y[c+1]; } bad_list.x -= 1; return 1; }