public List<ThreadData> GetContexts() { // Attach to the process we provided the thread as an argument #if RestartEachTime #if UseDebugger if (!Win32Imports.DebugActiveProcess(CurrentProcess.Id)) { throw new Win32Exception(); } if (!Win32Imports.DebugSetProcessKillOnExit(false)) { throw new Win32Exception(); } #endif #endif var retval = new List<ThreadData>(); //CONTEXT_X64 context = new CONTEXT_X64(); Win32Imports.ContextX64 context = new Win32Imports.ContextX64(); foreach (ProcessThread thread in CurrentProcess.Threads) { uint iThreadId = (uint)thread.Id; getRip(iThreadId, ref context, GetRipAction.ActionGetContext); //Console.WriteLine($"Rip: {Rip}"); retval.Add(new ThreadData() { ThreadId = iThreadId, Rip = context.Rip }); //Console.WriteLine($"thread id: {iThreadId}"); //Console.WriteLine($"RIP: {context.Rip}"); } #if RestartEachTime #if UseDebugger if (!Win32Imports.DebugActiveProcessStop(CurrentProcess.Id)) { throw new Win32Exception(); } #endif #endif return retval; }
public void Stop() { LoggerInstance.WriteLine("stopping"); foreach (var address in BreakPoints.Keys) { UninstallBreakPoint(address); } #if RestartEachTime foreach (ProcessThread thread in CurrentProcess.Threads) { uint iThreadId = (uint)thread.Id; setTrace(iThreadId, true, true); } for (var i = 0; i < 10; i++) { ClearEvents(); Thread.Sleep(100); } if (!Win32Imports.DebugActiveProcessStop(CurrentProcess.Id)) { throw new Win32Exception(); } }
public static TraceState InstallTracer(Process process, ulong patchSite, Logger logger, byte[] asmSizes, ImportResolver ir) { var processHandle = Win32Imports.OpenProcess(Win32Imports.ProcessAccessFlags.All, false, process.Id); var traceState = new TraceState(); if ((ulong)processHandle == 0) { logger.WriteLine("could not open process"); return(null); } const int patchSiteSize = 24; const int codeSize = 1024; const int traceLogSize = 1024; var originalCode = DebugProcessUtils.ReadBytes(process, (ulong)process.MainModule.BaseAddress, process.MainModule.ModuleMemorySize); var originalCodeAddress = Win32Imports.VirtualAllocEx( processHandle, new IntPtr(0), new IntPtr(originalCode.Length), Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve, Win32Imports.MemoryProtection.ReadWrite); if ((ulong)originalCodeAddress == 0) { logger.WriteLine($"could not allocate memory for {nameof(originalCodeAddress)}"); return(null); } var traceLogAddress = Win32Imports.VirtualAllocEx( processHandle, new IntPtr(0), new IntPtr(traceLogSize), Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve, Win32Imports.MemoryProtection.ReadWrite); if ((ulong)traceLogAddress == 0) { logger.WriteLine($"could not allocate memory for {nameof(traceLogAddress)}"); return(null); } var injectedCodeAddress = Win32Imports.VirtualAllocEx( processHandle, new IntPtr(0), new IntPtr(codeSize), Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve, Win32Imports.MemoryProtection.ExecuteReadWrite); if ((ulong)injectedCodeAddress == 0) { logger.WriteLine($"could not allocate memory for {nameof(injectedCodeAddress)}"); return(null); } var asmSizesAddress = Win32Imports.VirtualAllocEx( processHandle, new IntPtr(0), new IntPtr(asmSizes.Length), Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve, Win32Imports.MemoryProtection.ReadWrite); if ((ulong)asmSizesAddress == 0) { logger.WriteLine($"could not allocate memory for {nameof(asmSizesAddress)}"); return(null); } traceState.CodeAddress = (ulong)injectedCodeAddress; traceState.TraceLogAddress = (ulong)traceLogAddress; int addrSize = Marshal.SizeOf(typeof(IntPtr)); var c1 = Assembler.CreateContext <Action>(); // save rax c1.Push(c1.Rax); // push return address c1.Lea(c1.Rax, Memory.QWord(CodeContext.Rip, -addrSize)); c1.Push(c1.Rax); // push "call" address c1.Mov(c1.Rax, (ulong)injectedCodeAddress); c1.Push(c1.Rax); // "call" codeAddress c1.Ret(); c1.Nop(); c1.Nop(); c1.Nop(); var patchSiteCodeJit = AssemblyUtil.GetAsmJitBytes(c1); Debug.Assert(patchSiteCodeJit.Length == patchSiteSize, "patch site size incorrect"); var c = Assembler.CreateContext <Action>(); c.Push(c.Rbp); c.Mov(c.Rbp, c.Rsp); c.Pushf(); c.Push(c.Rbx); c.Push(c.Rcx); c.Push(c.Rdx); // log thread id var getThreadContext = ir.LookupFunction("KERNEL32.DLL:GetCurrentThreadId"); //c.Call(getThreadContext); c.Lea(c.Rax, Memory.QWord(CodeContext.Rip, 13)); // skips next instructions c.Push(c.Rax); c.Mov(c.Rax, getThreadContext); c.Push(c.Rax); c.Ret(); c.Mov(c.Rbx, (ulong)traceLogAddress); c.Mov(Memory.Word(c.Rbx), c.Eax); // reserve space for instruction counter, the 01-08 is just so AsmJit doesn't shorten the instruction // we overwrite the value to 0 later on c.Mov(c.Rax, (ulong)0x0102030405060708); var smcLastRipJit = AssemblyUtil.GetAsmJitBytes(c).Length - addrSize; c.Lea(c1.Rax, Memory.QWord(CodeContext.Rip, -7 - addrSize)); c.Mov(c.Rcx, Memory.QWord(c.Rax)); c.Inc(c.Rcx); c.Xchg(Memory.QWord(c.Rax), c.Rcx); // find return address and store size of instruction at return address in rcx c.Mov(c.Rdx, Memory.QWord(c.Rbp, addrSize)); c.Mov(c.Rbx, c.Rdx); c.Mov(c.Rax, (ulong)process.MainModule.BaseAddress); c.Sub(c.Rdx, c.Rax); c.Mov(c.Rax, (ulong)asmSizesAddress); c.Add(c.Rdx, c.Rax); c.Movzx(c.Rcx, Memory.Byte(c.Rdx)); // RESTORE ORIGINAL CODE // get address of original code in Rdx c.Mov(c.Rdx, c.Rbx); c.Mov(c.Rax, (ulong)process.MainModule.BaseAddress); c.Sub(c.Rdx, c.Rax); // TODO: compare Rdx with process.MainModule.ModuleMemorySize - patchSiteSize and abort if greater c.Mov(c.Rax, (ulong)originalCodeAddress); c.Add(c.Rdx, c.Rax); // restore call site 1 c.Mov(c.Rax, Memory.QWord(c.Rdx)); c.Mov(Memory.QWord(c.Rbx), c.Rax); // restore call site 2 c.Mov(c.Rax, Memory.QWord(c.Rdx, addrSize)); c.Mov(Memory.QWord(c.Rbx, addrSize), c.Rax); // restore call site 3 c.Mov(c.Rax, Memory.QWord(c.Rdx, addrSize * 2)); c.Mov(Memory.QWord(c.Rbx, addrSize * 2), c.Rax); // CLEAN UP AND RETURN // put new patch in place // add instruction size to return address, so we write patch at next instruction c.Add(c.Rbx, c.Rcx); // restore patch site 1 c.Mov(c.Rax, BitConverter.ToUInt64(patchSiteCodeJit, 0)); c.Mov(Memory.QWord(c.Rbx), c.Rax); // restore patch site 2 c.Mov(c.Rax, BitConverter.ToUInt64(patchSiteCodeJit, addrSize)); c.Mov(Memory.QWord(c.Rbx, addrSize), c.Rax); // restore patch site 3 c.Mov(c.Rax, BitConverter.ToUInt64(patchSiteCodeJit, addrSize * 2)); c.Mov(Memory.QWord(c.Rbx, addrSize * 2), c.Rax); // end put new patch in place // restore rax from call site c.Mov(c.Rax, Memory.QWord(c.Rbp, 2 * addrSize)); c.Pop(c.Rdx); c.Pop(c.Rcx); c.Pop(c.Rbx); c.Popf(); c.Pop(c.Rbp); c.Ret((ulong)8); // overwrite some pieces of the code with values computed later on var codeSiteCodeJit = AssemblyUtil.GetAsmJitBytes(c); for (var j = 0; j < 8; j++) { codeSiteCodeJit[smcLastRipJit + j] = 0; } var i = 0; var nextOffset1 = 0; using (var sw = new StreamWriter(Specifics.PatchAsmDumpFileName)) { foreach (var s in codeSiteCodeJit.Select((b1) => $"b: 0x{b1:X2}")) { string asm1S; try { var asm1 = AssemblyUtil.Disassemble(codeSiteCodeJit.Skip(i).Take(AssemblyUtil.MaxInstructionBytes).ToArray()); asm1S = i == nextOffset1?asm1.ToString() : ""; if (i == nextOffset1) { nextOffset1 += asm1.Length; } } catch (DecodeException) { asm1S = "failed"; } sw.WriteLine($"{s}: ASM: {asm1S}"); i++; } } if (codeSiteCodeJit.Length > codeSize) { throw new Exception("did not reserve enough memory for code"); } // write process memory DebugProcessUtils.WriteBytes(process, (ulong)originalCodeAddress, originalCode); DebugProcessUtils.WriteBytes(process, (ulong)asmSizesAddress, asmSizes); DebugProcessUtils.WriteBytes(process, (ulong)injectedCodeAddress, codeSiteCodeJit); DebugProcessUtils.WriteBytes(process, patchSite, patchSiteCodeJit); return(traceState); }
// remember to set targetAddress before using this function public List<ThreadData> TestBreak() { #if RestartEachTime #if UseDebugger // Attach to the process we provided the thread as an argument if (!Win32Imports.DebugActiveProcess(CurrentProcess.Id)) { throw new Win32Exception(); } if (!Win32Imports.DebugSetProcessKillOnExit(false)) { throw new Win32Exception(); } #endif #endif var retval = new List<ThreadData>(); var context = new Win32Imports.ContextX64(); //Console.WriteLine("TestBreak"); if (!CheckBreakPointActive()) { //Console.WriteLine("adding breakpoints: " + breakPoints.Keys.Count); foreach (var breakPoint in BreakPoints.Keys) { var shouldEnable = BreakPoints[breakPoint].ShouldEnable; //Console.WriteLine($"enumerating breakPoint: 0x{breakPoint}, shouldEnable: {shouldEnable}"); if (shouldEnable) { var msg = BreakPoints[breakPoint].Description; Console.WriteLine($"installing continuation breakpoint at {breakPoint:X}: {msg}"); LoggerInstance.WriteLine($"installing continuation breakpoint at {breakPoint:X}: {msg}"); /* if (!Specifics.useDebugger) { LoggerInstance.WriteLine("Suspending all threads"); foreach (ProcessThread thread2 in process.Threads) { getRip((uint)thread2.Id, ref context, ACTION_SUSPEND); } }*/ try { #if UseHardwareBreakPoints foreach (ProcessThread thread in CurrentProcess.Threads) { InstallHardwareBreakPoint((uint) thread.Id, breakPoint); } #else InstallBreakPoint(breakPoint); #endif } catch (Exception e) { Console.WriteLine($"Exception: {e.Message}"); Console.WriteLine(e.StackTrace); LoggerInstance.WriteLine($"Exception: {e.Message}"); LoggerInstance.WriteLine(e.StackTrace); } } else { Console.WriteLine("stale breakpoint" + BreakPoints[breakPoint].Description); } } } foreach (ProcessThread thread in CurrentProcess.Threads) { uint iThreadId = (uint)thread.Id; #if UseDebugger var traceInterval = Specifics.TraceInterval; var waitInterval = Specifics.WaitInterval; var sw = new Stopwatch(); sw.Start(); // trace program. slows down program a lot var done = false; foreach (ProcessThread thread2 in CurrentProcess.Threads) { //Console.WriteLine("reinstalling CloseHandle breakpoint"); InstallHardwareBreakPoint((uint)thread2.Id, BreakPoints.First(kv => kv.Value.Description.Equals(CloseHandleDescription)).Key); } while (sw.ElapsedMilliseconds <= traceInterval) { getRip(iThreadId, ref context, GetRipAction.ActionGetContext); var breakAddress = context.Rip; if (BreakPoints.ContainsKey(breakAddress) && BreakPoints[breakAddress].IsActive) { LoggerInstance.WriteLine("setting initial trace"); setTrace(iThreadId); } if (ResumeBreak()) { done = true; break; } } sw.Stop(); if (!done) { sw = new Stopwatch(); sw.Start(); // give program time to run without debugging while (sw.ElapsedMilliseconds <= waitInterval) { ResumeBreak(false); } sw.Stop(); } #else var sw = new Stopwatch(); sw.Start(); //getRip(iThreadId, ref context, ACTION_RESUME); while (sw.ElapsedMilliseconds <= Specifics.loopWaitInterval) { foreach (ProcessThread thread2 in CurrentProcess.Threads) { uint iThreadId2 = (uint)thread2.Id; getRip(iThreadId2, ref context, ActionGetContext); var breakAddress = context.Rip; if (BreakPoints.ContainsKey(breakAddress) && BreakPoints[breakAddress].IsActive) { LoggerInstance.WriteLine($"match at {breakAddress:X} for thread {iThreadId2}"); setTrace((uint)iThreadId2, true); UninstallBreakPoint(breakAddress); _lastBreakAddress = breakAddress; CurrentInfo.LastContext = context; CurrentInfo.LastContextReady = true; // trace BreakPointCallBack(this, (int)iThreadId2, context, true); } } } //getRip(iThreadId, ref context, ACTION_SUSPEND); sw.Stop(); #endif // XXX: ONLY FIRST THREAD /*if (Specifics.useDebugger) { break; }*/ break; } /* if (!Specifics.useDebugger) { foreach (var address in originalCode.Keys) { if (activeBreakPoints[address]) { RemoveBreakPoint(address); } } if (!Specifics.useDebugger) { LoggerInstance.WriteLine("Resuming all threads"); foreach (ProcessThread thread2 in process.Threads) { getRip((uint)thread2.Id, ref context, ACTION_RESUME); } } }*/ //ResumeBreak(address); //Thread.Sleep(5000); #if RestartEachTime #if UseDebugger if (!Win32Imports.DebugActiveProcessStop(CurrentProcess.Id)) { throw new Win32Exception(); } #endif #endif return retval; }
public bool ResumeBreak(bool trace = true) { //Console.WriteLine("ResumeBreak"); //for (var i = 0; i < 10000; i++) { var done = false; var goNextBreakPoint = false; var continueCode = Win32Imports.DbgContinue; while (!done) { Win32Imports.DebugEvent evt; if (!Win32Imports.WaitForDebugEvent(out evt, 0)) { //if (!handleDebugEvent(out evt, out continueCode)) { //Console.WriteLine("WaitForDebugEvent failed"); //throw new Win32Exception(); done = true; } else { CurrentInfo.EventCount++; // Multiple if's for easier debugging at this moment switch (evt.dwDebugEventCode) { case Win32Imports.DebugEventType.LoadDllDebugEvent: //Console.WriteLine($"resumed load dll event: {evt.dwThreadId}"); break; case Win32Imports.DebugEventType.UnloadDllDebugEvent: //Console.WriteLine($"resumed unload dll event: {evt.dwThreadId}"); break; case Win32Imports.DebugEventType.ExceptionDebugEvent: var exceptionAddress = (ulong) evt.Exception.ExceptionRecord.ExceptionAddress.ToInt64(); //Console.WriteLine($"first addr: {breakAddress:X} vs {address:X}"); var context = new Win32Imports.ContextX64(); var breakAddress = getRip((uint) evt.dwThreadId, ref context, GetRipAction.ActionGetContext); var code = evt.Exception.ExceptionRecord.ExceptionCode; //Console.WriteLine($"code: {code}, events: {eventCount}, thread: {evt.dwThreadId}, addr: {breakAddress2:X} vs {address:X}"); if (BreakPoints.ContainsKey(breakAddress) && BreakPoints[breakAddress].IsActive) { LoggerInstance.WriteLine($"match at {breakAddress:X}, trace: {trace}"); setTrace((uint)evt.dwThreadId); UninstallBreakPoint(breakAddress); _lastBreakAddress = breakAddress; CurrentInfo.LastContext = context; CurrentInfo.LastContextReady = true; // trace //setTrace((uint) evt.dwThreadId, false); if (BreakPoints[breakAddress].Description.Equals(CloseHandleDescription)) { LoggerInstance.WriteLine("CloseHandle hit"); LoggerInstance.WriteLine("Registers:" + AssemblyUtil.FormatContext(context)); } else { } done = BreakPointCallBack(this, evt.dwThreadId, context, trace).StepOver; goNextBreakPoint = done; //} else if (!CheckBreakPointActive()) { } else { // if we have seen it before var shouldTrace = !BreakPoints.ContainsKey(breakAddress) || !(BreakPoints.ContainsKey(breakAddress) && !BreakPoints[breakAddress].ShouldEnable) || !(BreakPoints.ContainsKey(breakAddress) && !BreakPoints[breakAddress].IsActive); // no breakpoint active, so we are tracing if (trace && shouldTrace) { LoggerInstance.WriteLine($"tracing thread {evt.dwThreadId} at 0x{breakAddress:X}"); var ret = BreakPointCallBack(this, evt.dwThreadId, context, true); done = ret.StepOver; goNextBreakPoint = done; if (ret.Ignore) { LoggerInstance.WriteLine("continuing with exception handlers"); continueCode = Win32Imports.DbgExceptionNotHandled; } } else { LoggerInstance.WriteLine("continuing with exception handlers"); continueCode = Win32Imports.DbgExceptionNotHandled; } } Instruction instr = null; string asm = "N/A"; string asm2 = "N/A"; try { instr = AssemblyUtil.Disassemble(CurrentProcess, exceptionAddress); asm = AssemblyUtil.FormatInstruction(AssemblyUtil.Disassemble(CurrentProcess, breakAddress)); asm2 = AssemblyUtil.FormatInstruction(AssemblyUtil.Disassemble(CurrentProcess, exceptionAddress)); } catch (Exception) { // ignored } string msg; switch (code) { case Win32Imports.ExceptionCodeStatus.ExceptionSingleStep: asm = AssemblyUtil.FormatInstruction(AssemblyUtil.Disassemble(CurrentProcess, breakAddress)); LoggerInstance.WriteLine($"single step at {breakAddress:X}, evtCount: {CurrentInfo.EventCount}, asm: {asm}"); continueCode = HasBreakPoint(breakAddress) ? Win32Imports.DbgContinue : Win32Imports.DbgExceptionNotHandled; break; case Win32Imports.ExceptionCodeStatus.ExceptionBreakpoint: asm = AssemblyUtil.FormatInstruction(instr); //if (instr.Mnemonic.Equals("INT") && instr.Operands.Equals("3")) { if (instr != null && instr.Mnemonic == ud_mnemonic_code.UD_Iint3) { LoggerInstance.WriteLine($"int3 breakpoint at: {exceptionAddress:X}, evtCount: {CurrentInfo.EventCount}, asm: {asm}"); LoggerInstance.WriteLine("overwriting with NOP"); DebugProcessUtils.WriteByte(CurrentProcess, exceptionAddress, AssemblyUtil.Nop); continueCode = Win32Imports.DbgContinue; } else { msg = $"breakpoint, chance: { evt.Exception.dwFirstChance}"; msg += $", at: 0x{breakAddress:X}, exc at: 0x{exceptionAddress:X}, asm: {asm}, exc asm: {asm2}"; LoggerInstance.WriteLine(msg); } break; case Win32Imports.ExceptionCodeStatus.ExceptionInvalidHandle: msg = $"invalid handle, chance: { evt.Exception.dwFirstChance}"; msg += $", at: 0x{breakAddress:X}, exc at: 0x{exceptionAddress:X}, asm: {asm}, exc asm: {asm2}"; LoggerInstance.WriteLine(msg); LoggerInstance.WriteLine(msg); AssemblyUtil.LogStackTrace(_importResolver, LoggerInstance, CurrentProcess, context.Rsp); continueCode = Win32Imports.DbgExceptionNotHandled; break; case Win32Imports.ExceptionCodeStatus.ExceptionInvalidOperation: msg = $"invalid operation, chance: { evt.Exception.dwFirstChance}"; msg += $", at: 0x{breakAddress:X}, exc at: 0x{exceptionAddress:X}, asm: {asm}, exc asm: {asm2}"; // anti-anti-debug measure if (instr != null && instr.Mnemonic == ud_mnemonic_code.UD_Iud2) { LoggerInstance.WriteLine("overwriting UD2 with NOP"); DebugProcessUtils.WriteBytes(CurrentProcess, exceptionAddress, new[] { AssemblyUtil.Nop, AssemblyUtil.Nop }); } // anti-anti-debug measure var instr2 = AssemblyUtil.Disassemble(CurrentProcess, exceptionAddress - 1); if (instr2.Mnemonic == ud_mnemonic_code.UD_Iint && instr2.Operands[0].Value == 0x2D) { ulong rip = context.Rip - 1; setRip((uint) evt.dwThreadId, false, rip); LoggerInstance.WriteLine("INT2D encountered, subtracting 1 from Rip"); continueCode = Win32Imports.DbgContinue; } else { continueCode = Win32Imports.DbgExceptionNotHandled; } LoggerInstance.WriteLine(msg); break; case Win32Imports.ExceptionCodeStatus.ExceptionAccessViolation: msg = $"access violation: {code:X}, chance: { evt.Exception.dwFirstChance}"; msg += $", at: 0x{breakAddress:X}, exc at: 0x{exceptionAddress:X}, asm: {asm}, exc asm: {asm2}"; LoggerInstance.WriteLine(msg); break; case 0: LoggerInstance.WriteLine($"event 0 at: {breakAddress:X}"); break; default: msg = $"unknown code: {code:X}, chance: { evt.Exception.dwFirstChance}"; msg += $", at: 0x{breakAddress:X}, exc at: 0x{exceptionAddress:X}, asm: {asm}, exc asm: {asm2}"; LoggerInstance.WriteLine(msg); break; } break; case Win32Imports.DebugEventType.CreateProcessDebugEvent: //Console.WriteLine($"resumed create process event for thread {evt.dwThreadId}"); break; case Win32Imports.DebugEventType.CreateThreadDebugEvent: //Console.WriteLine($"resumed create thread event for thread {evt.dwThreadId}"); break; case Win32Imports.DebugEventType.ExitThreadDebugEvent: //Console.WriteLine($"resumed exit thread event for thread {evt.dwThreadId}"); break; case Win32Imports.DebugEventType.ExitProcessDebugEvent: Console.WriteLine($"resumed exit process event for thread {evt.dwThreadId}"); break; default: Console.WriteLine($"resumed debug event for thread: {evt.dwThreadId} {evt.dwDebugEventCode}"); break; } //LoggerInstance.WriteLine($"debug event of type {evt.dwDebugEventCode}"); if (!Win32Imports.ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, continueCode)) { throw new Win32Exception(); } //ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); //setBreakPoint((uint) evt.dwThreadId, address, false); } } //Console.WriteLine("End ResumeBreak"); //return events; return goNextBreakPoint; }
public static void TraceIt(Process process, ulong patchSite, Logger logger, bool debug, Action <Process, ulong, Logger> installTracer) { if (debug) { if (!Win32Imports.DebugActiveProcess(process.Id)) { throw new Win32Exception(); } if (!Win32Imports.DebugSetProcessKillOnExit(false)) { throw new Win32Exception(); } } foreach (ProcessThread thread in process.Threads) { var threadId = thread.Id; var context = new Win32Imports.ContextX64(); ContextManager.getRip((uint)threadId, ref context, ContextManager.GetRipAction.ActionSuspend); } Console.WriteLine("installing tracer"); installTracer(process, patchSite, logger); var mainThread = 0; foreach (ProcessThread thread in process.Threads) { var threadId = thread.Id; var context = new Win32Imports.ContextX64(); var breakAddress = ContextManager.getRip((uint)threadId, ref context, ContextManager.GetRipAction.ActionGetContext); var diff = new BigInteger(breakAddress) - new BigInteger(patchSite); diff = diff < 0 ? -diff : diff; if (diff < 1000) { mainThread = threadId; logger.WriteLine($"thread {threadId} setting Rip to patch site: {patchSite:X}"); ContextManager.setRip((uint)threadId, false, patchSite); if (debug) { ContextManager.setTrace((uint)threadId, false); } } ContextManager.getRip((uint)threadId, ref context, ContextManager.GetRipAction.ActionResume); } /*if (!Win32Imports.DebugBreakProcess(process.Id)) { * throw new Win32Exception(); * }*/ if (debug) { Dictionary <int, OldState> oldThreadState = new Dictionary <int, OldState>(); try { while (true) { Win32Imports.DebugEvent evt; if (Win32Imports.WaitForDebugEvent(out evt, -1)) { Console.WriteLine($"debug event {evt.dwDebugEventCode}"); var continueCode = Win32Imports.DbgContinue; if (evt.dwDebugEventCode == Win32Imports.DebugEventType.ExceptionDebugEvent && evt.dwThreadId == mainThread) { var exceptionAddress = (ulong)evt.Exception.ExceptionRecord.ExceptionAddress.ToInt64(); var context = new Win32Imports.ContextX64(); var breakAddress = ContextManager.getRip((uint)evt.dwThreadId, ref context, ContextManager.GetRipAction.ActionGetContext); var code = evt.Exception.ExceptionRecord.ExceptionCode; Console.WriteLine($"thread {evt.dwThreadId} break at 0x{exceptionAddress:X} code {code}, 0x{breakAddress:X}"); ContextManager.setTrace((uint)evt.dwThreadId); var instr = AssemblyUtil.Disassemble(process, exceptionAddress); var asm = AssemblyUtil.FormatInstruction(instr); var strContext = oldThreadState.ContainsKey(evt.dwThreadId) ? AssemblyUtil.FormatContextDiff(context, oldThreadState[evt.dwThreadId].Context, oldThreadState[evt.dwThreadId].Instruction) : AssemblyUtil.FormatContext(context); logger.WriteLine($"thread {evt.dwThreadId} break at 0x{exceptionAddress:X}, 0x{breakAddress:X} code {code}: {asm}, regs-1: {strContext}"); oldThreadState[evt.dwThreadId] = new OldState { Context = context, Instruction = instr }; if (code == Win32Imports.ExceptionCodeStatus.ExceptionAccessViolation) { continueCode = Win32Imports.DbgExceptionNotHandled; } } if (!Win32Imports.ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, continueCode)) { throw new Win32Exception(); } } } } finally { if (!Win32Imports.DebugActiveProcessStop(process.Id)) { throw new Win32Exception(); } } } }
public static void InstallTracer(Process process, ulong patchSite, Logger logger) { var processHandle = Win32Imports.OpenProcess(Win32Imports.ProcessAccessFlags.All, false, process.Id); var retVal = new TraceState(); if ((ulong)processHandle == 0) { logger.WriteLine("could not open process"); return; } const int patchSiteSize = 24; const int patchAreaSize = 100; const int codeSize = 1024 + patchAreaSize * 100; var originalCode = DebugProcessUtils.ReadBytes(process, patchSite, patchAreaSize); // minus patchSiteSize because we need to restore patchSiteSize bytes var ci = new diStorm.CodeInfo(0, originalCode.Take(patchAreaSize - patchSiteSize).ToArray(), diStorm.DecodeType.Decode64Bits, 0); var dr = new diStorm.DecodedResult(patchAreaSize); diStorm.diStorm3.Decode(ci, dr); var decodedInstructions = dr.Instructions; var bogusAddress = Win32Imports.VirtualAllocEx( processHandle, new IntPtr(0), new IntPtr(patchSiteSize * 2), Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve, Win32Imports.MemoryProtection.ReadWrite); var codeAddress = Win32Imports.VirtualAllocEx( processHandle, new IntPtr(0), new IntPtr(codeSize), Win32Imports.AllocationType.Commit | Win32Imports.AllocationType.Reserve, Win32Imports.MemoryProtection.ExecuteReadWrite); retVal.CodeAddress = (ulong)codeAddress; if ((ulong)codeAddress == 0) { logger.WriteLine("could not allocate memory"); return; } var patchSiteCode = new byte[patchSiteSize]; var codeSiteCode = new byte[codeSize]; var patchPtr = 0; var ptr = 0; //patchSiteCode[patchPtr++] = 0xCC; // push rax, see RESTORE_RAX patchSiteCode[patchPtr++] = 0x50; // lea rax, [rip-8] patchSiteCode[patchPtr++] = 0x48; patchSiteCode[patchPtr++] = 0x8D; patchSiteCode[patchPtr++] = 0x05; patchSiteCode[patchPtr++] = 0xF8; patchSiteCode[patchPtr++] = 0xFF; patchSiteCode[patchPtr++] = 0xFF; patchSiteCode[patchPtr++] = 0xFF; // push rax (rip) patchSiteCode[patchPtr++] = 0x50; // mov rax, constant patchSiteCode[patchPtr++] = 0x48; patchSiteCode[patchPtr++] = 0xB8; foreach (var b in BitConverter.GetBytes((ulong)codeAddress)) { patchSiteCode[patchPtr++] = b; } // push rax patchSiteCode[patchPtr++] = 0x50; // ret patchSiteCode[patchPtr++] = 0xC3; patchPtr += 3; if (patchPtr != patchSiteSize) { logger.WriteLine($"made a mistake: {patchPtr} != {patchSiteSize}"); return; } // push rbp codeSiteCode[ptr++] = 0x55; // mov rbp, rsp codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x89; codeSiteCode[ptr++] = 0xE5; // push flags codeSiteCode[ptr++] = 0x9C; // push rbx codeSiteCode[ptr++] = 0x53; // push rcx codeSiteCode[ptr++] = 0x51; // push rdx codeSiteCode[ptr++] = 0x52; // reserve space for last Rip address // mov rax, constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0xB8; var smcLastRip = ptr; ptr += 8; // lea rax, [rip-7-8] codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x8D; codeSiteCode[ptr++] = 0x05; codeSiteCode[ptr++] = 0xF1; codeSiteCode[ptr++] = 0xFF; codeSiteCode[ptr++] = 0xFF; codeSiteCode[ptr++] = 0xFF; // mov rcx, [rax] codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x8B; codeSiteCode[ptr++] = 0x08; // inc rcx codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0xFF; codeSiteCode[ptr++] = 0xC1; // xchg [rax], rcx codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x87; codeSiteCode[ptr++] = 0x08; // find return address // mov rbx, [rbp+8] codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x8B; codeSiteCode[ptr++] = 0x5D; codeSiteCode[ptr++] = 0x08; // call pastEndCode codeSiteCode[ptr++] = 0xE8; var callPastEndCodeImmediate = ptr; ptr += 4; var callPastEndCodeBaseOffset = ptr; // END FUNCTION // put new patch in place // mov rax, constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0xB8; foreach (var b in patchSiteCode.Skip(0).Take(8)) { codeSiteCode[ptr++] = b; } // restore patch site 1 // mov [rbx], rax codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x89; codeSiteCode[ptr++] = 0x03; // mov rax, constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0xB8; foreach (var b in patchSiteCode.Skip(8).Take(8)) { codeSiteCode[ptr++] = b; } // restore patch site 2 // mov [rbx+8], rax codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x89; codeSiteCode[ptr++] = 0x43; codeSiteCode[ptr++] = 0x08; // mov rax, constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0xB8; foreach (var b in patchSiteCode.Skip(16).Take(8)) { codeSiteCode[ptr++] = b; } // restore patch site 3 // mov [rbx+16], rax codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x89; codeSiteCode[ptr++] = 0x43; codeSiteCode[ptr++] = 0x10; // end put new patch in place // mov rax,[rbp+16] codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x8B; codeSiteCode[ptr++] = 0x45; codeSiteCode[ptr++] = 0x10; // pop rdx codeSiteCode[ptr++] = 0x5A; // pop rcx codeSiteCode[ptr++] = 0x59; // pop rbx codeSiteCode[ptr++] = 0x5B; // pop flags codeSiteCode[ptr++] = 0x9D; // pop rbp codeSiteCode[ptr++] = 0x5D; // ret 8 codeSiteCode[ptr++] = 0xC2; codeSiteCode[ptr++] = 0x08; codeSiteCode[ptr++] = 0x00; codeSiteCode[callPastEndCodeImmediate] = (byte)(ptr - callPastEndCodeBaseOffset); // imul rcx,rcx,constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x6B; codeSiteCode[ptr++] = 0xC9; // = size of restore function var multiplyImmediate = ptr; codeSiteCode[ptr++] = 0x00; // lea rcx, rcx+eax+constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x8D; codeSiteCode[ptr++] = 0x4C; codeSiteCode[ptr++] = 0x08; var leaRcxConstant = ptr; codeSiteCode[ptr++] = 0x00; // jmp rcx codeSiteCode[ptr++] = 0xFF; codeSiteCode[ptr++] = 0xE1; codeSiteCode[leaRcxConstant] = (byte)(ptr - smcLastRip); // restore function uint offset = 0; var lastReturn = 0; foreach (var instr in decodedInstructions.Select((value, index) => new { Value = value, Index = index })) { var startOfRestore = ptr; Console.WriteLine($"instruction {instr.Value.Mnemonic}, size: {instr.Value.Size}"); // reserve space for call site restore 1 // mov rax, constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0xB8; var originalOffset = 0; var patchOffset = 0; foreach (var b in originalCode.Skip((int)offset).Take(8)) { if (originalOffset < instr.Value.Size) { codeSiteCode[ptr++] = b; } else { codeSiteCode[ptr++] = patchSiteCode[patchOffset++]; } originalOffset++; } // restore call site 1 // mov [rbx], rax codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x89; codeSiteCode[ptr++] = 0x03; // reserve space for call site restore 2 // mov rax, constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0xB8; foreach (var b in originalCode.Skip((int)offset + 8).Take(8)) { if (originalOffset < instr.Value.Size) { codeSiteCode[ptr++] = b; } else { codeSiteCode[ptr++] = patchSiteCode[patchOffset++]; } originalOffset++; } // restore call site 2 // mov [rbx+8], rax codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x89; codeSiteCode[ptr++] = 0x43; codeSiteCode[ptr++] = 0x08; // reserve space for call site restore 3 // mov rax, constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0xB8; foreach (var b in originalCode.Skip((int)offset + 16).Take(8)) { if (originalOffset < instr.Value.Size) { codeSiteCode[ptr++] = b; } else { codeSiteCode[ptr++] = patchSiteCode[patchOffset++]; } originalOffset++; } // restore call site 3 // mov [rbx+16], rax codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x89; codeSiteCode[ptr++] = 0x43; codeSiteCode[ptr++] = 0x10; // prepare rbx to put new patch in place // add rbx,instrSize codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0x83; codeSiteCode[ptr++] = 0xC3; codeSiteCode[ptr++] = (byte)(instr.Value.Size); // return lastReturn = ptr; codeSiteCode[ptr++] = 0xC3; var endOfRestore = ptr; Console.WriteLine($"mul: {endOfRestore - startOfRestore}"); codeSiteCode[multiplyImmediate] = (byte)(endOfRestore - startOfRestore); offset += instr.Value.Size; } ptr = lastReturn; // we set rbx to bogus so that patch is not written for last instruction in set // mov rbx, constant codeSiteCode[ptr++] = 0x48; codeSiteCode[ptr++] = 0xBB; foreach (var b in BitConverter.GetBytes((ulong)bogusAddress)) { codeSiteCode[ptr++] = b; } // return codeSiteCode[ptr] = 0xC3; DebugProcessUtils.WriteBytes(process, (ulong)codeAddress, codeSiteCode); DebugProcessUtils.WriteBytes(process, patchSite, patchSiteCode); }