public static TypeDefinition ImportFlagHelper(MetadataImage image, VMConstants constants) { // Clone flag helper class. var cloner = new MemberCloner(image); var flagHelperType = cloner.CloneType(VmHelperType); image.Assembly.Modules[0].TopLevelTypes.Add(flagHelperType); // Obtain static cctor. var constructor = flagHelperType.Methods.First(x => x.IsConstructor && x.IsStatic); var instructions = constructor.CilMethodBody.Instructions; instructions.Clear(); // Assign values of flags to the fields. foreach (var entry in constants.Flags.OrderBy(x => x.Value)) { instructions.Add(CilInstruction.Create(CilOpCodes.Ldc_I4, entry.Key)); instructions.Add(CilInstruction.Create(CilOpCodes.Stsfld, flagHelperType.Fields.First(x => x.Name == "FL_" + entry.Value.ToString()))); } instructions.Add(CilInstruction.Create(CilOpCodes.Ret)); return(flagHelperType); }
private void ApplyTransformations(ILCompilationUnit result, VMConstants constants) { var pipeline = new IILAstTransform[] { new StackFrameTransform(), new SsaTransform(), new TransformLoop("Expression Simplification", 5, new IChangeAwareILAstTransform[] { new VariableInliner(), new PushMinimizer(), new LogicSimplifier(), new FlagOperationSimplifier(constants), }), new PhiRemovalTransform(), }; foreach (var transform in pipeline) { if (transform is TransformLoop loop) { loop.TransformStart += (sender, args) => OnTransformStart(args); loop.TransformEnd += (sender, args) => OnTransformEnd(args); } Logger.Debug2(Tag, $"Applying {transform.Name}..."); OnTransformStart(new ILTransformEventArgs(result, transform, 1)); transform.ApplyTransformation(result, Logger); OnTransformEnd(new ILTransformEventArgs(result, transform, 1)); } }
public static TypeDefinition ImportFlagHelper(ModuleDefinition module, VMConstants constants) { // Clone flag helper class. var cloner = new MemberCloner(module); cloner.Include(VmHelperType); var result = cloner.Clone(); var flagHelperType = result.ClonedMembers.OfType <TypeDefinition>().First(); module.Assembly.Modules[0].TopLevelTypes.Add(flagHelperType); // Obtain static cctor. var constructor = flagHelperType.Methods.First(x => x.IsConstructor && x.IsStatic); var instructions = constructor.CilMethodBody.Instructions; instructions.Clear(); // Assign values of flags to the fields. foreach (var entry in constants.Flags.OrderBy(x => x.Value)) { instructions.Add(CilInstruction.CreateLdcI4(entry.Key)); instructions.Add(new CilInstruction(CilOpCodes.Stsfld, flagHelperType.Fields.First(x => x.Name == "FL_" + entry.Value.ToString()))); } instructions.Add(new CilInstruction(CilOpCodes.Ret)); return(flagHelperType); }
public InferenceDisassembler(VMConstants constants, KoiStream koiStream) { Constants = constants ?? throw new ArgumentNullException(nameof(constants)); KoiStream = koiStream ?? throw new ArgumentNullException(nameof(koiStream)); _decoder = new InstructionDecoder(constants, KoiStream.Contents.CreateReader()); _processor = new InstructionProcessor(this); _cfgBuilder = new ControlFlowGraphBuilder(); }
public uint?ResolveExitKey(ILogger logger, KoiStream koiStream, VMConstants constants, VMFunction function) { // Strategy: // // Find CALL references to the function, and try every possible key in the key space that // decrypts the next instruction to either a PUSHR_xxxx R0 or a PUSHR_DWORD SP, as they // appear in the post-call to either store the return value, or adjust SP to clean up // the stack. // Since the actual decryption of the opcode only uses the 8 least significant bits, we can // make an optimisation that shaves off around 8 bits of the key space. By attempting to // decrypt the first byte using just the first 8 bits, and verifying whether this output // results to one of the instructions mentioned in the above, we can quickly rule out many // potential keys. var callReferences = function.References.Where(r => r.ReferenceType == FunctionReferenceType.Call).ToArray(); if (callReferences.Length == 0) { logger.Warning(Tag, $"Cannot brute-force the exit key of function_{function.EntrypointAddress:X4} as it has no recorded call references."); return(null); } var data = koiStream.Data; // Find any call reference. foreach (var callReference in callReferences) { var call = callReference.Caller.Instructions[callReference.Offset]; long targetOffset = call.Offset + call.Size; // Go over all possible LSBs. for (uint lsb = 0; lsb < byte.MaxValue; lsb++) { // Check whether the LSB decodes to a PUSHR_xxxx. if (IsPotentialLSB(constants, data[targetOffset], lsb)) { // Go over all remaining 24 bits. for (uint i = 0; i < 0x00FFFFFF; i++) { uint currentKey = (i << 8) | lsb; // Try new key. if (IsValidKey(constants, data, targetOffset, currentKey)) { // We have found a key! return(currentKey); } } // for all other 24 bits. } // if potential LSB } // foreach LSB } // foreach call reference return(null); }
public ILCompilationUnit BuildAst( ControlFlowGraph graph, IFrameLayout frameLayout, VMConstants constants) { var result = BuildBasicAst(graph, frameLayout); OnInitialAstBuilt(result); ApplyTransformations(result, constants); return(result); }
public CodeGenerationContext(CilMethodBody methodBody, VMConstants constants, CilVariable flagVariable, TypeDefinition flagHelperType) { MethodBody = methodBody; Constants = constants; _flagVariable = flagVariable; VmHelperType = flagHelperType; ReferenceImporter = new ReferenceImporter(TargetModule); _arg0 = new CilVariable("__arg0", TargetModule.CorLibTypeFactory.UInt32); _arg1 = new CilVariable("__arg1", TargetModule.CorLibTypeFactory.UInt32); _result = new CilVariable("__result", TargetModule.CorLibTypeFactory.UInt32); }
public ILCompilationUnit BuildAst( ControlFlowGraph graph, IFrameLayout frameLayout, VMConstants constants) { Logger.Debug(Tag, "Building initial IL-AST..."); var result = BuildBasicAst(graph, frameLayout); OnInitialAstBuilt(result); Logger.Debug(Tag, "Applying IL-AST transforms..."); ApplyTransformations(result, constants); return(result); }
private static IFrameLayout InferLayoutFromLdftnReference(VMConstants constants, MetadataImage image, FunctionReference reference) { // LDFTN instructions reference a physical method, or an export defined in the export table containing // the signature of an intra-linked method. We can therefore reliably extract the necessary information // without too much guessing. var ldftn = reference.Caller.Instructions[reference.Offset]; var annotation = (LdftnAnnotation)ldftn.Annotation; var parameterTypes = new List <TypeSignature>(); TypeSignature returnType; bool hasThis; if (annotation.IsIntraLinked) { returnType = ((ITypeDefOrRef)image.ResolveMember(annotation.Signature.ReturnToken)) .ToTypeSignature(); foreach (var token in annotation.Signature.ParameterTokens) { parameterTypes.Add(((ITypeDefOrRef)image.ResolveMember(token)) .ToTypeSignature()); } hasThis = (annotation.Signature.Flags & constants.FlagInstance) != 0; } else { var methodSig = (MethodSignature)annotation.Method.Signature; foreach (var parameter in methodSig.Parameters) { parameterTypes.Add(parameter.ParameterType); } returnType = methodSig.ReturnType; hasThis = methodSig.HasThis; } return(new DefaultFrameLayout( image, parameterTypes, Array.Empty <TypeSignature>(), returnType, hasThis)); }
private static bool IsValidKey(VMConstants constants, byte[] data, long offset, uint key) { // Opcode byte. byte pushRDword = DecryptByte(data[offset], ref key); if (!constants.OpCodes.TryGetValue(pushRDword, out var opCode)) { return(false); } switch (opCode) { case ILCode.PUSHR_BYTE: case ILCode.PUSHR_WORD: case ILCode.PUSHR_DWORD: case ILCode.PUSHR_QWORD: case ILCode.PUSHR_OBJECT: break; default: return(false); } // Fixup byte. byte fixup = DecryptByte(data[offset + 1], ref key); // Register operand. byte rawRegister = DecryptByte(data[offset + 2], ref key); if (!constants.Registers.TryGetValue(rawRegister, out var register)) { return(false); } switch (register) { case VMRegisters.R0: case VMRegisters.SP: return(true); } return(false); }
public IFrameLayout DetectFrameLayout(VMConstants constants, MetadataImage image, VMExportInfo export) { var parameterTypes = new List <TypeSignature>(); foreach (var token in export.Signature.ParameterTokens) { parameterTypes.Add(((ITypeDefOrRef)image.ResolveMember(token)) .ToTypeSignature()); } var returnType = ((ITypeDefOrRef)image.ResolveMember(export.Signature.ReturnToken)) .ToTypeSignature(); bool hasThis = (export.Signature.Flags & constants.FlagInstance) != 0; return(new DefaultFrameLayout( image, parameterTypes, Array.Empty <TypeSignature>(), returnType, hasThis)); }
private static bool IsPotentialLSB(VMConstants constants, byte encryptedOpCode, uint lsb) { byte pushRDword = DecryptByte(encryptedOpCode, ref lsb); if (!constants.OpCodes.TryGetValue(pushRDword, out var opCode)) { return(false); } switch (opCode) { case ILCode.PUSHR_BYTE: case ILCode.PUSHR_WORD: case ILCode.PUSHR_DWORD: case ILCode.PUSHR_QWORD: case ILCode.PUSHR_OBJECT: return(true); default: return(false); } }
public IFrameLayout DetectFrameLayout(VMConstants constants, MetadataImage image, VMFunction function) { if (function.References.Count == 0) { throw new ArgumentException("Can only infer frame layout of a function that is at least referenced once."); } var exceptions = new List <Exception>(); // Order the references by reference type, as LDFTN references are more reliable. foreach (var reference in function.References.OrderBy(r => r.ReferenceType)) { try { switch (reference.ReferenceType) { case FunctionReferenceType.Call: return(InferLayoutFromCallReference(image, reference)); case FunctionReferenceType.Ldftn: return(InferLayoutFromLdftnReference(constants, image, reference)); default: throw new ArgumentOutOfRangeException(); } } catch (Exception ex) { exceptions.Add(ex); } } throw new AggregateException( $"Failed to infer the stack frame layout of function_{function.EntrypointAddress:X4}.", exceptions); }
public InstructionDecoder(VMConstants constants, IBinaryStreamReader reader) : this(constants, reader, 0) { }
public VMConstants CreateVmConstants() { var ilCodes = CreateEnumDictionary <ILCode>(); var flags = CreateEnumDictionary <VMFlags>(); var registers = CreateEnumDictionary <VMRegisters>(); var vmCalls = CreateEnumDictionary <VMCalls>(); var eCalls = CreateEnumDictionary <VMECallOpCode>(); var ehTypes = CreateEnumDictionary <EHType>(); var constants = new VMConstants(); var missing = new List <string>(); Process(OpCodes, ilCodes, constants.OpCodes); Process(Flags, flags, constants.Flags); Process(Registers, registers, constants.Registers); Process(VMCalls, vmCalls, constants.VMCalls); Process(ECalls, eCalls, constants.ECallOpCodes); Process(EHTypes, ehTypes, constants.EHTypes); missing.AddRange(ilCodes.Keys); missing.AddRange(flags.Keys); missing.AddRange(registers.Keys); missing.AddRange(vmCalls.Keys); missing.AddRange(eCalls.Keys); missing.AddRange(ehTypes.Keys); if (Misc.TryGetValue("HELPER_INIT", out byte value)) { constants.HelperInit = value; } else { missing.Add("HELPER_INIT"); } if (Misc.TryGetValue("FLAG_INSTANCE", out value)) { constants.FlagInstance = value; } else { missing.Add("FLAG_INSTANCE"); } int missingCount = missing.Count; if (missingCount > 0) { string suffix = string.Empty; if (missingCount > 5) { missing.RemoveRange(5, missingCount - 6); suffix = $" and {missingCount - 6} more"; } throw new ArgumentException( $"Incomplete configuration file. Missing constants {string.Join(", ", missing)}{suffix}."); } return(constants); }
public InstructionDecoder(VMConstants constants, IBinaryStreamReader reader, uint key) { _constants = constants; Reader = reader; CurrentKey = key; }
private VMConstants AutoDetectConstants(DevirtualisationContext context) { bool rename = context.Options.RenameSymbols; var constants = new VMConstants(); var fields = FindConstantFieldsAndValues(context); foreach (var field in fields) { constants.ConstantFields.Add(field.Key, field.Value); } // TODO: // We assume that the constants appear in the same order as they were defined in the original source code. // This means the metadata tokens of the fields are also in increasing order. However, this could cause // problems when a fork of the obfuscation tool is made which scrambles the order. A more robust way of // matching should be done that is order agnostic. var sortedFields = fields .OrderBy(x => x.Key.MetadataToken.ToUInt32()) .ToArray(); int currentIndex = 0; context.Logger.Debug2(Tag, "Resolving register mapping..."); for (int i = 0; i < (int)VMRegisters.Max; i++, currentIndex++) { constants.Registers.Add(sortedFields[currentIndex].Value, (VMRegisters)i); if (rename) { sortedFields[currentIndex].Key.Name = "REG_" + (VMRegisters)i; } } context.Logger.Debug2(Tag, "Resolving flag mapping..."); for (int i = 1; i < (int)VMFlags.Max; i <<= 1, currentIndex++) { constants.Flags.Add(sortedFields[currentIndex].Value, (VMFlags)i); if (rename) { sortedFields[currentIndex].Key.Name = "FLAG_" + (VMFlags)i; } } context.Logger.Debug2(Tag, "Resolving opcode mapping..."); for (int i = 0; i < (int)ILCode.Max; i++, currentIndex++) { constants.OpCodes.Add(sortedFields[currentIndex].Value, (ILCode)i); if (rename) { sortedFields[currentIndex].Key.Name = "OPCODE_" + (ILCode)i; } } context.Logger.Debug2(Tag, "Resolving vmcall mapping..."); for (int i = 0; i < (int)VMCalls.Max; i++, currentIndex++) { constants.VMCalls.Add(sortedFields[currentIndex].Value, (VMCalls)i); if (rename) { sortedFields[currentIndex].Key.Name = "VMCALL_" + (VMCalls)i; } } context.Logger.Debug2(Tag, "Resolving helper init ID..."); if (rename) { sortedFields[currentIndex].Key.Name = "HELPER_INIT"; } constants.HelperInit = sortedFields[currentIndex++].Value; context.Logger.Debug2(Tag, "Resolving ECall mapping..."); for (int i = 0; i < 4; i++, currentIndex++) { constants.ECallOpCodes.Add(sortedFields[currentIndex].Value, (VMECallOpCode)i); if (rename) { sortedFields[currentIndex].Key.Name = "ECALL_" + (VMECallOpCode)i; } } context.Logger.Debug2(Tag, "Resolving function signature flags..."); sortedFields[currentIndex].Key.Name = "FLAG_INSTANCE"; constants.FlagInstance = sortedFields[currentIndex++].Value; context.Logger.Debug2(Tag, "Resolving exception handler types..."); for (int i = 0; i < (int)EHType.Max; i++, currentIndex++) { constants.EHTypes.Add(sortedFields[currentIndex].Value, (EHType)i); if (rename) { sortedFields[currentIndex].Key.Name = "EH_" + (EHType)i; } } return(constants); }
public CilMethodBodyGenerator(VMConstants constants, TypeDefinition flagHelperType) { _constants = constants ?? throw new ArgumentNullException(nameof(constants)); _flagHelperType = flagHelperType ?? throw new ArgumentNullException(nameof(flagHelperType)); }
public FlagOperationSimplifier(VMConstants constants) { _constants = constants; }
public uint?ResolveExitKey(ILogger logger, KoiStream koiStream, VMConstants constants, VMFunction function) { // Strategy: // // Find CALL references to the function, and try every possible key in the key space that // decrypts the next instruction to either a PUSHR_xxxx R0 or a PUSHR_DWORD SP, as they // appear in the post-call to either store the return value, or adjust SP to clean up // the stack. // Since the actual decryption of the opcode only uses the 8 least significant bits, we can // make an optimisation that shaves off around 8 bits of the key space. By attempting to // decrypt the first byte using just the first 8 bits, and verifying whether this output // results to one of the instructions mentioned in the above, we can quickly rule out many // potential keys. var callReferences = function.References .Where(r => r.ReferenceType == FunctionReferenceType.Call) .ToArray(); if (callReferences.Length == 0) { logger.Warning(Tag, $"Cannot brute-force the exit key of function_{function.EntrypointAddress:X4} as it has no recorded call references."); return(null); } var reader = koiStream.Contents.CreateReader(); byte[] encryptedOpCodes = new byte[3]; var watch = new Stopwatch(); // Find any call reference. for (int i = 0; i < callReferences.Length; i++) { var callReference = callReferences[i]; logger.Debug(Tag, $"Started bruteforcing key for call reference {i.ToString()} ({callReference.ToString()})."); watch.Restart(); var call = callReference.Caller.Instructions[callReference.Offset]; long targetOffset = call.Offset + call.Size; reader.FileOffset = (uint)targetOffset; reader.ReadBytes(encryptedOpCodes, 0, encryptedOpCodes.Length); // Go over all possible LSBs. for (uint lsb = 0; lsb < byte.MaxValue; lsb++) { // Check whether the LSB decodes to a PUSHR_xxxx. if (IsPotentialLSB(constants, encryptedOpCodes[0], lsb)) { // Go over all remaining 24 bits. for (uint j = 0; j < 0x00FFFFFF; j++) { uint currentKey = (j << 8) | lsb; // Try new key. if (IsValidKey(constants, encryptedOpCodes, currentKey)) { // We have found a key! watch.Stop(); logger.Debug(Tag, $"Found key after {watch.Elapsed.TotalSeconds:0.00}s."); return(currentKey); } } // for all other 24 bits. } // if potential LSB } // foreach LSB watch.Stop(); logger.Debug(Tag, $"Exhausted key space after {watch.Elapsed.TotalSeconds:0.00}s without finding key."); } return(null); }