EXPORT int add_breakpoint(mach_port_t task, vm_address_t patch_addr, int cont, callback handler) { kern_return_t kret; char *tmp; mach_vm_size_t len = 1; // number of bytes to write uint8_t opcode = 0xcc; // the CC byte to write interface *face; face = find_interface(task); if(face->registered_exception_handler == 0) { DEBUG_PRINT("[+add_breakpoint] HERE IN ADD BREAK\n %d", 0); register_(task); } kret = mach_vm_protect(task, patch_addr, len, FALSE, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE); RETURN_ON_MACH_ERROR("[-add_breakpoint] mach_vm_protect()", kret); if (patch_addr <= MAX_BREAKS) { DEBUG_PRINT("[-add_breakpoint] INVALID BREAKPOINT ADDRESS %lx\n", patch_addr); return -1; } else if(face->current_break >= MAX_BREAKS) { DEBUG_PRINT("[-add_breakpoint] Max %d breaks reached!\n", MAX_BREAKS); return -1; } DEBUG_PRINT("[+add_breakpoint] Breakpoint %u: %lx added\n", face->current_break, patch_addr); tmp = (char*) read_memory(task, patch_addr, 1); breakpoint_struct *new_break = safe_malloc(sizeof(breakpoint_struct)); new_break->address = patch_addr; new_break->original = tmp[0] & 0xff; new_break->handler = handler; if(face->single_step) { new_break->index = face->single_step_index; } else { new_break->index = face->current_break == 0 ? 0 : face->breaks[face->current_break-1]->index + 1; } new_break->flags = cont; if(face->max_break == 0) { face->max_break = 1; } if(face->current_break >= (face->max_break - 1)) { DEBUG_PRINT("[+add_breakpoint] ALLOCATING MORE BP! CURRENTLY: %d\n", face->current_break); face->breaks = safe_realloc(face->breaks, sizeof(breakpoint_struct*) *(face->max_break*2)); face->max_break *= 2; } // face->breaks = safe_realloc(face->breaks, sizeof(breakpoint_struct*) *(face->current_break+1)); face->breaks[face->current_break++] = new_break; write_memory(task, patch_addr, opcode, len); // write the byte kret = mach_vm_protect(task, patch_addr, (mach_vm_size_t)1, FALSE, VM_PROT_READ | VM_PROT_EXECUTE); RETURN_ON_MACH_ERROR("[-add_breakpoint] RESTORE mach_vm_protect()", kret); return 1; }
EXPORT int start(mach_port_t task, pid_t infoPid) { kern_return_t kret; pthread_t tid[2]; interface* face = find_interface(task); kret = mach_port_allocate(current_task(), MACH_PORT_RIGHT_RECEIVE, &face->server_port); RETURN_ON_MACH_ERROR("[-start] mach_port_allocate failed", kret); kret = mach_port_insert_right(current_task(), face->server_port, face->server_port, MACH_MSG_TYPE_MAKE_SEND); RETURN_ON_MACH_ERROR("[-start] mach_port_insert_right failed", kret); kret = task_set_exception_ports(task, EXC_MASK_ALL, face->server_port, EXCEPTION_DEFAULT|MACH_EXCEPTION_CODES, THREAD_STATE_NONE); RETURN_ON_MACH_ERROR("[-start] task_set_exception_ports failed", kret); int err = pthread_create(&tid[0], NULL, (void *(*)(void*))kqueue_loop, (void *)(unsigned long long)infoPid); if (err != 0) DEBUG_PRINT("\n[-start] can't create thread :[%s]", strerror(err)); else DEBUG_PRINT("\n[-start] Thread created successfully %d\n", 0); err = pthread_create(&tid[1], NULL, (void *(*)(void*))exception_server, (void *(*)(void*))(unsigned long long)face->server_port); if (err != 0) DEBUG_PRINT("\n[-start] can't create thread :[%s]", strerror(err)); else DEBUG_PRINT("\n[-start] Thread created successfully %d\n", 0); return 1; }
EXPORT mach_port_t attach(pid_t infoPid) { mach_port_t task; int count = 0; task = get_task(infoPid); if(task == 0) { int kret = 0; RETURN_ON_MACH_ERROR("[-attach] invalid pid", kret); } if(bad_list.max_attach == 0) { bad_list.max_attach = 1; } while(count < bad_list.x) { if(bad_list.y[count]->task == task) { int kret = 0; RETURN_ON_MACH_ERROR("[-attach] duplicate pid", kret); } count++; } if(bad_list.x >= (bad_list.max_attach - 1)) { DEBUG_PRINT("ALLOCATING MORE! CURRENTLY: %d\n", bad_list.max_attach); bad_list.y = realloc(bad_list.y, sizeof(interface*) * (bad_list.max_attach*2)); bad_list.max_attach *= 2; } bad_list.y[bad_list.x] = malloc(sizeof(interface*)); interface* tmp = malloc(sizeof(interface)); memset(tmp, 0, sizeof(interface)); tmp->task = task; tmp->pid = infoPid; tmp->current_break = 0; tmp->current_exception = 0; tmp->single_step = NULL; tmp->registered_exception_handler = 0; bad_list.y[bad_list.x++] = tmp; DEBUG_PRINT("ATTACHING TO PROCESS # %d\n", bad_list.x); return task; }
/* * Removes a breakpoint from breaks array indicated by index or address (both are unsigned long long) */ EXPORT int remove_breakpoint(mach_port_t task, vm_address_t bp) { kern_return_t kret; int c, position, index; mach_vm_address_t address; interface *face; face = find_interface(task); if(!face->registered_exception_handler) { DEBUG_PRINT("SHOULD NEVER HAPPEN :| %d\n", 1); return -1; } position = find_break(task, bp); if(position == -1) { DEBUG_PRINT("[-remove_breakpoint] Failed find_break %d\n", position); return -1; } breakpoint_struct *breakpoint = face->breaks[position]; uint8_t opcode = breakpoint->original; // CC byte to write mach_vm_size_t len = 1; // number of bytes to write kret = mach_vm_protect(task, breakpoint->address, len, FALSE, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE); RETURN_ON_MACH_ERROR("[-remove_breakpoint] mach_vm_protect()", kret); write_memory(task, breakpoint->address, opcode, len); // and write the byte kret = mach_vm_protect(task, breakpoint->address, len, FALSE, VM_PROT_READ | VM_PROT_EXECUTE); RETURN_ON_MACH_ERROR("[-remove_breakpoint] RESTORE mach_vm_protect()", kret); address = face->breaks[position]->address; index = face->breaks[position]->index; if(face->single_step) face->single_step_index = index; for(c = position; c < face->current_break; c++) { face->breaks[c] = face->breaks[c+1]; } DEBUG_PRINT("[-remove_breakpoint] Breakpoint %x at %llx removed\n", index, address); face->current_break -= 1; // decrement counter return 1; }
static bool xnu_create_exception_thread(RDebug *dbg) { kern_return_t kr; int ret; mach_port_t exception_port = MACH_PORT_NULL; // 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 == -1) { eprintf ("error to get task for the debugging 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", R_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", R_FALSE); // Save the original state of the exception ports for our child process ret = xnu_save_exception_ports (dbg); if (ret == R_FALSE) { eprintf ("error to save exception port info\n"); return false; } // Set the ability to get all exceptions on this port kr = task_set_exception_ports (task, EXC_MASK_ALL, exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); RETURN_ON_MACH_ERROR ("error to set port to receive exceptions\n", R_FALSE); // Create the exception thread //TODO where to save the exception thread //TODO see options pthread_create ret = pthread_create (&dbg->ex->thread, NULL, xnu_exception_thread, dbg); if (ret) { perror ("pthread_create"); return false; } return true; }
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 }
mach_port_t get_task(pid_t infoPid) { kern_return_t kret; mach_port_t task; DEBUG_PRINT("[+getstate] Trying pid %d\n", infoPid); kret = task_for_pid(current_task(), infoPid, &task); RETURN_ON_MACH_ERROR("[-get_state] task_for_pid failed", kret); return task; }
int persistent_break(exc_msg_t *exc) { interface *face; x86_thread_state64_t *break_state; kern_return_t kret; break_state = get_state(exc->thread); face = find_interface(exc->task); DEBUG_PRINT("[+presistent_break] single_step %lx\n", face->single_step->address); add_breakpoint(exc->task, face->single_step->address, PERSISTENT, face->single_step->handler); free(face->single_step); face->single_step = NULL; break_state->__rflags &= ~0x100; kret = thread_set_state(exc->thread, x86_THREAD_STATE64, (thread_state_t)break_state, x86_THREAD_STATE64_COUNT); RETURN_ON_MACH_ERROR("[-persistent_break] failed setting thread state", kret); return 1; }
int handle_break(exc_msg_t *exc) { interface* face; int ret, our_break; kern_return_t kret; x86_thread_state64_t *break_state; break_state = get_state(exc->thread); our_break = find_break(exc->task, break_state->__rip-1); //find_break returns -1 aka big unsigned int if(our_break == -1) { return -1; } face = find_interface(exc->task); face->breaks[our_break]->hit++; breakpoint_struct *actual = face->breaks[our_break]; if(actual->flags == ONE_TIME) { remove_breakpoint(exc->task, actual->address); //restore original byte break_state->__rip = actual->address; //Restore original rip -- 1 byte back } else if(actual->flags == PERSISTENT) { face->single_step = actual; break_state->__rflags |= 0x100; //TRAP FLAG remove_breakpoint(exc->task, actual->address); break_state->__rip = actual->address; } kret = thread_set_state(exc->thread, x86_THREAD_STATE64, (thread_state_t)break_state, x86_THREAD_STATE64_COUNT); RETURN_ON_MACH_ERROR("[-handle_break] failed setting thread state", kret); if(actual->handler) { ret = actual->handler(exc); if(ret == -1) { return -1; } } return 1; }
//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; }