private static int ScanFunctions(Process process, ImportResolver ir, StreamWriter sw) { var totalSize = 0; foreach (var mi in ir.ModuleFunctions) { var result = "success"; try { DebugProcessUtils.ReadBytes(process, mi.Address, (int)mi.Size); totalSize += (int)mi.Size; } catch { result = "failed"; } sw.WriteLine($"function: {mi.Module.ModuleName}.{mi.FunctionName}, size: {mi.Size} result: {result}"); } return(totalSize); }
public static void TraceMain(Process process, ImportResolver ir, List <ulong> matches, Logger logger) { MemScan.MemMain(process, ir, true); var patchSite = matches.First(); var asmSizes = File.ReadAllBytes(Specifics.ReadAsmSizesDumpFileName); var branches = File.ReadAllBytes(Specifics.ReadAsmBranchDumpFileName); logger.WriteLine($"patch site: {patchSite:X}"); EmulatedMemTracer.TraceState traceState = null; SimpleMemTracer.TraceIt(process, patchSite, logger, false, (x, y, z) => { traceState = EmulatedMemTracer.InstallTracer(x, y, z, ir, asmSizes, branches); }); if (traceState != null) { // TODO: fix race var threadId = BitConverter.ToUInt32(DebugProcessUtils.ReadBytes(process, traceState.TraceLogAddress, 4), 0); Console.WriteLine($"thread id: {threadId:X}"); } }
public static void DoIt() { var name = Specifics.ProcessName; var address = Specifics.StartAddress; var process = DebugProcessUtils.GetFirstProcessByName(name); using (Form form = new Form()) { form.Text = "Inspector"; form.Size = new Size(600, 1080); var table = new DataGridView(); var bindingSource = new BindingSource(); table.DataSource = bindingSource; var infoTable = new DataGridView(); var infoSource = new BindingSource(); infoTable.DataSource = infoSource; var formClosed = false; var cleanupFinished = false; var splitter = new Splitter(); infoTable.Dock = DockStyle.Left; splitter.Dock = DockStyle.Left; table.Dock = DockStyle.Fill; form.Controls.AddRange(new Control[] { table, splitter, infoTable }); Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; //form.Close(); }; var progress = new Progress <List <ContextManager.ThreadData> >( (contexts) => { AddContexts(bindingSource, contexts); } ); IProgress <ContextManager.Info> infoProgress = new Progress <ContextManager.Info>( (info) => { UpdateInfo(infoSource, info); } ); var specifics = new Specifics(); Task.Factory.StartNew( () => { var logName = Specifics.LogName; var logName2 = Specifics.LogNameLatest; using (var logFile = new Logger(logName, logName2)) { ContextManager cg = null; logFile.WriteLine(""); logFile.WriteLine("STARTING INSPECTOR"); try { var importResolver = new ImportResolver(process); importResolver.DumpDebug(); var logContext = new ContextTracer(importResolver); var logFile2 = logFile; cg = new ContextManager(name, logFile, specifics, (cm, threadId, context, trace) => { Debug.Assert(logFile2 != null, "logFile2 != null"); return(logContext.Log(cm, logFile2, process, threadId, context, trace)); }, importResolver); //if (File.Exists(Specifics.appStateFileName)) { //SaveAndRestore.Restore(Specifics.appStateFileName, cg); //} else { cg.EnableBreakPoint(importResolver.ResolveRelativeAddress(address), new ContextManager.BreakPointInfo { Description = "starting breakpoint" }); //} cg.AntiAntiDebug(); /*try { * cg.InstallBreakPoint(address); * } catch (InvalidOperationException e) { * Console.WriteLine($"Failed to install break points: {e.ToString()}"); * }*/ while (!formClosed) { logFile.WriteLine("main debugger loop"); cg.CurrentProcess = DebugProcessUtils.GetFirstProcessByName(cg.CurrentProcess.ProcessName); cg.TestBreak(); Update(cg, progress); infoProgress.Report(cg.CurrentInfo); SaveAndRestore.Save(Specifics.AppStateFileName, cg); Task.Delay(Specifics.MainLoopDelay).Wait(); } } catch (Exception e) { Console.WriteLine($"Exception: {e.Message}"); Console.WriteLine(e.StackTrace); logFile.WriteLine($"Exception: {e.Message}"); logFile.WriteLine(e.StackTrace); } // cleanup Console.WriteLine("cleaning up"); SaveAndRestore.Save(Specifics.AppStateFileName, cg); cg?.Stop(); cleanupFinished = true; } }, TaskCreationOptions.LongRunning ); /*Task.Factory.StartNew( * () => { * while (true) { * cg.ResumeEvents(); * } * }, * TaskCreationOptions.LongRunning * );*/ form.FormClosing += (sender, e) => { Console.WriteLine("form closing"); formClosed = true; while (!cleanupFinished) { Console.WriteLine("waiting for cleanup"); Task.Delay(1000).Wait(); } }; form.ShowDialog(); } }
public static void MemMain(Process process, ImportResolver ir, bool setPageExecuteReadWrite = false) { // getting minimum & maximum address SystemInfo sysInfo; GetSystemInfo(out sysInfo); var procMinAddress = sysInfo.minimumApplicationAddress; var procMaxAddress = sysInfo.maximumApplicationAddress; // saving the values as long ints so I won't have to do a lot of casts later var procMinAddressL = (ulong)procMinAddress; var procMaxAddressL = (ulong)procMaxAddress; // opening the process with desired access level // IntPtr processHandle = OpenProcess(ProcessAccessFlags.QueryInformation | ProcessAccessFlags.VirtualMemoryRead, false, process.Id); var processHandle = OpenProcess(ProcessAccessFlags.All, false, process.Id); if ((ulong)processHandle == 0) { throw new Win32Exception(); } var sw = new StreamWriter(Specifics.RawRegionsDumpFileName); // this will store any information we get from VirtualQueryEx() var bytesRead = new IntPtr(0); // number of bytes read with ReadProcessMemory ulong totalBytesRead = 0; // expect 4 gig var progressTotal = new BigInteger(1024 * 1024 * 1024); progressTotal *= 4; BigInteger lastProgress = 0; Console.WriteLine("start scanning"); while (procMinAddressL < procMaxAddressL) { // 28 = sizeof(MEMORY_BASIC_INFORMATION) MemoryBasicInformation memBasicInfo; if ( (ulong) VirtualQueryEx(processHandle, procMinAddress, out memBasicInfo, new IntPtr(Marshal.SizeOf(typeof(MemoryBasicInformation)))) == 0) { throw new Win32Exception(); } if (setPageExecuteReadWrite) { AllocationProtectEnum oldProtection; if ( !VirtualProtectEx(processHandle, memBasicInfo.BaseAddress, new UIntPtr((ulong)memBasicInfo.RegionSize), AllocationProtectEnum.PageExecuteReadwrite, out oldProtection)) { //throw new Win32Exception(); } } var regionStartModule = ir.LookupAddress((ulong)memBasicInfo.BaseAddress); var regionEndModule = ir.LookupAddress((ulong)memBasicInfo.BaseAddress + (ulong)memBasicInfo.RegionSize); var isAccessible = memBasicInfo.Protect.HasFlag(AllocationProtectEnum.PageReadwrite) || memBasicInfo.Protect.HasFlag(AllocationProtectEnum.PageExecuteReadwrite); if (isAccessible && memBasicInfo.State.HasFlag(StateEnum.MemCommit)) { var buffer = new byte[(ulong)memBasicInfo.RegionSize]; // read everything in the buffer above var success = ""; if ( !ReadProcessMemory(processHandle, memBasicInfo.BaseAddress, buffer, memBasicInfo.RegionSize, ref bytesRead)) { success = "false"; } else { totalBytesRead += (ulong)bytesRead; } var regionStart = memBasicInfo.BaseAddress.ToString("X"); var regionEnd = ((ulong)memBasicInfo.BaseAddress + (ulong)memBasicInfo.RegionSize).ToString("X"); sw.WriteLine( $"region 0x{regionStart}({regionStartModule})-0x{regionEnd}({regionEndModule}): size {memBasicInfo.RegionSize}: {success}"); // then output this in the file /*for (int i = 0; i < mem_basic_info.RegionSize; i++) { * //sw.WriteLine("0x{0} : {1}", (mem_basic_info.BaseAddress + i).ToString("X"), (char)buffer[i]); * }*/ } else { var regionStart = memBasicInfo.BaseAddress.ToString("X"); var regionEnd = ((ulong)memBasicInfo.BaseAddress + (ulong)memBasicInfo.RegionSize).ToString("X"); sw.WriteLine( $"NOT READ region 0x{regionStart}({regionStartModule})-0x{regionEnd}({regionEndModule}): size {memBasicInfo.RegionSize}"); } // move to the next memory chunk procMinAddressL += (ulong)memBasicInfo.RegionSize; procMinAddress = new IntPtr((long)procMinAddressL); var progress = new BigInteger(totalBytesRead) * 100 / progressTotal; if (progress != lastProgress) { Console.WriteLine($"scanning memory: estimated {progress}%, totalSize: "); } lastProgress = progress; } Console.WriteLine($"end scanning. total MB: {totalBytesRead/1024/1024}"); sw.Close(); }
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); }
// see also StackWalk64, but I'm not sure I can use that, because I don't have a function table public static void LogStackTrace(ImportResolver ir, Logger log, Process process, ulong stackPointer) { int size = 4096; var mem = DebugProcessUtils.ReadBytes(process, stackPointer, 4096); var ptrSize = Marshal.SizeOf(typeof(IntPtr)); for (var offset = 0; offset + ptrSize < size; offset += 1) { ulong ptr = (ulong)BitConverter.ToInt64(mem, offset); if (ptr == 0) { continue; } Tuple <string, ulong> ret = null; try { ret = ir.LookupAddress(ptr); } catch (Exception) { // ignored } string module = "lookup-failed"; ulong relative = 0; if (ret != null) { module = ret.Item1; var functionAddress = ret.Item2; relative = ptr - functionAddress; } byte[] ptrMem = null; try { ptrMem = DebugProcessUtils.ReadBytes(process, ptr, ptrSize); } catch (Exception) { // ignored } ulong data = 0; if (ptrMem != null) { data = (ulong)BitConverter.ToInt64(ptrMem, 0); } for (ulong potentialCallOffset = 0; potentialCallOffset <= 6; potentialCallOffset++) { try { var callLocation = ptr - potentialCallOffset; var instr = Disassemble(process, callLocation); var asm = FormatInstruction(instr); if (instr.Mnemonic == ud_mnemonic_code.UD_Icall || potentialCallOffset == 0) { log.WriteLine($"stack call {offset}-{potentialCallOffset}: {module}+0x{relative:X} 0x{ptr:X}: asm 0x{data:X} {asm}"); } } catch (Exception) { if (potentialCallOffset == 0) { log.WriteLine($"stack trace {offset}-{potentialCallOffset}: {module}+0x{relative:X} 0x{ptr:X}: asm 0x{data:X} exception"); } } } } }