bool Message::send(mach_msg_option_t options, mach_msg_timeout_t timeout, mach_port_name_t notify) { return check(mach_msg_overwrite(*this, options | MACH_SEND_MSG, length(), 0, MACH_PORT_NULL, timeout, notify, NULL, 0)); }
bool Message::sendReceive(mach_port_t receivePort, mach_msg_option_t options, mach_msg_timeout_t timeout, mach_port_name_t notify) { return check(mach_msg_overwrite(*this, options | MACH_SEND_MSG | MACH_RCV_MSG, length(), mSize, receivePort, timeout, notify, NULL, 0)); }
int main(UNUSED int argc, char **argv) { pid_t pid = argv[1] ? atoi(argv[1]) : 1; /* for testing */ void *event_system = IOHIDEventSystemCreate(NULL); if (!event_system) { ib_log("couldn't create HID event system"); } else { /* consumer page -> Volume Increment */ if (button_pressed(event_system, 0x0c, 0xe9) || /* telephony page -> Flash */ button_pressed(event_system, 0x0b, 0x21)) { ib_log("disabling due to button press"); return 0; } } mach_port_t port = 0; kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); if (kr) { ib_log("mach_port_allocate: %x", kr); return 0; } const char *lib = "/Library/Substitute/Helpers/posixspawn-hook.dylib"; struct shuttle shuttle = { .type = SUBSTITUTE_SHUTTLE_MACH_PORT, .u.mach.right_type = MACH_MSG_TYPE_MAKE_SEND, .u.mach.port = port }; char *error; int ret = substitute_dlopen_in_pid(pid, lib, 0, &shuttle, 1, &error); if (ret) { ib_log("substitute_dlopen_in_pid: %s/%s", substitute_strerror(ret), error); return 0; } /* wait for it to finish */ static struct { mach_msg_header_t hdr; mach_msg_trailer_t huh; } msg; kr = mach_msg_overwrite(NULL, MACH_RCV_MSG, 0, sizeof(msg), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, &msg.hdr, 0); if (kr) ib_log("mach_msg_overwrite: %x", kr); }
static void ExceptionThread(mach_port_t port) { Common::SetCurrentThreadName("Mach exception thread"); #pragma pack(4) struct { mach_msg_header_t Head; NDR_record_t NDR; exception_type_t exception; mach_msg_type_number_t codeCnt; int64_t code[2]; int flavor; mach_msg_type_number_t old_stateCnt; natural_t old_state[x86_THREAD_STATE64_COUNT]; mach_msg_trailer_t trailer; } msg_in; struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; int flavor; mach_msg_type_number_t new_stateCnt; natural_t new_state[x86_THREAD_STATE64_COUNT]; } msg_out; #pragma pack() memset(&msg_in, 0xee, sizeof(msg_in)); memset(&msg_out, 0xee, sizeof(msg_out)); mach_msg_header_t *send_msg = nullptr; mach_msg_size_t send_size = 0; mach_msg_option_t option = MACH_RCV_MSG; while (true) { // If this isn't the first run, send the reply message. Then, receive // a message: either a mach_exception_raise_state RPC due to // thread_set_exception_ports, or MACH_NOTIFY_NO_SENDERS due to // mach_port_request_notification. CheckKR("mach_msg_overwrite", mach_msg_overwrite(send_msg, option, send_size, sizeof(msg_in), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, &msg_in.Head, 0)); if (msg_in.Head.msgh_id == MACH_NOTIFY_NO_SENDERS) { // the other thread exited mach_port_destroy(mach_task_self(), port); return; } if (msg_in.Head.msgh_id != 2406) { PanicAlert("unknown message received"); return; } if (msg_in.flavor != x86_THREAD_STATE64) { PanicAlert("unknown flavor %d (expected %d)", msg_in.flavor, x86_THREAD_STATE64); return; } x86_thread_state64_t *state = (x86_thread_state64_t *) msg_in.old_state; bool ok = JitInterface::HandleFault((uintptr_t) msg_in.code[1], state); // Set up the reply. msg_out.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(msg_in.Head.msgh_bits), 0); msg_out.Head.msgh_remote_port = msg_in.Head.msgh_remote_port; msg_out.Head.msgh_local_port = MACH_PORT_NULL; msg_out.Head.msgh_id = msg_in.Head.msgh_id + 100; msg_out.NDR = msg_in.NDR; if (ok) { msg_out.RetCode = KERN_SUCCESS; msg_out.flavor = x86_THREAD_STATE64; msg_out.new_stateCnt = x86_THREAD_STATE64_COUNT; memcpy(msg_out.new_state, msg_in.old_state, x86_THREAD_STATE64_COUNT * sizeof(natural_t)); } else { // Pass the exception to the next handler (debugger or crash). msg_out.RetCode = KERN_FAILURE; msg_out.flavor = 0; msg_out.new_stateCnt = 0; } msg_out.Head.msgh_size = offsetof(__typeof__(msg_out), new_state) + msg_out.new_stateCnt * sizeof(natural_t); send_msg = &msg_out.Head; send_size = msg_out.Head.msgh_size; option |= MACH_SEND_MSG; } }
void MachServer::runServerThread(bool doTimeout) { // allocate request/reply buffers Message bufRequest(mMaxSize); Message bufReply(mMaxSize); // all exits from runServerThread are through exceptions try { // register as a worker thread perThread().server = this; for (;;) { // progress hook eventDone(); // process all pending timers while (processTimer()) {} // check for worker idle timeout { StLock<Mutex> _(managerLock); // record idle thread low-water mark in scan interval if (idleCount < leastIdleWorkers) leastIdleWorkers = idleCount; // perform self-timeout processing if (doTimeout) { if (workerCount > maxWorkerCount) // someone reduced maxWorkerCount recently... break; // ... so release this thread immediately Time::Absolute rightNow = Time::now(); if (rightNow >= nextCheckTime) { // reaping period complete; process UInt32 idlers = leastIdleWorkers; secinfo("machserver", "reaping workers: %d %d", (uint32_t) workerCount, (uint32_t) idlers); nextCheckTime = rightNow + workerTimeout; leastIdleWorkers = INT_MAX; if (idlers > 1) // multiple idle threads throughout measuring interval... break; // ... so release this thread now } } } // determine next timeout (if any) bool indefinite = false; Time::Interval timeout = workerTimeout; { StLock<Mutex> _(managerLock); if (timers.empty()) { indefinite = !doTimeout; } else { timeout = max(Time::Interval(0), timers.next() - Time::now()); if (doTimeout && workerTimeout < timeout) timeout = workerTimeout; } } // receive next IPC request (or wait for timeout) mach_msg_return_t mr = indefinite ? mach_msg_overwrite(bufRequest, MACH_RCV_MSG | mMsgOptions, 0, mMaxSize, mPortSet, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, (mach_msg_header_t *) 0, 0) : mach_msg_overwrite(bufRequest, MACH_RCV_MSG | MACH_RCV_TIMEOUT | MACH_RCV_INTERRUPT | mMsgOptions, 0, mMaxSize, mPortSet, mach_msg_timeout_t(timeout.mSeconds()), MACH_PORT_NULL, (mach_msg_header_t *) 0, 0); switch (mr) { case MACH_MSG_SUCCESS: // process received request message below break; default: secinfo("machserver", "received error: %d", mr); continue; } // process received message if (bufRequest.msgId() >= MACH_NOTIFY_FIRST && bufRequest.msgId() <= MACH_NOTIFY_LAST) { // mach kernel notification message // we assume this is quick, so no thread arbitration here cdsa_notify_server(bufRequest, bufReply); } else { // normal request message StLock<MachServer, &MachServer::busy, &MachServer::idle> _(*this); secinfo("machserver", "begin request: %d, %d", bufRequest.localPort().port(), bufRequest.msgId()); // try subsidiary handlers first bool handled = false; for (HandlerSet::const_iterator it = mHandlers.begin(); it != mHandlers.end(); it++) if (bufRequest.localPort() == (*it)->port()) { (*it)->handle(bufRequest, bufReply); handled = true; } if (!handled) { // unclaimed, send to main handler handle(bufRequest, bufReply); } secinfo("machserver", "end request"); } // process reply generated by handler if (!(bufReply.bits() & MACH_MSGH_BITS_COMPLEX) && bufReply.returnCode() != KERN_SUCCESS) { if (bufReply.returnCode() == MIG_NO_REPLY) continue; // don't destroy the reply port right, so we can send an error message bufRequest.remotePort(MACH_PORT_NULL); mach_msg_destroy(bufRequest); } if (bufReply.remotePort() == MACH_PORT_NULL) { // no reply port, so destroy the reply if (bufReply.bits() & MACH_MSGH_BITS_COMPLEX) bufReply.destroy(); continue; } /* * We don't want to block indefinitely because the client * isn't receiving messages from the reply port. * If we have a send-once right for the reply port, then * this isn't a concern because the send won't block. * If we have a send right, we need to use MACH_SEND_TIMEOUT. * To avoid falling off the kernel's fast RPC path unnecessarily, * we only supply MACH_SEND_TIMEOUT when absolutely necessary. */ mr = mach_msg_overwrite(bufReply, (MACH_MSGH_BITS_REMOTE(bufReply.bits()) == MACH_MSG_TYPE_MOVE_SEND_ONCE) ? MACH_SEND_MSG | mMsgOptions : MACH_SEND_MSG | MACH_SEND_TIMEOUT | mMsgOptions, bufReply.length(), 0, MACH_PORT_NULL, 0, MACH_PORT_NULL, NULL, 0); switch (mr) { case MACH_MSG_SUCCESS: break; default: secinfo("machserver", "send error: %d %d", mr, bufReply.remotePort().port()); bufReply.destroy(); break; } // clean up after the transaction releaseDeferredAllocations(); } perThread().server = NULL; } catch (...) { perThread().server = NULL; throw; } }
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; }
void client(void *tsd) { mach_msg_header_t *request; mach_msg_header_t *reply; mach_msg_option_t option; kern_return_t ret; int idx; tsd_t *ts = (tsd_t *)tsd; #ifdef SWAP_BUFFERS mach_msg_header_t *tmp; #endif request = (mach_msg_header_t *)ts->request_msg; reply = (mach_msg_header_t *)ts->reply_msg; for (idx = 0; idx < ts->num_msgs; idx++) { request->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); request->msgh_size = ts->request_msg_size; request->msgh_remote_port = ts->server_port; request->msgh_local_port = ts->reply_port; if (ts->msg_type == msg_type_complex) { ipc_complex_message *complexmsg = (ipc_complex_message *)request; request->msgh_bits |= MACH_MSGH_BITS_COMPLEX; complexmsg->body.msgh_descriptor_count = 1; complexmsg->descriptor.address = ts->ints; complexmsg->descriptor.size = ts->num_ints * sizeof(u_int32_t); complexmsg->descriptor.deallocate = FALSE; complexmsg->descriptor.copy = MACH_MSG_VIRTUAL_COPY; complexmsg->descriptor.type = MACH_MSG_OOL_DESCRIPTOR; } if (ts->oneway) { request->msgh_id = 0; option = MACH_SEND_MSG; } else { request->msgh_id = 1; option = MACH_SEND_MSG|MACH_RCV_MSG; } if (ts->verbose) printf("Sending request\n"); #ifdef SWAP_BUFFERS ret = mach_msg( request, option, ts->request_msg_size, ts->reply_msg_size, ts->reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (MACH_MSG_SUCCESS != ret) { mach_error("client: mach_msg: ", ret); fprintf(stderr, "bailing after %u iterations\n", idx); exit(1); } tmp = request; request = reply; reply = tmp; #else ret = mach_msg_overwrite(request, option, ts->request_msg_size, ts->reply_msg_size, ts->reply_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, reply, 0); if (MACH_MSG_SUCCESS != ret) { mach_error("client: mach_msg_overwrite: ", ret); fprintf(stderr, "bailing after %u iterations\n", idx); exit(1); } #endif if (ts->verbose && !ts->oneway) printf("Received reply\n"); } }