/* attach a process to a debugger thread and suspend it */ static int debugger_attach( struct process *process, struct thread *debugger ) { struct thread *thread; if (process->debugger) goto error; /* already being debugged */ if (!is_process_init_done( process )) goto error; /* still starting up */ if (list_empty( &process->thread_list )) goto error; /* no thread running in the process */ /* make sure we don't create a debugging loop */ for (thread = debugger; thread; thread = thread->process->debugger) if (thread->process == process) goto error; /* don't let a debugger debug its console... won't work */ if (debugger->process->console && debugger->process->console->renderer->process == process) goto error; suspend_process( process ); if (!set_process_debugger( process, debugger )) { resume_process( process ); return 0; } if (!set_process_debug_flag( process, 1 )) { process->debugger = NULL; resume_process( process ); return 0; } return 1; error: set_error( STATUS_ACCESS_DENIED ); return 0; }
/* * pipe_reader_read * * This function is used as the read handler for file handles of type * FH_PIPE_READER, and is invoked from the read system call. * * If there is data available in the pipe, then we can just return this to the * process and return immediately. An immedaite return is also possible if the pipe * is empty, but it is no longer being written to; this situation corresponds to * the end of file. * * If neither of these two situations arise, then we must block here, since the * system call can't actually complete until either some more data becomes * available, or the pipe is closed for writing. In this case, we suspend the * current process. When the pipe is later written to or closed for writing, the * wake_up_reader function will resume the process, causing this function to be * called again. The second time round, either one of the first two cases will * match, and the system call can then return. */ static ssize_t pipe_reader_read(filehandle * fh, void *buf, size_t count) { /* * Don't allow multiple processes to read from the same pipe at the same * time */ if (-1 != fh->p->readpid) return -EBADF; if (0 < fh->p->len) { /* * Data available - can return immediately */ int copy = (count < fh->p->len) ? count : fh->p->len; memmove(buf, fh->p->data, copy); if (fh->p->len > copy) memmove(&fh->p->data[0], &fh->p->data[copy], fh->p->len - copy); fh->p->len -= copy; return copy; } else if (!fh->p->writing) { /* * Pipe has been closed for writing - return end-of-file indicator */ return 0; } else { /* * No more data available yet - suspend process */ fh->p->readpid = current_process->pid; suspend_process(current_process); return -ESUSPEND; } }
/* attach a process to a debugger thread and suspend it */ static int debugger_attach( struct process *process, struct thread *debugger ) { if (process->debugger) goto error; /* already being debugged */ if (debugger->process == process) goto error; if (!is_process_init_done( process )) goto error; /* still starting up */ if (list_empty( &process->thread_list )) goto error; /* no thread running in the process */ /* don't let a debugger debug its console... won't work */ if (debugger->process->console) { struct thread *renderer = console_get_renderer(debugger->process->console); if (renderer && renderer->process == process) goto error; } suspend_process( process ); if (!set_process_debugger( process, debugger )) { resume_process( process ); return 0; } if (!set_process_debug_flag( process, 1 )) { process->debugger = NULL; resume_process( process ); return 0; } return 1; error: set_error( STATUS_ACCESS_DENIED ); return 0; }
/* * syscall_waitpid * * Wait for a process to complete. The specified process id must be a child of the * current process. If it has already finished, then the exit code will be passed * back in the supplied status parameter. Otherwise, the current process will block * until the child process finally completes. * * The logic for handling the resumption of a process that is blocked on this call * is implemented in kill_process. */ pid_t syscall_waitpid(pid_t pid, int *status, int options) { if ((0 > pid) || (MAX_PROCESSES <= pid)) return -ECHILD; current_process->waiting_on = -1; /* * Check that the child exists and is in fact a child of this process */ process *child = &processes[pid]; if (!child->exists || (child->parent_pid != current_process->pid)) return -ECHILD; if (child->exited) { /* * Child has already finished executing; just return its exit code, and * release the slot in the process table */ if (NULL != status) *status = child->exit_status; child->exists = 0; return pid; } else { /* * Child is still running... block the calling process */ current_process->waiting_on = pid; suspend_process(current_process); return -ESUSPEND; } }
/* generate a debug event from inside the server and queue it */ void generate_debug_event( struct thread *thread, int code, void *arg ) { if (thread->process->debugger) { struct debug_event *event = alloc_debug_event( thread, code, arg, NULL ); if (event) { link_event( event ); suspend_process( thread->process ); release_object( event ); } } }
/* generate a debug event from inside the server and queue it */ void generate_debug_event( struct thread *thread, int code, const void *arg ) { if (thread->process->debugger) { struct debug_event *event = alloc_debug_event( thread, code, arg ); if (event) { link_event( event ); suspend_process( thread->process ); release_object( event ); } clear_error(); /* ignore errors */ } }
/* detach a process from a debugger thread (and resume it ?) */ int debugger_detach( struct process *process, struct thread *debugger ) { struct debug_event *event; struct debug_ctx *debug_ctx; if (!process->debugger || process->debugger != debugger) goto error; /* not currently debugged, or debugged by another debugger */ if (!debugger->debug_ctx ) goto error; /* should be a debugger */ /* init should be done, otherwise wouldn't be attached */ assert(is_process_init_done(process)); suspend_process( process ); /* send continue indication for all events */ debug_ctx = debugger->debug_ctx; /* find the event in the queue * FIXME: could loop on process' threads and look the debug_event field */ LIST_FOR_EACH_ENTRY( event, &debug_ctx->event_queue, struct debug_event, entry ) { if (event->state != EVENT_QUEUED) continue; if (event->sender->process == process) { assert( event->sender->debug_event == event ); event->status = DBG_CONTINUE; event->state = EVENT_CONTINUED; wake_up( &event->obj, 0 ); unlink_event( debug_ctx, event ); /* from queued debug event */ resume_process( process ); break; } } /* remove relationships between process and its debugger */ process->debugger = NULL; if (!set_process_debug_flag( process, 0 )) clear_error(); /* ignore error */ /* from this function */ resume_process( process ); return 0; error: set_error( STATUS_ACCESS_DENIED ); return 0; }
//-------------------------------------------------------------------------- static int idaapi callback(void * /*user_data*/, int notification_code, va_list va) { switch ( notification_code ) { case dbg_process_start: // reset instruction counter g_nb_insn = 0; break; case dbg_run_to: msg("tracer: entrypoint reached\n"); enable_insn_trace(true); // while continue_process() would work here too, request+run is more universal // because they do not ignore the request queue request_continue_process(); run_requests(); break; // A step occured (one instruction was executed). This event // notification is only generated if step tracing is enabled. case dbg_trace: { /*thid_t tid =*/ va_arg(va, thid_t); ea_t ip = va_arg(va, ea_t); msg("[%d] tracing over: %a\n", g_nb_insn, ip); if ( g_nb_insn == g_max_insn ) { // stop the trace mode and suspend the process disable_step_trace(); suspend_process(); msg("process suspended (traced %d instructions)\n", g_max_insn); } else { g_nb_insn++; } } break; case dbg_process_exit: unhook_from_notification_point(HT_DBG, callback, NULL); break; } return 0; }
//-------------------------------------------------------------------------- static int idaapi callback( void * /*user_data*/, int notification_code, va_list va) { static int stage = 0; static bool is_dll; static char needed_file[QMAXPATH]; switch ( notification_code ) { case dbg_process_start: case dbg_process_attach: get_input_file_path(needed_file, sizeof(needed_file)); // no break case dbg_library_load: if ( stage == 0 ) { const debug_event_t *pev = va_arg(va, const debug_event_t *); if ( !strieq(pev->modinfo.name, needed_file) ) break; if ( notification_code == dbg_library_load ) is_dll = true; // remember the current module bounds if ( pev->modinfo.rebase_to != BADADDR ) curmod.startEA = pev->modinfo.rebase_to; else curmod.startEA = pev->modinfo.base; curmod.endEA = curmod.startEA + pev->modinfo.size; deb(IDA_DEBUG_PLUGIN, "UUNP: module space %a-%a\n", curmod.startEA, curmod.endEA); ++stage; } break; case dbg_library_unload: if ( stage != 0 && is_dll ) { const debug_event_t *pev = va_arg(va, const debug_event_t *); if ( curmod.startEA == pev->modinfo.base || curmod.startEA == pev->modinfo.rebase_to ) { deb(IDA_DEBUG_PLUGIN, "UUNP: unload unpacked module\n"); if ( stage > 2 ) enable_step_trace(false); stage = 0; curmod.startEA = 0; curmod.endEA = 0; _hide_wait_box(); } } break; case dbg_run_to: // Parameters: const debug_event_t *event dbg->stopped_at_debug_event(true); bp_gpa = get_name_ea(BADADDR, "kernel32_GetProcAddress"); #ifndef __X64__ if( (LONG)GetVersion() < 0 ) // win9x mode -- use thunk's { is_9x = true; win9x_resolve_gpa_thunk(); } #endif if ( bp_gpa == BADADDR ) { bring_debugger_to_front(); warning("Sorry, could not find kernel32.GetProcAddress"); FORCE_STOP: stage = 4; // last stage clear_requests_queue(); request_exit_process(); run_requests(); break; } else if( !my_add_bpt(bp_gpa) ) { bring_debugger_to_front(); warning("Sorry, can not set bpt to kernel32.GetProcAddress"); goto FORCE_STOP; } else { ++stage; set_wait_box("Waiting for a call to GetProcAddress()"); } continue_process(); break; case dbg_bpt: // A user defined breakpoint was reached. // Parameters: thid_t tid // ea_t breakpoint_ea // int *warn = -1 // Return (in *warn): // -1 - to display a breakpoint warning dialog // if the process is suspended. // 0 - to never display a breakpoint warning dialog. // 1 - to always display a breakpoint warning dialog. { thid_t tid = va_arg(va, thid_t); qnotused(tid); ea_t ea = va_arg(va, ea_t); //int *warn = va_arg(va, int*); if ( stage == 2 ) { if ( ea == bp_gpa ) { regval_t rv; if ( get_reg_val(REGNAME_ESP, &rv) ) { ea_t esp = ea_t(rv.ival); invalidate_dbgmem_contents(esp, 1024); ea_t gpa_caller = getPtr(esp); if ( !is_library_entry(gpa_caller) ) { ea_t nameaddr; if ( ptrSz == 4 ) { nameaddr = get_long(esp+8); } else { get_reg_val(REGNAME_ECX, &rv); nameaddr = ea_t(rv.ival); } invalidate_dbgmem_contents(nameaddr, 1024); char name[MAXSTR]; size_t len = get_max_ascii_length(nameaddr, ASCSTR_C, ALOPT_IGNHEADS); name[0] = '\0'; get_ascii_contents2(nameaddr, len, ASCSTR_C, name, sizeof(name)); if ( !ignore_win32_api(name) ) { deb(IDA_DEBUG_PLUGIN, "%a: found a call to GetProcAddress(%s)\n", gpa_caller, name); if ( !my_del_bpt(bp_gpa) || !my_add_bpt(gpa_caller) ) error("Can not modify breakpoint"); } } } } else if ( ea == bpt_ea ) { my_del_bpt(ea); if ( !is_library_entry(ea) ) { msg("Uunp: reached unpacker code at %a, switching to trace mode\n", ea); enable_step_trace(true); ++stage; uint64 eax; if ( get_reg_val(REGNAME_EAX, &eax) ) an_imported_func = ea_t(eax); set_wait_box("Waiting for the unpacker to finish"); } else { warning("%a: bpt in library code", ea); // how can it be? my_add_bpt(bp_gpa); } } // not our bpt? skip it else { // hide the wait box to allow others plugins to properly stop _hide_wait_box(); break; } } } // while continue_process() would work here too, request+run is more universal // because they do not ignore the request queue request_continue_process(); run_requests(); break; case dbg_trace: // A step occured (one instruction was executed). This event // notification is only generated if step tracing is enabled. // Parameter: none if ( stage == 3 ) { thid_t tid = va_arg(va, thid_t); qnotused(tid); ea_t ip = va_arg(va, ea_t); // ip reached the OEP range? if ( oep_area.contains(ip) ) { // stop the trace mode enable_step_trace(false); msg("Uunp: reached OEP %a\n", ip); set_wait_box("Reanalyzing the unpacked code"); // reanalyze the unpacked code do_unknown_range(oep_area.startEA, oep_area.size(), DOUNK_EXPAND); auto_make_code(ip); // plan to make code noUsed(oep_area.startEA, oep_area.endEA); // plan to reanalyze auto_mark_range(oep_area.startEA, oep_area.endEA, AU_FINAL); // plan to analyze move_entry(ip); // mark the program's entry point _hide_wait_box(); // inform the user bring_debugger_to_front(); if ( askyn_c(1, "HIDECANCEL\n" "The universal unpacker has finished its work.\n" "Do you want to take a memory snapshot and stop now?\n" "(you can do it yourself if you want)\n") > 0 ) { set_wait_box("Recreating the import table"); invalidate_dbgmem_config(); if ( is_9x ) find_thunked_imports(); create_impdir(); set_wait_box("Storing resources to 'resource.res'"); if ( resfile[0] != '\0' ) extract_resource(resfile); _hide_wait_box(); if ( take_memory_snapshot(true) ) goto FORCE_STOP; } suspend_process(); unhook_from_notification_point(HT_DBG, callback, NULL); } } break; case dbg_process_exit: { stage = 0; // stop the tracing _hide_wait_box(); unhook_from_notification_point(HT_DBG, callback, NULL); if ( success ) jumpto(inf.beginEA, -1); else tell_about_failure(); } break; case dbg_exception:// Parameters: const debug_event_t *event // int *warn = -1 // Return (in *warn): // -1 - to display an exception warning dialog // if the process is suspended. // 0 - to never display an exception warning dialog. // 1 - to always display an exception warning dialog. { // const debug_event_t *event = va_arg(va, const debug_event_t *); // int *warn = va_arg(va, int *); // FIXME: handle code which uses SEH to unpack itself if ( askyn_c(1, "AUTOHIDE DATABASE\n" "HIDECANCEL\n" "An exception occurred in the program.\n" "UUNP does not support exceptions yet.\n" "The execution has been suspended.\n" "Do you want to continue the unpacking?") <= 0 ) { _hide_wait_box(); stage = 0; enable_step_trace(false); // stop the trace mode suspend_process(); } else { continue_process(); } } break; case dbg_request_error: // An error occured during the processing of a request. // Parameters: ui_notification_t failed_command // dbg_notification_t failed_dbg_notification { ui_notification_t failed_cmd = va_arg(va, ui_notification_t); dbg_notification_t failed_dbg_notification = va_arg(va, dbg_notification_t); _hide_wait_box(); stage = 0; warning("dbg request error: command: %d notification: %d", failed_cmd, failed_dbg_notification); } break; } return 0; }