// returns true if new breakpoint was set public TraceReturn Log(ContextManager cm, Logger logFile, Process process, int threadId, Win32Imports.ContextX64 context, bool trace) { if (!trace) { // end trace return(new TraceReturn { StepOver = false }); } // http://stackoverflow.com/questions/14698350/x86-64-asm-maximum-bytes-for-an-instruction var breakAddress = context.Rip; var mem = DebugProcessUtils.ReadBytes(process, breakAddress, AssemblyUtil.MaxInstructionBytes); var distance = (long)(breakAddress - cm.LastBreakAddress); var decodedInstruction = AssemblyUtil.Disassemble(process, breakAddress); var hex = DebugProcessUtils.BytesToHex(mem.Take(decodedInstruction.Length).ToArray()); var moduleAddressTuple = _importResolver.LookupAddress(breakAddress); var module = moduleAddressTuple.Item1; var relativeAddress = breakAddress - moduleAddressTuple.Item2; var hitCounts = _oldState.ContainsKey(threadId) ? _oldState[threadId].HitCounts : new Dictionary <ulong, ulong>(); var stackDepth = 0; if (_oldState.ContainsKey(threadId)) { stackDepth = _oldState[threadId].StackDepth; } if (decodedInstruction.Mnemonic == ud_mnemonic_code.UD_Icall) { stackDepth++; } else if (decodedInstruction.Mnemonic == ud_mnemonic_code.UD_Iret) { stackDepth--; } if (breakAddress == _importResolver.ResolveRelativeAddress(Specifics.StartAddress)) { // reset trace state when we hit first breakpoint // except for oldHitCounts, which will be added back at end of function _oldState.Remove(threadId); } else if (breakAddress != cm.LastBreakAddress) { #if UseDebugger // we're not interested in logging threads where breakpoint was not found if (!_oldState.ContainsKey(threadId)) { logFile.WriteLine($"ignoring trace for thread {threadId}"); return(new TraceReturn { Ignore = true, StepOver = false }); } #endif } // IMPORTANT: continues the trace var retVal = false; ulong lastCallAddressInTraceModule = _oldState.ContainsKey(threadId) ? _oldState[threadId].LastCallAddressInTraceModule : 0; // clean up breakpoints as soon as they are hit cm.DisableBreakPointOnHit(breakAddress); #if UseDebugger if (decodedInstruction.Mnemonic == ud_mnemonic_code.UD_Icall && !module.Equals(Specifics.TraceModuleName)) { // step over call var returnAddress = breakAddress + (ulong)decodedInstruction.Length; logFile.WriteLine($"installing return breakpoint at {returnAddress:X}"); cm.EnableBreakPoint(returnAddress, new ContextManager.BreakPointInfo { Description = "return breakpoint" }); stackDepth--; // since the RET will not be executed retVal = true; } else { if (module.Equals(Specifics.TraceModuleName)) { if (decodedInstruction.Mnemonic == ud_mnemonic_code.UD_Iret) { } else if (decodedInstruction.Mnemonic == ud_mnemonic_code.UD_Ijmp) { } else { var cAddress = breakAddress + (ulong)decodedInstruction.Length; logFile.WriteLine($"setting continuation at 0x{cAddress:X} for {decodedInstruction.Mnemonic} of size {decodedInstruction.Length}"); cm.EnableBreakPoint(cAddress, new ContextManager.BreakPointInfo { Description = $"for {decodedInstruction.Mnemonic} of size {(ulong) decodedInstruction.Length}" }); } /*foreach (var condJump in new string[] { * "JE", "JZ", "JNE", "JNZ", "JG", "JNLE", "JGE", "JNL", "JL", "JNGE", * "JLE", "JNG", "JA", "JNBE", "JAE", "JNB", "JB", "JNAE", "JBE", "JNA" }) { * if (decodedInstruction.Mnemonic.Equals(condJump)) { * var nextTarget = breakAddress + decodedInstruction.Size; * var jumpMatch = Regex.Match("0x([a-z0-9]+)", decodedInstruction.Operands); * if (jumpMatch.Success) { * var offset = ulong.Parse(jumpMatch.Groups[0].Value, System.Globalization.NumberStyles.AllowHexSpecifier); * var jumpTarget = (ulong) (new BigInteger(breakAddress) + (long) offset); * //logFile.WriteLine($"jump calc: offset: {(long) offset}, target: 0x{jumpTarget:X}"); * } * * //cm.continuationBreakAddress += * break; * } * }*/ if (decodedInstruction.Mnemonic == ud_mnemonic_code.UD_Icall) { // step over instruction lastCallAddressInTraceModule = breakAddress; lastCallAddressInTraceModule += (ulong)decodedInstruction.Length; } logFile.WriteLine($"setting next trace for {threadId}"); ContextManager.setTrace((uint)threadId); } else { if (lastCallAddressInTraceModule == 0) { logFile.WriteLine("In external module, but no call to get back to."); } else if (!cm.BreakPoints.ContainsKey(lastCallAddressInTraceModule)) { logFile.WriteLine($"In external module, returning to {lastCallAddressInTraceModule:X}"); cm.EnableBreakPoint(lastCallAddressInTraceModule, new ContextManager.BreakPointInfo { Description = $"return from external module: {module}+{relativeAddress}" }); } } } #else throw new NotImplementedException(); /* * cm.continuationBreakAddress = breakAddress; * //cm.continuationBreakAddress += decodedInstruction.Size; * if (decodedInstruction.Mnemonic.Equals("RET")) { * var stackPointer = context.Rsp; * if (!decodedInstruction.Operands.Equals("")) { * var offset = ulong.Parse(decodedInstruction.Operands); * stackPointer += offset; * } * ulong returnAddress = BitConverter.ToUInt64(DebugProcessUtils.ReadBytes(process, stackPointer, 8), 0); * cm.continuationBreakAddress = returnAddress; * }*/ #endif if (hitCounts.ContainsKey(breakAddress)) { hitCounts[breakAddress]++; } else { hitCounts[breakAddress] = 1; } var registers = _oldState.ContainsKey(threadId) ? AssemblyUtil.FormatContextDiff(context, _oldState[threadId].Context, _oldState[threadId].SdInstruction) : AssemblyUtil.FormatContext(context); //logFile.WriteLine(registers); var previous = ""; var lineBreak = false; if (_oldState.ContainsKey(threadId)) { previous = _oldState[threadId].Line; if (_oldState[threadId].Instruction.Mnemonic == ud_mnemonic_code.UD_Iret || _oldState[threadId].Instruction.Mnemonic == ud_mnemonic_code.UD_Icall) { lineBreak = true; } } var asm = $"{ decodedInstruction.Mnemonic } { decodedInstruction.Operands}"; double logdist = distance == 0 ? 0.0 : distance < 0 ? -Math.Log(-distance, 2) : Math.Log(distance, 2); var pattern = ""; pattern += Regex.Escape("["); pattern += "(?<reg1>[A-Z0-9]{3})"; pattern += "((?<sign1>[" + Regex.Escape("+") + Regex.Escape("-") + "])(?<reg2>[A-Z0-9]{3})(" + Regex.Escape("*") + "(?<multiplier>[0-9]+))?)?"; pattern += "((?<sign2>[" + Regex.Escape("+") + Regex.Escape("-") + "])0x(?<offset>[0-9a-f]+))?"; pattern += Regex.Escape("]"); var rex = new Regex(pattern); // TODO: rewrite offset code to use SharpDisasm structure instead of string parsing var operands = decodedInstruction.Operands.ToString(); var match = rex.Matches(operands); BigInteger memAddress = 0; if (match.Count > 0) { var reg1 = match[0].Groups[rex.GroupNumberFromName("reg1")].Value; var reg2 = match[0].Groups[rex.GroupNumberFromName("reg2")].Value; long sign1 = match[0].Groups[rex.GroupNumberFromName("sign1")].Value.Equals("+") ? 1 : -1; long sign2 = match[0].Groups[rex.GroupNumberFromName("sign2")].Value.Equals("+") ? 1 : -1; var multiplierString = match[0].Groups[rex.GroupNumberFromName("multiplier")].Value; var multiplier = multiplierString.Equals("") ? 1 : long.Parse(multiplierString); var offsetHex = match[0].Groups[rex.GroupNumberFromName("offset")].Value; var reg1Value = (ulong)_fieldMap[reg1].GetValue(context); ulong reg2Value = 0; if (!reg2.Equals("")) { reg2Value = (ulong)_fieldMap[reg2].GetValue(context); } var offset = offsetHex.Equals("") ? 0 : long.Parse(offsetHex, System.Globalization.NumberStyles.AllowHexSpecifier); memAddress = new BigInteger(reg1Value) + sign1 * new BigInteger(reg2Value) * multiplier + sign2 * new BigInteger(offset); } else if (decodedInstruction.Mnemonic == ud_mnemonic_code.UD_Ipop || decodedInstruction.Mnemonic == ud_mnemonic_code.UD_Ipush) { memAddress = context.Rsp; } //var module = DebugProcessUtils.GetModuleByAddress(process, breakAddress); var oldLine = $"d: {logdist,8:#.##}, thread: {threadId,6:D}, mem: {memAddress,16:X} "; var moduleAndAddress = $"{module}+0x{relativeAddress:X}:{breakAddress:X}"; oldLine += $"instr: {hex,30},"; var hits = hitCounts[breakAddress]; oldLine += $" {moduleAndAddress,64}(h{hits,2})(s{stackDepth,2}): {asm,-40} "; logFile.WriteLine(previous + registers); if (lineBreak) { logFile.WriteLine(""); } _oldState[threadId] = new State { Context = context, Instruction = decodedInstruction, Line = oldLine, HitCounts = hitCounts, StackDepth = stackDepth, LastCallAddressInTraceModule = lastCallAddressInTraceModule }; return(new TraceReturn { StepOver = retVal }); }
public static MaybeJumpSimple IsBranch(Instruction instruction) { var retVal = new MaybeJumpSimple(); var asm = instruction.ToString(); var mnemonic = udis86.ud_lookup_mnemonic(instruction.Mnemonic); var asmOperands = Regex.Replace(asm, "^.*" + mnemonic + " ", ""); var freeRegister = FindFreeRegister(asm); retVal.RipEquals = freeRegister; string ripRegister = freeRegister.ToString().ToLower(); string targetRegister64 = "rax"; string targetRegister = "rax"; if (asmOperands.Contains("far word")) { asmOperands = asmOperands.Replace("far word", "word"); targetRegister = AssemblyUtil.Get16BitRegisterFrom64BitRegister(targetRegister64.ToUpper()).ToLower(); } else if (asmOperands.Contains("far dword")) { asmOperands = asmOperands.Replace("far dword", "dword"); targetRegister = AssemblyUtil.Get32BitRegisterFrom64BitRegister(targetRegister64.ToUpper()).ToLower(); } else if (asmOperands.Contains("far qword")) { asmOperands = asmOperands.Replace("far qword", "qword"); } else if (instruction.Operands.Length > 0) { // maybe invalid instruction on 64-bit, so won't be called, but wth var opr = instruction.Operands[0]; switch (instruction.Operands[0].Size) { case 8: targetRegister = AssemblyUtil.Get8BitLowerRegisterFrom64BitRegister(targetRegister64.ToUpper()).ToLower(); break; case 16: targetRegister = AssemblyUtil.Get16BitRegisterFrom64BitRegister(targetRegister64.ToUpper()).ToLower(); break; case 32: targetRegister = AssemblyUtil.Get32BitRegisterFrom64BitRegister(targetRegister64.ToUpper()).ToLower(); break; case 64: break; } if (opr.Type == ud_type.UD_OP_JIMM) { switch (opr.Size) { case 8: asmOperands = ((sbyte)(instruction.PC + (ulong)opr.LvalSByte)).ToString(); break; case 16: asmOperands = ((int)(instruction.PC + (ulong)opr.LvalSWord)).ToString(); break; case 32: asmOperands = ((long)(instruction.PC + (ulong)opr.LvalSDWord)).ToString(); break; default: throw new Exception("invalid relative offset size."); } } } asmOperands = asmOperands.Replace("rip", ripRegister); Func <bool> getRelative = () => instruction.Operands.Length > 0 && instruction.Operands[0].Type == ud_type.UD_OP_JIMM; Func <string> getTargetInstruction = () => getRelative() ? "add" : "mov"; retVal.AddBranchOfSameType = (label) => $"{mnemonic} {label}"; retVal.AddInstrToGetBranchTarget = () => new[] { $";; {asm}", $"{getTargetInstruction()} {targetRegister}, {asmOperands}" }; switch (instruction.Mnemonic) { // UNCONDITIONAL BRANCHES case ud_mnemonic_code.UD_Ijmp: retVal.Present = true; retVal.Branch = BranchInstruction.Jmp; retVal.AddBranchOfSameType = (label) => $"jmp {label}"; break; case ud_mnemonic_code.UD_Icall: retVal.Present = true; retVal.Branch = BranchInstruction.Call; retVal.AddBranchOfSameType = (label) => $"jmp {label}"; retVal.AddInstrToGetBranchTarget = () => new[] { $";; {asm}", $"{getTargetInstruction()} {targetRegister}, {asmOperands}", $"push {targetRegister64}" }; break; case ud_mnemonic_code.UD_Iret: retVal.Present = true; retVal.Branch = BranchInstruction.Ret; retVal.AddBranchOfSameType = (label) => $"jmp {label}"; retVal.AddInstrToGetBranchTarget = () => new[] { $";; {asm}", $"pop {targetRegister64}" }; break; // CONDITIONAL BRANCHES // loop case ud_mnemonic_code.UD_Iloop: retVal.Present = true; retVal.Branch = BranchInstruction.Loop; break; // loop if equal case ud_mnemonic_code.UD_Iloope: retVal.Present = true; retVal.Branch = BranchInstruction.Loope; break; // loop if not equal case ud_mnemonic_code.UD_Iloopne: retVal.Present = true; retVal.Branch = BranchInstruction.Loopne; break; // jump if above case ud_mnemonic_code.UD_Ija: retVal.Present = true; retVal.Branch = BranchInstruction.Ja; break; // jump if above or equal case ud_mnemonic_code.UD_Ijae: retVal.Present = true; retVal.Branch = BranchInstruction.Jae; break; // jump if below case ud_mnemonic_code.UD_Ijb: retVal.Present = true; retVal.Branch = BranchInstruction.Jb; break; // jump if below or equal case ud_mnemonic_code.UD_Ijbe: retVal.Present = true; retVal.Branch = BranchInstruction.Jbe; break; // jump if cx register is 0 case ud_mnemonic_code.UD_Ijcxz: retVal.Present = true; retVal.Branch = BranchInstruction.Jcxz; break; // jump if ecx register is 0 case ud_mnemonic_code.UD_Ijecxz: retVal.Present = true; retVal.Branch = BranchInstruction.Jecxz; break; // jump if greater case ud_mnemonic_code.UD_Ijg: retVal.Present = true; retVal.Branch = BranchInstruction.Jg; break; // jump if greater or equal case ud_mnemonic_code.UD_Ijge: retVal.Present = true; retVal.Branch = BranchInstruction.Jge; break; // jump if less than case ud_mnemonic_code.UD_Ijl: retVal.Present = true; retVal.Branch = BranchInstruction.Jl; break; // jump if less than or equal case ud_mnemonic_code.UD_Ijle: retVal.Present = true; retVal.Branch = BranchInstruction.Jle; break; // jump if not overflow case ud_mnemonic_code.UD_Ijno: retVal.Present = true; retVal.Branch = BranchInstruction.Jno; break; // jump if not parity case ud_mnemonic_code.UD_Ijnp: retVal.Present = true; retVal.Branch = BranchInstruction.Jnp; break; // jump if not sign case ud_mnemonic_code.UD_Ijns: retVal.Present = true; retVal.Branch = BranchInstruction.Jns; break; // jump if not zero case ud_mnemonic_code.UD_Ijnz: retVal.Present = true; retVal.Branch = BranchInstruction.Jnz; break; // jump if overflow case ud_mnemonic_code.UD_Ijo: retVal.Present = true; retVal.Branch = BranchInstruction.Jno; break; // jump if parity case ud_mnemonic_code.UD_Ijp: retVal.Present = true; retVal.Branch = BranchInstruction.Jp; break; // jump if rcx zero case ud_mnemonic_code.UD_Ijrcxz: retVal.Present = true; retVal.Branch = BranchInstruction.Jrcxz; break; // jump if signed case ud_mnemonic_code.UD_Ijs: retVal.Present = true; retVal.Branch = BranchInstruction.Js; break; // jump if zero case ud_mnemonic_code.UD_Ijz: retVal.Present = true; retVal.Branch = BranchInstruction.Jz; break; } return(retVal); }
private static void DumpAssembly(byte[] traceMemory) { BigInteger oldProgress = 0; var subRange = new byte[AssemblyUtil.MaxInstructionBytes]; using (StreamWriter sw2 = new StreamWriter(Specifics.AsmDumpFileName)) { using (FileStream asmSizesFs = new FileStream(Specifics.WriteAsmSizesDumpFileName, FileMode.Create)) { using (BinaryWriter asmSizesBw = new BinaryWriter(asmSizesFs)) { StreamWriter asmBranchesNasm = null; for (ulong i = 0; i < (ulong)traceMemory.Length; i++) { ulong instructionsPerFile = 50000; if (i % instructionsPerFile == 0) { asmBranchesNasm?.Close(); asmBranchesNasm = new StreamWriter(Specifics.WriteAsmBranchNasmDumpFileName + $"{(i / instructionsPerFile):D8}.asm"); asmBranchesNasm.Write(@"BITS 64 ; %idefine rip rel $+(.next-.prev) "); } if (asmBranchesNasm == null) { throw new Exception("shouldn't happen"); } Array.Copy(traceMemory, (int)i, subRange, 0, (int)Math.Min(AssemblyUtil.MaxInstructionBytes, (ulong)traceMemory.Length - i)); var progress = new BigInteger(i) * 100 / traceMemory.Length; //Console.WriteLine($"progress: {progress}"); if (progress != oldProgress) { Console.WriteLine($"decoding asm, progress: {progress}"); } oldProgress = progress; byte instructionLength = 0; Instruction instruction = null; try { instruction = AssemblyUtil.Disassemble(subRange); instructionLength = (byte)instruction.Length; } catch (IndexOutOfRangeException) { // ignore sw2.WriteLine("SharpDisasm: failed with IndexOutOfRangeException"); } catch (DecodeException) { // ignore sw2.WriteLine("SharpDisasm: failed with DecodeException"); } string asm; bool toStringWorked = false; try { asm = instruction?.ToString(); toStringWorked = true; } catch (IndexOutOfRangeException) { asm = "IndexOutOfRangeException"; } asmBranchesNasm.WriteLine($"instr{i}:"); asmBranchesNasm.WriteLine(".prev:"); if (toStringWorked && instruction != null && AssemblyUtil.IsNasmValid(instruction, asm)) { var maybeJump = BranchRewriteSimple.IsBranch(instruction); sw2.WriteLine($"{i:X}: {asm}"); if (maybeJump.Present) { var flags = (byte)(maybeJump.RipEquals | BranchRewriteSimple.RegisterMaskAndFlags.IsBranch); asmBranchesNasm.WriteLine($"db 0x{flags:X2}"); asmBranchesNasm.WriteLine($"{maybeJump.AddBranchOfSameType(".setrip")}"); asmBranchesNasm.WriteLine("clc"); // clear carry flag if we didn't jump == modify rip/rax asmBranchesNasm.WriteLine("jmp .next"); asmBranchesNasm.WriteLine(".setrip:"); foreach (var s in maybeJump.AddInstrToGetBranchTarget()) { asmBranchesNasm.WriteLine(s); } asmBranchesNasm.WriteLine("stc"); // set carry flag if we didn't jump == modify rip/rax } else { if (asm.Contains("rip")) { var freeRegister = BranchRewriteSimple.FindFreeRegister(asm); string ripRegister = freeRegister.ToString().ToLower(); asm = asm.Replace("rip", ripRegister); // flags asmBranchesNasm.WriteLine($"db 0x{(byte) freeRegister:X2}"); asmBranchesNasm.WriteLine(asm); } else { // flags asmBranchesNasm.WriteLine("db 0x0"); asmBranchesNasm.WriteLine("db " + AssemblyUtil.BytesToHexZeroX(instruction.Bytes, ",")); } } } else { asmBranchesNasm.WriteLine("; failed to decode"); // flags asmBranchesNasm.WriteLine($"db 0x{(byte) BranchRewriteSimple.RegisterMaskAndFlags.NotDecoded:X2}"); asmBranchesNasm.WriteLine("db " + AssemblyUtil.BytesToHexZeroX(subRange, ",")); } asmBranchesNasm.WriteLine(".next:"); asmBranchesNasm.WriteLine($"%if {AssemblyUtil.MaxBranchBytes}-(.next-.prev) < 0"); asmBranchesNasm.WriteLine("%error Not enough space allocated"); asmBranchesNasm.WriteLine("%endif"); asmBranchesNasm.WriteLine($"times {AssemblyUtil.MaxBranchBytes}-(.next-.prev) nop"); asmBranchesNasm.WriteLine(""); asmSizesBw.Write(instructionLength); } asmBranchesNasm?.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); }
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(); } } } }