See More

using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using DebugNET; // Avoid conflict with .Net Debugger class in System.Diagnostics using Debugger = DebugNET.Debugger; namespace DebugNETExample { public static class Program { struct Vector { public int x, y; } public const string name = "DebugeeProgram"; ///

/// The main entry point for the application. /// This method demonstrates the usage of the debugger. /// [STAThread] public static void Main() { // Get process instance. Process[] processes = Process.GetProcessesByName(name); Process process = processes.Length > 0 ? processes[0] : null; // Start the process when not running. if (process == null) process = Process.Start($"{ name }.exe"); // Attaching too fast after starting results in an exception. Thread.Sleep(1000); try { // Using statement to take care of disposing the debugger. using (Debugger debugger = new Debugger(process)) { // Register events. debugger.Attached += (sender, e) => Console.WriteLine($"Attached to { e.Process.ProcessName }!"); debugger.Detached += (sender, e) => Console.WriteLine($"Detached from { e.Process.ProcessName }!"); debugger.ProcessExited += (sender, e) => Console.WriteLine($"{ e.Process.ProcessName } exited!"); // Retrieve address by code. IntPtr codeAddress = debugger.Seek($"{ name }.exe", 0x89, 0x45, 0xD0); // Retrieve address by module-offset pair. IntPtr address = debugger.GetAddress($"\"{ name }.exe\"+13648"); // Resolving pointers should be wrapped in a try catch block. try { IntPtr invalidAddress = debugger.GetAddress(IntPtr.Zero); } catch (AccessViolationException) { // Cannot read pointer. } catch (ArgumentException) { // Provided an invalid pointer. } // Allocate memory in the process with rwx access. IntPtr memory = debugger.AllocateMemory(); debugger.WriteByte(memory, 0xFF); debugger.FreeMemory(memory); // Loading a library into the process. IntPtr handle = debugger.InjectLibrary("inject.dll"); IntPtr parameter = debugger.AllocateMemory(); debugger.WriteUInt32(parameter, 22); debugger.WriteUInt32(parameter + 4, 20); // Executing remote functions. See inject project to see how the functions have to be defined for this to work. Task echoTask = debugger.ExecuteRemoteFunctionAsync("inject.dll", handle, "echo", 12345678); Task fibonacciTask = debugger.ExecuteRemoteFunctionAsync("inject.dll", handle, "fibonacci", 10); Task addTask = debugger.ExecuteRemoteFunctionAsync("inject.dll", handle, "add", parameter); IntPtr paramMemory = debugger.AllocateMemory(); debugger.WriteStruct(paramMemory, new Vector() { x = 2, y = 3 }); Task plus2Task = debugger.ExecuteRemoteFunctionAsync("inject.dll", handle, "plus2", paramMemory); plus2Task.ContinueWith(t => { Vector v = debugger.ReadStruct((IntPtr)t.Result); Console.WriteLine(v.x + " -- " + v.y); }); // Tell the tasks what to do when they are done void printTaskResult(Task t) => Console.WriteLine($"Function executed: { t.Result }"); echoTask.ContinueWith(printTaskResult); fibonacciTask.ContinueWith(printTaskResult); addTask.ContinueWith(printTaskResult); // When all functions ran, release the library and the allocated memory Task.WhenAll(echoTask, fibonacciTask, addTask).ContinueWith(t => { debugger.FreeMemory(parameter); debugger.FreeRemoteLibrary(handle); }); try { /* Listening can be wrapped in a DebugListener class with the following members: private CancellationTokenSource TokenSource; private DebugListener() public Task GetListener() public void Cancel() ~DebugListener() */ // Using statement around a CancellationTokenSource using (CancellationTokenSource tokenSource = new CancellationTokenSource()) { // Attaching to the process. Task listener = debugger.AttachAsync(tokenSource.Token); // Preferred way to create a breakpoint. debugger.Breakpoints.Add(codeAddress, (sender, e) => Console.WriteLine(e.Context.Eax), e => e.Context.Eax < 200); // Stopping the debugger. tokenSource.CancelAfter(3000); listener.Wait(); } } catch (AggregateException ex) { foreach (Exception exception in ex.InnerExceptions) { if (exception is AttachException) { // Cannot attach to process. Console.WriteLine(exception.Message); } else throw exception; } } } // We can also inherit from the debugger. // This way we can hardcode the process name and wrap many methods to simplify the whole thing. using (var programDebugger = new DebugeeProgramDebugger()) { programDebugger.OutputNumber += (sender, e) => Console.WriteLine(e); programDebugger.Listen(); // Equals to: /* IntPtr address = programDebugger.Seek("DebugeeProgram.exe", 0x89, 0x45, 0xD0); programDebugger.Breakpoints.Add(address, (sender, eventArgs) => { Console.WriteLine(eventArgs.Context.Eax); }); programDebugger.Listen(); programDebugger.Breakpoints[address].Enable(programDebugger, address); */ // Do work here. programDebugger.DetachAfter(2500); programDebugger.Listener.Wait(); } Console.WriteLine("Done."); Console.ReadKey(); } catch (ProcessNotFoundException ex) { Console.WriteLine($"Process cannot be found. { ex.InnerException.Message }"); Console.ReadKey(); } } } public class DebugeeProgramDebugger : Debugger { public event EventHandler OutputNumber; public Task Listener { get; private set; } public CancellationTokenSource TokenSource { get; private set; } public DebugeeProgramDebugger() : base("DebugeeProgram") {} public void Listen() { if (!IsAttached) { TokenSource = new CancellationTokenSource(); Listener = AttachAsync(TokenSource.Token); } } public void Detach() => TokenSource.Cancel(); public void DetachAfter(int ms) => TokenSource.CancelAfter(ms); protected override void OnAttached(AttachedEventArgs e) { IntPtr address = Seek("DebugeeProgram.exe", 0x89, 0x45, 0xD0); Breakpoints.Add(address, (sender, eventArgs) => { OutputNumber?.Invoke(this, (int)eventArgs.Context.Eax); }); } } }