private void PerformFlowControl(VMFunction function, ILInstruction instruction, List <ProgramState> nextStates, ProgramState next) { switch (instruction.OpCode.FlowControl) { case ILFlowControl.Next: { // Normal flow. nextStates.Add(next); break; } case ILFlowControl.Jump: { function.BlockHeaders.Add((long)next.IP); // Unconditional jump target. var metadata = InferJumpTargets(instruction); if (metadata != null) { next.IP = metadata.InferredJumpTargets[0]; function.BlockHeaders.Add((long)next.IP); nextStates.Add(next); } break; } case ILFlowControl.ConditionalJump: { // We need to consider that the condition might be true or false. // Conditional branch(es): var metadata = InferJumpTargets(instruction); if (metadata != null) { foreach (var target in metadata.InferredJumpTargets) { var branch = next.Copy(); branch.IP = target; nextStates.Add(branch); function.BlockHeaders.Add((long)branch.IP); } } // Fall through branch: nextStates.Add(next); function.BlockHeaders.Add((long)next.IP); break; } default: { throw new ArgumentOutOfRangeException(); } } }
private IEnumerable <ProgramState> ProcessCall(VMFunction function, ILInstruction instruction, ProgramState next) { var symbolicAddress = next.Stack.Pop(); instruction.Dependencies.AddOrMerge(0, symbolicAddress); uint address = (uint)symbolicAddress.InferStackValue().U8; if (address >= KoiStream.Contents.GetPhysicalSize()) { Logger.Warning(Tag, $"Call instruction at IL_{instruction.Offset:X4} " + $"transfers control to a function outside of the KoiVM stream (IL_{address:X4}."); } var callee = _disassembler.GetOrCreateFunctionInfo(address, next.Key); callee.References.Add(new FunctionReference(function, instruction.Offset, FunctionReferenceType.Call, callee)); instruction.Annotation = new CallAnnotation { Function = callee, InferredPopCount = 1, InferredPushCount = 1, }; if (!callee.ExitKey.HasValue) { // Exit key of called function is not known yet. // We cannot continue disassembly yet because of the encryption used in KoiVM. function.UnresolvedOffsets.Add(instruction.Offset); Logger.Debug(Tag, $"Stopped at call instruction at IL_{instruction.Offset:X4} " + $"as exit key of function_{address:X4} is not known yet."); return(Enumerable.Empty <ProgramState>()); } else { // Exit key is known, we can continue disassembly! function.UnresolvedOffsets.Remove(instruction.Offset); next.Key = callee.ExitKey.Value; return(new[] { next }); } }
private void ProcessRet(VMFunction function, ILInstruction instruction, ProgramState next) { // Pop return address. var symbolicReturnAddress = next.Stack.Pop(); instruction.Dependencies.AddOrMerge(0, symbolicReturnAddress); // Add metadata. instruction.Annotation = new Annotation { InferredPopCount = instruction.Dependencies.Count, InferredPushCount = 0 }; if (!next.IgnoreExitKey) { // Returns indicate the end of the method, and therefore also determine the encryption key of the // instruction after a call instruction. Store this information so it can be used to continue // disassembly at these points later in time. if (function.ExitKey.HasValue) { if (function.ExitKey != next.Key) { Logger.Debug2(Tag, $"Resolved an alternative exit key ({next.Key:X8}) at offset " + $"IL_{instruction.Offset:X4}for function_{function.EntrypointAddress:X4}."); } } else { Logger.Debug2(Tag, $"Inferred exit key {next.Key:X8} at offset IL_{instruction.Offset:X4}."); function.ExitKey = next.Key; } } }
public IList <ProgramState> GetNextStates(VMFunction function, ILInstruction instruction, ProgramState next) { var nextStates = new List <ProgramState>(1); var metadata = instruction.Annotation as VCallAnnotation; int stackSize = next.Stack.Count; var symbolicVCallValue = next.Stack.Pop(); instruction.Dependencies.AddOrMerge(0, symbolicVCallValue); var vcall = metadata?.VMCall ?? Constants.VMCalls[symbolicVCallValue.InferStackValue().U1]; bool returnNextState = true; switch (vcall) { case VMCalls.BOX: ProcessBox(instruction, next); break; case VMCalls.CAST: ProcessCast(instruction, next); break; case VMCalls.CKFINITE: ProcessCkFinite(instruction, next); break; case VMCalls.CKOVERFLOW: ProcessCkOverflow(instruction, next); break; case VMCalls.ECALL: ProcessECall(instruction, next); break; case VMCalls.INITOBJ: ProcessInitObj(instruction, next); break; case VMCalls.LOCALLOC: ProcessLocalloc(instruction, next); break; case VMCalls.LDFLD: ProcessLdfld(instruction, next); break; case VMCalls.LDFTN: ProcessLdftn(function, instruction, next); break; case VMCalls.RANGECHK: ProcessRangeChk(instruction, next); break; case VMCalls.SIZEOF: ProcessSizeOf(instruction, next); break; case VMCalls.STFLD: ProcessStfld(instruction, next); break; case VMCalls.THROW: ProcessThrow(instruction, next); returnNextState = false; break; case VMCalls.TOKEN: ProcessToken(instruction, next); break; case VMCalls.UNBOX: ProcessUnbox(instruction, next); break; case VMCalls.EXIT: case VMCalls.BREAK: throw new NotSupportedException($"VCALL {vcall} is not supported."); default: throw new ArgumentOutOfRangeException(); } if (returnNextState) { nextStates.Add(next); } if ((next.Stack.Count - stackSize) != instruction.Annotation.InferredStackDelta) { // Should not happen, but sanity checks are always nice to check whether we have implemented the // vcall processors correctly. throw new DisassemblyException($"VCall at offset IL_{instruction.Offset:X4} ({vcall}) inferred stack delta does not match the emulated stack delta."); } return(nextStates); }
private void ProcessLdftn(VMFunction currentFunction, ILInstruction instruction, ProgramState next) { var symbolicMethod = next.Stack.Pop(); var symbolicObject = next.Stack.Pop(); instruction.Dependencies.AddOrMerge(1, symbolicMethod); instruction.Dependencies.AddOrMerge(2, symbolicObject); var methodSlot = symbolicMethod.InferStackValue(); if (symbolicObject.Type == VMType.Object) { // This is a virtual dispatched ldftn. var method = (IMethodDescriptor)KoiStream.ResolveReference(Logger, instruction.Offset, methodSlot.U4, TableIndex.Method, TableIndex.MemberRef, TableIndex.MethodSpec); instruction.Annotation = new LdftnAnnotation(method, true); } else { // This is a static ldftn. var obj = symbolicObject.InferStackValue(); if (obj.U8 != 0) { // We are dealing with intra-linked methods. // Pop entry key. var symbolicEntryKey = next.Stack.Pop(); instruction.Dependencies.AddOrMerge(3, symbolicEntryKey); uint entryKey = symbolicEntryKey.InferStackValue().U4; // Obtain export containing signature. uint exportId = obj.U4; var exportInfo = KoiStream.Exports[exportId]; // Get the function at the pushed address and register reference. uint codeAddress = (uint)methodSlot.U8; var function = _disassembler.GetOrCreateFunctionInfo(codeAddress, entryKey); function.References.Add(new FunctionReference( currentFunction, instruction.Offset, FunctionReferenceType.Ldftn, function)); instruction.Annotation = new LdftnAnnotation(function, exportInfo.Signature); } else { // Resolve method. var method = (IMethodDescriptor)KoiStream.ResolveReference(Logger, instruction.Offset, methodSlot.U4, TableIndex.Method, TableIndex.MemberRef, TableIndex.MethodSpec); instruction.Annotation = new LdftnAnnotation(method, false); } } next.Stack.Push(new SymbolicValue(instruction, VMType.Pointer)); instruction.Annotation.InferredPopCount = instruction.Dependencies.Count; instruction.Annotation.InferredPushCount = 1; }
public IList <ProgramState> GetNextStates( VMFunction function, ProgramState currentState, ILInstruction instruction, uint nextKey) { var nextStates = new List <ProgramState>(1); var next = currentState.Copy(); next.IP += (ulong)instruction.Size; next.Key = nextKey; if (instruction.OpCode.AffectsFlags) { next.Registers[VMRegisters.FL] = new SymbolicValue(instruction, VMType.Byte); } switch (instruction.OpCode.Code) { case ILCode.CALL: nextStates.AddRange(ProcessCall(function, instruction, next)); break; case ILCode.RET: ProcessRet(function, instruction, next); function.BlockHeaders.Add((long)next.IP); break; case ILCode.VCALL: // VCalls have embedded opcodes with different behaviours. nextStates.AddRange(_vCallProcessor.GetNextStates(function, instruction, next)); break; case ILCode.TRY: // TRY opcodes have a very distinct behaviour from the other common opcodes. nextStates.AddRange(ProcessTry(instruction, next)); foreach (var state in nextStates) { function.BlockHeaders.Add((long)state.IP); } break; case ILCode.LEAVE: nextStates.AddRange(ProcessLeave(instruction, next)); function.BlockHeaders.Add((long)next.IP); break; case ILCode.POP when(VMRegisters) instruction.Operand == VMRegisters.SP: nextStates.Add(ProcessPopSp(instruction, next)); break; case ILCode.SWT: nextStates.AddRange(ProcessSwt(instruction, next)); function.BlockHeaders.UnionWith(nextStates.Select(s => (long)s.IP)); break; default: { // Push/pop necessary values from stack. int initial = next.Stack.Count; PopSymbolicValues(instruction, next); int popCount = initial - next.Stack.Count; initial = next.Stack.Count; PushSymbolicValues(instruction, next); int pushCount = next.Stack.Count - initial; // Apply control flow. PerformFlowControl(function, instruction, nextStates, next); if (instruction.Annotation == null) { instruction.Annotation = new Annotation(); } instruction.Annotation.InferredPopCount = popCount; instruction.Annotation.InferredPushCount = pushCount; break; } } return(nextStates); }
public FunctionEventArgs(VMFunction function) { Function = function; }
private void ProcessLdftn(VMFunction currentFunction, ILInstruction instruction, ProgramState next) { var symbolicMethod = next.Stack.Pop(); var symbolicObject = next.Stack.Pop(); instruction.Dependencies.AddOrMerge(1, symbolicMethod); instruction.Dependencies.AddOrMerge(2, symbolicObject); var methodSlot = symbolicMethod.InferStackValue(); if (symbolicObject.Type == VMType.Object) { // TODO: get base method. throw new DisassemblyException( $"Failed to process the LDFTN instruction at offset IL_{instruction.Offset:X4}.", new NotSupportedException("LDFTN instructions based on objects is not supported yet.")); } else { var obj = symbolicObject.InferStackValue(); if (obj.U8 != 0) { // We are dealing with intra-linked methods. // Pop entry key. var symbolicEntryKey = next.Stack.Pop(); instruction.Dependencies.AddOrMerge(3, symbolicEntryKey); uint entryKey = symbolicEntryKey.InferStackValue().U4; // Obtain export containing signature. uint exportId = obj.U4; var exportInfo = KoiStream.Exports[exportId]; // Get the function at the pushed address and register reference. uint codeAddress = (uint)methodSlot.U8; var function = _disassembler.GetOrCreateFunctionInfo(codeAddress, entryKey); function.References.Add(new FunctionReference( currentFunction, instruction.Offset, FunctionReferenceType.Ldftn, function)); instruction.Annotation = new LdftnAnnotation(function, exportInfo.Signature); } else { // Resolve method. var method = (ICallableMemberReference)KoiStream.ResolveReference(Logger, instruction.Offset, methodSlot.U4, MetadataTokenType.Method, MetadataTokenType.MemberRef, MetadataTokenType.MethodSpec); instruction.Annotation = new LdftnAnnotation(method); } } next.Stack.Push(new SymbolicValue(instruction, VMType.Pointer)); instruction.Annotation.InferredPopCount = instruction.Dependencies.Count; instruction.Annotation.InferredPushCount = 1; }