/// <summary> /// This is the main debug loop, which is called as a single thread that attaches to the target process, using /// debugging privileges. /// </summary> private void DebugLoop() { WinApi.DEBUG_EVENT de = new WinApi.DEBUG_EVENT(); WinApi.DbgCode continueStatus = WinApi.DbgCode.CONTINUE; bool isFirstInstBreakpointSet = false; while (this.Proc.HasExited == false && this.allowedToDebug == true) { if (!isFirstInstBreakpointSet && this.pauseOnFirstInst) { try { // Attempt to set a breakpoint at the main module's entry point. this.SetSoftBP(this.Proc.MainModule.EntryPointAddress); isFirstInstBreakpointSet = true; } catch (NullReferenceException) { // Let the .NET Framework calm down and then continue once its feathers are unruffled. continue; } catch (System.ComponentModel.Win32Exception e) { if (e.NativeErrorCode == 299) { // Catch and ignore the partial ReadProcessMemory/WriteProcessMemory exception. } else { // Otherwise, log the exception message. this.Status.Log("Unhandled win32 exception: " + e.Message, Logger.Level.HIGH); } } } if (!WinApi.WaitForDebugEvent(ref de, this.DebugEventTimeout)) { continue; } this.IsBusy = true; bool pauseDebugger = false; bool isFirstBreakpointPass = false; switch (de.dwDebugEventCode) { case (uint)WinApi.DebugEventType.EXCEPTION_DEBUG_EVENT: // Only ignore first chance exceptions after the first breakpoint has been hit. if (this.InitialBreakpointHit) { if (this.IgnoreFirstChanceExceptions && de.Exception.dwFirstChance != 0) { continueStatus = WinApi.DbgCode.EXCEPTION_NOT_HANDLED; break; } } switch (de.Exception.ExceptionRecord.ExceptionCode) { case (uint)WinApi.ExceptionType.STATUS_WX86_SINGLE_STEP: case (uint)WinApi.ExceptionType.SINGLE_STEP: continueStatus = this.OnSingleStepDebugException(ref de); if (this.Settings.PauseOnSingleStep || this.stepOverOperationInProgress) { pauseDebugger = true; this.stepOverOperationInProgress = false; } break; case (uint)WinApi.ExceptionType.ACCESS_VIOLATION: continueStatus = this.OnAccessViolationDebugException(ref de); if (this.Settings.PauseOnAccessViolation) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.ARRAY_BOUNDS_EXCEEDED: continueStatus = this.OnArrayBoundsExceededDebugException(ref de); if (this.Settings.PauseOnArrayBoundsExceeded) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.STATUS_WX86_BREAKPOINT: case (uint)WinApi.ExceptionType.BREAKPOINT: bool initialBreakpointStart = this.InitialBreakpointHit; continueStatus = this.OnBreakpointDebugException(ref de); bool initialBreakpointEnd = this.InitialBreakpointHit; if (initialBreakpointStart != initialBreakpointEnd) { isFirstBreakpointPass = true; } if (this.Settings.PauseOnBreakpoint) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.DATATYPE_MISALIGNMENT: continueStatus = this.OnDatatypeMisalignmentDebugException(ref de); if (this.Settings.PauseOnDatatypeMisalignment) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.FLT_DENORMAL_OPERAND: continueStatus = this.OnFltDenormalOperandDebugException(ref de); if (this.Settings.PauseOnFltDenormalOperand) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.FLT_DIVIDE_BY_ZERO: continueStatus = this.OnFltDivideByZeroDebugException(ref de); if (this.Settings.PauseOnFltDivideByZero) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.FLT_INEXACT_RESULT: continueStatus = this.OnFltInexactResultDebugException(ref de); if (this.Settings.PauseOnFltInexactResult) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.FLT_INVALID_OPERATION: continueStatus = this.OnFltInvalidOperationDebugException(ref de); if (this.Settings.PauseOnFltInvalidOperation) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.FLT_OVERFLOW: continueStatus = this.OnFltOverflowDebugException(ref de); if (this.Settings.PauseOnFltOverflow) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.FLT_STACK_CHECK: continueStatus = this.OnFltStackCheckDebugException(ref de); if (this.Settings.PauseOnFltStackCheck) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.FLT_UNDERFLOW: continueStatus = this.OnFltUnderflowDebugException(ref de); if (this.Settings.PauseOnFltUnderflow) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.GUARD_PAGE: continueStatus = this.OnGuardPageDebugException(ref de); if (this.Settings.PauseOnGuardPage) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.ILLEGAL_INSTRUCTION: continueStatus = this.OnIllegalInstructionDebugException(ref de); if (this.Settings.PauseOnIllegalInstruction) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.IN_PAGE_ERROR: continueStatus = this.OnInPageErrorDebugException(ref de); if (this.Settings.PauseOnInPageError) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.INT_DIVIDE_BY_ZERO: continueStatus = this.OnIntDivideByZeroDebugException(ref de); if (this.Settings.PauseOnIntDivideByZero) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.INT_OVERFLOW: continueStatus = this.OnIntOverflowDebugException(ref de); if (this.Settings.PauseOnIntOVerflow) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.INVALID_DISPOSITION: continueStatus = this.OnInvalidDispositionDebugException(ref de); if (this.Settings.PauseOnInvalidDisposition) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.NONCONTINUABLE_EXCEPTION: continueStatus = this.OnNoncontinuableExceptionDebugException(ref de); if (this.Settings.PauseOnNoncontinuableException) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.PRIV_INSTRUCTION: continueStatus = this.OnPrivInstructionDebugException(ref de); if (this.Settings.PauseOnPrivInstruction) { pauseDebugger = true; } break; case (uint)WinApi.ExceptionType.STACK_OVERFLOW: continueStatus = this.OnStackOverflowDebugException(ref de); if (this.Settings.PauseOnStackOverflow) { pauseDebugger = true; } break; default: continueStatus = this.OnUnhandledDebugException(ref de); if (this.Settings.PauseOnUnhandledDebugException) { pauseDebugger = true; } break; } // Only break on second exceptions once the first breakpoint has been encountered. if (this.PauseOnSecondChanceException && !isFirstBreakpointPass) { pauseDebugger = true; } break; case (uint)WinApi.DebugEventType.CREATE_THREAD_DEBUG_EVENT: continueStatus = this.OnCreateThreadDebugEvent(ref de); break; case (uint)WinApi.DebugEventType.CREATE_PROCESS_DEBUG_EVENT: this.debugThreadInitSuccess = true; this.debugThreadInitComplete = true; continueStatus = this.OnCreateProcessDebugEvent(ref de); break; case (uint)WinApi.DebugEventType.EXIT_THREAD_DEBUG_EVENT: if (this.stepOverOperationInProgress) { this.stepOverOperationInProgress = false; } continueStatus = this.OnExitThreadDebugEvent(ref de); break; case (uint)WinApi.DebugEventType.EXIT_PROCESS_DEBUG_EVENT: continueStatus = this.OnExitProcessDebugEvent(ref de); break; case (uint)WinApi.DebugEventType.LOAD_DLL_DEBUG_EVENT: continueStatus = this.OnLoadDllDebugEvent(ref de); break; case (uint)WinApi.DebugEventType.UNLOAD_DLL_DEBUG_EVENT: continueStatus = this.OnUnloadDllDebugEvent(ref de); break; case (uint)WinApi.DebugEventType.OUTPUT_DEBUG_STRING_EVENT: continueStatus = this.OnOutputDebugStringEvent(ref de); break; case (uint)WinApi.DebugEventType.RIP_EVENT: continueStatus = this.OnRipEvent(ref de); break; default: continueStatus = WinApi.DbgCode.EXCEPTION_NOT_HANDLED; break; } if (pauseDebugger) { // Reset the target state information. this.ts.Reset(); // Get the thread context. IntPtr threadHandle; WinApi.CONTEXT context = new WinApi.CONTEXT(); this.BeginEditThread(de.dwThreadId, out threadHandle, out context); // Save the thread state information. this.ts.ThreadId = de.dwThreadId; this.ts.ThreadHandle = threadHandle; this.ts.Context = context; this.ts.IsReady = true; // Pause the debugger and target. this.pauseDebuggerLock.Reset(); this.IsDebuggingPaused = true; this.IsBusy = false; this.pauseDebuggerLock.WaitOne(); // Save any changes that have been made to the thread context. threadHandle = this.ts.ThreadHandle; context = this.ts.Context; this.EndEditThread(this.ts.ThreadId, ref threadHandle, ref context); // Flag the debugger as unpaused. this.IsDebuggingPaused = false; // Delete any target state information. this.ts.Reset(); } this.IsBusy = false; WinApi.ContinueDebugEvent(de.dwProcessId, de.dwThreadId, continueStatus); } this.debugThreadInitComplete = true; if (!WinApi.DebugActiveProcessStop((uint)this.PID)) { this.Status.Log("Failed to stop debugging. Error: " + Marshal.GetLastWin32Error()); } this.threadMayExit = true; return; }
/// <summary> /// Starts the main debug loop, optionally creating a process from the supplied file path. /// </summary> /// <param name="arguments">An optional set of arguments that dictate how an executable is debugged.</param> private void StartDebugLoop(object arguments = null) { string filePath; string parameters; if (arguments == null) { filePath = string.Empty; parameters = string.Empty; } else if (arguments is DebugLoopArguments) { DebugLoopArguments dla = (DebugLoopArguments)arguments; filePath = dla.FilePath; parameters = dla.Parameters; } else { filePath = string.Empty; parameters = string.Empty; } if (arguments == null) { if (!this.IsOpen) { return; } if (!WinApi.DebugActiveProcess((uint)this.PID)) { #if DEBUG this.Status.Log("Cannot debug. Error: " + Marshal.GetLastWin32Error(), Logger.Level.HIGH); #endif return; } else if (!WinApi.DebugSetProcessKillOnExit(false)) { #if DEBUG this.Status.Log("Cannot exit cleanly in the future.", Logger.Level.MEDIUM); #endif } else { this.allowedToDebug = true; this.threadMayExit = false; } WinApi.ThreadAccess thread_rights = WinApi.ThreadAccess.SET_CONTEXT | WinApi.ThreadAccess.GET_CONTEXT | WinApi.ThreadAccess.SUSPEND_RESUME; IntPtr threadHandle = WinApi.OpenThread(thread_rights, false, (uint)this.ThreadID); WinApi.CONTEXT cx = new WinApi.CONTEXT(); cx.ContextFlags = WinApi.CONTEXT_FLAGS.DEBUG_REGISTERS; WinApi.GetThreadContext(threadHandle, ref cx); cx.ContextFlags = WinApi.CONTEXT_FLAGS.DEBUG_REGISTERS; cx.Dr7 = (uint)(Debugger.DRegSettings.reg0w | Debugger.DRegSettings.reg0len4 | Debugger.DRegSettings.reg0set); bool stc = WinApi.SetThreadContext(threadHandle, ref cx); WinApi.CloseHandle(threadHandle); this.SetDebuggerArchitecture(); this.DebugLoop(); return; } else { if (!SysInteractor.IsInitialized) { SysInteractor.Init(); } bool res = false; string application = filePath; string commandLine = string.Empty; if (!string.IsNullOrEmpty(parameters)) { commandLine = '"' + filePath + "\" " + parameters; } WinApi.PROCESS_INFORMATION procInfo = new WinApi.PROCESS_INFORMATION(); WinApi.STARTUPINFO startupInfo = new WinApi.STARTUPINFO(); WinApi.SECURITY_ATTRIBUTES processSecurity = new WinApi.SECURITY_ATTRIBUTES(); WinApi.SECURITY_ATTRIBUTES threadSecurity = new WinApi.SECURITY_ATTRIBUTES(); processSecurity.nLength = Marshal.SizeOf(processSecurity); threadSecurity.nLength = Marshal.SizeOf(threadSecurity); // Open the process. res = WinApi.CreateProcess( application, commandLine, ref processSecurity, ref threadSecurity, false, (uint)WinApi.ProcessCreationFlags.DEBUG_PROCESS, IntPtr.Zero, null, ref startupInfo, out procInfo); if (!res) { return; } this.ProcHandle = procInfo.hProcess; if (this.ProcHandle != null) { try { this.Proc = System.Diagnostics.Process.GetProcessById(procInfo.dwProcessId); } catch (ArgumentException) { WinApi.CloseHandle(this.ProcHandle); return; } if (!WinApi.DebugSetProcessKillOnExit(false)) { #if DEBUG this.Status.Log("Cannot exit cleanly in the future.", Logger.Level.MEDIUM); #endif } else { this.allowedToDebug = true; this.threadMayExit = false; } this.SetDebuggerArchitecture(); this.DebugLoop(); return; } this.Status.Log("Unable to open the target process.", Logger.Level.HIGH); } }
/// <summary> /// Suspend the thread and prepare the thread context to be modified. /// </summary> /// <param name="threadId">The ID of the thread to be modified.</param> /// <param name="threadHandle">A handle of the thread to be modified.</param> /// <param name="cx">The context of the thread to be modified.</param> protected void BeginEditThread(uint threadId, out IntPtr threadHandle, out WinApi.CONTEXT cx) { WinApi.ThreadAccess threadRights = WinApi.ThreadAccess.SET_CONTEXT | WinApi.ThreadAccess.GET_CONTEXT | WinApi.ThreadAccess.SUSPEND_RESUME; threadHandle = WinApi.OpenThread(threadRights, false, threadId); if (threadHandle == null || threadHandle.Equals(IntPtr.Zero)) { string msg = "Unable to obtain a thread handle for TID: " + threadId + ". Error: " + Marshal.GetLastWin32Error(); WinApi.CloseHandle(threadHandle); throw new InvalidOperationException(msg); } uint result = WinApi.SuspendThread(threadHandle); unchecked { if (result == (uint)(-1)) { string msg = "Unable to suspend thread, TID: " + threadId + ". Error: " + Marshal.GetLastWin32Error(); WinApi.CloseHandle(threadHandle); throw new InvalidOperationException(msg); } } WinApi.CONTEXT context = new WinApi.CONTEXT(); context.ContextFlags = WinApi.CONTEXT_FLAGS.FULL; if (!WinApi.GetThreadContext(threadHandle, ref context)) { string msg = "Unable to get thread context, TID: " + threadId + ". Error: " + Marshal.GetLastWin32Error(); WinApi.CloseHandle(threadHandle); throw new InvalidOperationException(msg); } cx = context; }