From be54115842642d39bc2142a6935d8d60d95bfdab Mon Sep 17 00:00:00 2001 From: CursedSheep Date: Fri, 23 Jun 2023 00:30:17 +0800 Subject: [PATCH 1/9] Added Read Order Detection and Improved Resolver Read Order Analyzer has been added for VM Method Reader and Operand Reader --- .../InlineOperands/VMInlineOperand.cs | 19 +- src/EazyDevirt/Core/Architecture/VMMethod.cs | 57 ++++-- src/EazyDevirt/Core/IO/Resolver.cs | 21 ++- .../DevirtualizationContext.cs | 2 + .../Devirtualization/Devirtualizer.cs | 1 + .../Pipeline/MethodDevirtualizer.cs | 2 +- .../Pipeline/ReadOrderAnalyzer.cs | 168 ++++++++++++++++++ .../Patterns/OperandResolverPattern.cs | 28 +++ .../Patterns/ReadVMMethodPattern.cs | 28 +++ 9 files changed, 299 insertions(+), 27 deletions(-) create mode 100644 src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs create mode 100644 src/EazyDevirt/PatternMatching/Patterns/OperandResolverPattern.cs create mode 100644 src/EazyDevirt/PatternMatching/Patterns/ReadVMMethodPattern.cs diff --git a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperand.cs b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperand.cs index 813f4d0..7e2309f 100644 --- a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperand.cs +++ b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperand.cs @@ -1,4 +1,6 @@ -namespace EazyDevirt.Core.Architecture.InlineOperands; +using EazyDevirt.Devirtualization; + +namespace EazyDevirt.Core.Architecture.InlineOperands; // thank you to saneki @@ -85,15 +87,20 @@ public VMInlineOperand(ValueType valueType, int value) ValueType = valueType; Value = value; } - - public VMInlineOperand(BinaryReader reader) + + public VMInlineOperand(ValueType valueType, VMInlineOperandData data) { - ValueType = (ValueType)reader.ReadByte(); + ValueType = valueType; + Data = data; + } + public static VMInlineOperand ReadInternal(DevirtualizationContext ctx, BinaryReader reader) + { + var ValueType = ctx.OperandReadOrder[reader.ReadByte()]; if (ValueType == ValueType.Token) - Value = reader.ReadInt32(); + return new VMInlineOperand(ValueType, reader.ReadInt32()); else - Data = VMInlineOperandData.Read(reader); + return new VMInlineOperand(ValueType, VMInlineOperandData.Read(reader)); } public static VMInlineOperand ReadInternal(BinaryReader reader) => new(ValueType.Position, reader.ReadInt32()); diff --git a/src/EazyDevirt/Core/Architecture/VMMethod.cs b/src/EazyDevirt/Core/Architecture/VMMethod.cs index 316bd38..bbd0ec2 100644 --- a/src/EazyDevirt/Core/Architecture/VMMethod.cs +++ b/src/EazyDevirt/Core/Architecture/VMMethod.cs @@ -46,21 +46,42 @@ internal record VMMethodInfo public ITypeDefOrRef DeclaringType { get; set; } public ITypeDefOrRef ReturnType { get; set; } - - public VMMethodInfo(BinaryReader reader) + + public VMMethodInfo(BinaryReader reader, List ReadOrder) { - VMDeclaringType = reader.ReadInt32(); - Name = reader.ReadString(); - BindingFlags = reader.ReadByte(); - VMReturnType = reader.ReadInt32(); + foreach (VMMethodField field in ReadOrder) + { + switch (field) + { + case VMMethodField.VMDeclaringType: + VMDeclaringType = reader.ReadInt32(); + break; + + case VMMethodField.Name: + Name = reader.ReadString(); + break; + + case VMMethodField.BindingFlags: + BindingFlags = reader.ReadByte(); + break; + + case VMMethodField.ReturnType: + VMReturnType = reader.ReadInt32(); + break; - VMLocals = new List(reader.ReadInt16()); - for (var i = 0; i < VMLocals.Capacity; i++) - VMLocals.Add(new VMLocal(reader.ReadInt32())); - - VMParameters = new List(reader.ReadInt16()); - for (var i = 0; i < VMParameters.Capacity; i++) - VMParameters.Add(new VMParameter(reader.ReadInt32(), reader.ReadBoolean())); + case VMMethodField.Locals: + VMLocals = new List(reader.ReadInt16()); + for (var i = 0; i < VMLocals.Capacity; i++) + VMLocals.Add(new VMLocal(reader.ReadInt32())); + break; + + case VMMethodField.Parameters: + VMParameters = new List(reader.ReadInt16()); + for (var i = 0; i < VMParameters.Capacity; i++) + VMParameters.Add(new VMParameter(reader.ReadInt32(), reader.ReadBoolean())); + break; + } + } } public override string ToString() => @@ -70,6 +91,16 @@ public override string ToString() => $"DeclaringType: {DeclaringType.FullName} | ReturnType: {ReturnType.FullName}"; } +internal enum VMMethodField +{ + VMDeclaringType, + Name, + BindingFlags, + ReturnType, + Locals, + Parameters +} + internal record VMLocal(int VMType) { public int VMType { get; } = VMType; diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs index e2d7195..b04465f 100644 --- a/src/EazyDevirt/Core/IO/Resolver.cs +++ b/src/EazyDevirt/Core/IO/Resolver.cs @@ -1,6 +1,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.DotNet.Signatures.Types.Parsing; using EazyDevirt.Core.Architecture; using EazyDevirt.Core.Architecture.InlineOperands; using EazyDevirt.Devirtualization; @@ -40,10 +41,16 @@ private TypeSignature ApplySigModifiers(TypeSignature baseTypeSig, Stack { Ctx.VMResolverStream.Seek(position, SeekOrigin.Begin); - var inlineOperand = new VMInlineOperand(VMStreamReader); + var inlineOperand = VMInlineOperand.ReadInternal(this.Ctx, VMStreamReader); if (inlineOperand.IsToken) return Ctx.Module.LookupMember(inlineOperand.Token); - + + if (inlineOperand.Data is VMUserStringData strData) + { + //attempt to resolve type from string (mabye not present in eaz 2020 and above) + return TypeNameParser.Parse(this.Ctx.Module, strData.Value).ToTypeDefOrRef(); + } + if (!inlineOperand.HasData || inlineOperand.Data is not VMTypeData data) throw new Exception("VM inline operand expected to have type data!"); @@ -108,7 +115,7 @@ private TypeSignature ApplySigModifiers(TypeSignature baseTypeSig, Stack { Ctx.VMResolverStream.Seek(position, SeekOrigin.Begin); - var inlineOperand = new VMInlineOperand(VMStreamReader); + var inlineOperand = VMInlineOperand.ReadInternal(this.Ctx, VMStreamReader); if (inlineOperand.IsToken) return Ctx.Module.LookupMember(inlineOperand.Token); @@ -191,7 +198,7 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu { Ctx.VMResolverStream.Seek(position, SeekOrigin.Begin); - var inlineOperand = new VMInlineOperand(VMStreamReader); + var inlineOperand = VMInlineOperand.ReadInternal(this.Ctx, VMStreamReader); if (inlineOperand.IsToken) return Ctx.Module.LookupMember(inlineOperand.Token); @@ -319,7 +326,7 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu { Ctx.VMResolverStream.Seek(position, SeekOrigin.Begin); - var inlineOperand = new VMInlineOperand(VMStreamReader); + var inlineOperand = VMInlineOperand.ReadInternal(this.Ctx, VMStreamReader); if (inlineOperand.IsToken) { var member = Ctx.Module.LookupMember(inlineOperand.Token); @@ -362,7 +369,7 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu { Ctx.VMResolverStream.Seek(position, SeekOrigin.Begin); - var methodInfo = new VMMethodInfo(VMStreamReader); + var methodInfo = new VMMethodInfo(VMStreamReader, this.Ctx.VMMethodReadOrder); var declaringType = ResolveType(methodInfo.VMDeclaringType); if (declaringType is null) @@ -389,7 +396,7 @@ public string ResolveString(int position) { Ctx.VMResolverStream.Seek(position, SeekOrigin.Begin); - var inlineOperand = new VMInlineOperand(VMStreamReader); + var inlineOperand = VMInlineOperand.ReadInternal(this.Ctx, VMStreamReader); if (inlineOperand.IsToken) return Ctx.Module.LookupString(inlineOperand.Token); diff --git a/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs b/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs index 6f118a2..933ae24 100644 --- a/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs +++ b/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs @@ -42,6 +42,8 @@ internal record DevirtualizationContext public TypeDefinition VMDeclaringType { get; set; } public VMCipherStream VMStream { get; set; } public VMCipherStream VMResolverStream { get; set; } + public List VMMethodReadOrder { get; set; } + public List OperandReadOrder { get; set; } public int PositionCryptoKey { get; set; } public int MethodCryptoKey { get; set; } diff --git a/src/EazyDevirt/Devirtualization/Devirtualizer.cs b/src/EazyDevirt/Devirtualization/Devirtualizer.cs index d84e565..4537186 100644 --- a/src/EazyDevirt/Devirtualization/Devirtualizer.cs +++ b/src/EazyDevirt/Devirtualization/Devirtualizer.cs @@ -13,6 +13,7 @@ public Devirtualizer(DevirtualizationContext ctx) new ResourceParsing(ctx), new OpCodeMapping(ctx), new MethodDiscovery(ctx), + new ReadOrderAnalyzer(ctx), new MethodDevirtualizer(ctx), }; } diff --git a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs index e438d27..70102e8 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs @@ -39,7 +39,7 @@ public override bool Run() private void ReadVMMethod(VMMethod vmMethod) { - vmMethod.MethodInfo = new VMMethodInfo(VMStreamReader); + vmMethod.MethodInfo = new VMMethodInfo(VMStreamReader, this.Ctx.VMMethodReadOrder); ReadExceptionHandlers(vmMethod); diff --git a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs new file mode 100644 index 0000000..d75bd8a --- /dev/null +++ b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs @@ -0,0 +1,168 @@ +using AsmResolver.DotNet.Serialized; +using EazyDevirt.Core.Abstractions; +using EazyDevirt.PatternMatching.Patterns; +using EazyDevirt.PatternMatching; +using AsmResolver.DotNet; +using EazyDevirt.Core.Architecture; +using AsmResolver.PE.DotNet.Cil; + +using ValueType = EazyDevirt.Core.Architecture.InlineOperands.ValueType; + +namespace EazyDevirt.Devirtualization +{ + internal class ReadOrderAnalyzer : StageBase + { + public ReadOrderAnalyzer(DevirtualizationContext ctx) : base(ctx) + { + } + + //Set field function order is the same accross different versions + VMMethodField[] FieldOrder = + { + VMMethodField.Locals, + VMMethodField.Parameters, + VMMethodField.Name, + VMMethodField.ReturnType, + VMMethodField.VMDeclaringType, + VMMethodField.BindingFlags + }; + + public override bool Run() + { + this.Ctx.OperandReadOrder = FindOperandResolver(this.Ctx); + if (this.Ctx.OperandReadOrder.Count == 0) + { + Ctx.Console.Error($"Failed to find Correct Reading order for Operand Reader!"); + return false; + } + + Ctx.Console.Success("Found Correct Operand Read Order!"); + + MethodDefinition? vmFuncReader = FindMethodReadOrderFunction(this.Ctx); + if (vmFuncReader == null) + { + Ctx.Console.Error($"Failed to find VM Method Reader"); + return false; + } + + var readOrder = AnalyzeReadOrder(vmFuncReader); + + if (readOrder.Count != FieldOrder.Length) + { + Ctx.Console.Error($"Failed to analyze VMMethod Read Order {vmFuncReader?.MetadataToken}"); + return false; + } + + string orderStrFormat = ": " + string.Join(", ", readOrder.Select((d, i) => string.Format("[{0}] {1}", i + 1, d))); + + if (Ctx.Options.VeryVerbose) + Ctx.Console.InfoStr(orderStrFormat, "Correct VMMethod Read Order"); + Ctx.Console.Success("Found Correct Method Read Order!"); + + this.Ctx.VMMethodReadOrder = readOrder; + return true; + } + + private List AnalyzeReadOrder(MethodDefinition readFunc) + { + List readOrder = new(); + var funcRetType = readFunc?.Signature?.ReturnType.Resolve(); + + //find all set function inside VMMethod Type (order is same across different versions) + var methodsToAnalyze = funcRetType?.Methods + .Where(m => m.Parameters.Count == 1) + .OrderBy(z => z.MetadataToken.ToInt32()) + .Zip(FieldOrder, (fieldType, readerSetFunc) => (readerSetFunc, fieldType)) + .ToDictionary(x => x.fieldType, x => x.readerSetFunc); + + var instructions = readFunc?.CilMethodBody?.Instructions; + int i, j; + for (i = 0, j = 0; j < methodsToAnalyze?.Count; i++) + { + if (instructions?[i].OpCode == CilOpCodes.Callvirt && instructions[i].Operand is SerializedMethodDefinition smd + && methodsToAnalyze.ContainsKey(smd)) + { + readOrder.Add(methodsToAnalyze[smd]); + j++; + + } + } + + return readOrder; + } + + private List FindOperandResolver(DevirtualizationContext ctx) + { + List order = new(); + foreach (var t in ctx.Module.GetAllTypes()) + { + foreach (var method in t.Methods) + { + if (!method.HasMethodBody || method?.CilMethodBody == null || method?.CilMethodBody?.Instructions.Count == 0 || method?.Parameters.Count != 1) + continue; + + var matchedInstrs = PatternMatcher.GetAllMatchingInstructions(new OperandResolverPattern(), method); + if (matchedInstrs.Count > 0) + { + var lastIndex = method.CilMethodBody.Instructions.GetIndexByOffset(matchedInstrs.First().Last().Offset); + var currInstr = method.CilMethodBody.Instructions[lastIndex + 1]; + var secInstr = method.CilMethodBody.Instructions[lastIndex + 2]; + + if (currInstr.IsLdcI4() && secInstr.IsConditionalBranch()) //2021 sample + { + if (currInstr.GetLdcI4Constant() == 1) + { + order.Add(ValueType.Position); + order.Add(ValueType.Token); + return order; + } + else + { + //from older sample + order.Add(ValueType.Position); + order.Add(ValueType.Token); + return order; + } + } + else + { + order.Add(ValueType.Position); + order.Add(ValueType.Token); + return order; + } + } + + } + } + + return order; + } + + private MethodDefinition? FindMethodReadOrderFunction(DevirtualizationContext ctx) + { + foreach (var t in ctx.Module.GetAllTypes()) + { + foreach (var method in t.Methods) + { + if (!method.HasMethodBody || method?.CilMethodBody?.Instructions.Count == 0 || method?.Parameters.Count != 3) + continue; + + if (method.Parameters[0].ParameterType.FullName == "System.IO.Stream" + && method.Parameters[1].ParameterType.FullName == "System.Int64" + && method.Parameters[2].ParameterType.FullName == "System.String") + { + var matchedInstrs = PatternMatcher.GetAllMatchingInstructions(new ReadVMMethodPattern(), method); + if (matchedInstrs.Count > 0) + { + var ReadVMFunc = matchedInstrs.First()[4].Operand as SerializedMethodDefinition; + return ReadVMFunc; + } + } + + } + } + + return null; + } + } +} diff --git a/src/EazyDevirt/PatternMatching/Patterns/OperandResolverPattern.cs b/src/EazyDevirt/PatternMatching/Patterns/OperandResolverPattern.cs new file mode 100644 index 0000000..a877239 --- /dev/null +++ b/src/EazyDevirt/PatternMatching/Patterns/OperandResolverPattern.cs @@ -0,0 +1,28 @@ +using AsmResolver.PE.DotNet.Cil; +using EazyDevirt.Core.Abstractions.Interfaces; + +namespace EazyDevirt.PatternMatching.Patterns +{ + internal class OperandResolverPattern : IPattern + { + public IList Pattern => new List() + { + CilOpCodes.Callvirt, //17 002E callvirt instance int64 '\u000e\u2004'::'\u000e\u2004\u2000\u2000\u2009\u2009\u0002'(int64, int32) + CilOpCodes.Pop, //18 0033 pop + CilOpCodes.Newobj, //19 0034 newobj instance void '\u000e\u2000'::.ctor() + CilOpCodes.Stloc_1, //20 0039 stloc.1 + CilOpCodes.Ldloc, //21 003A ldloc.1 + CilOpCodes.Ldarg_0, //22 003B ldarg.0 + CilOpCodes.Ldfld, //23 003C ldfld class '\u000f\u2002' '\b\u2008'::'\b\u2000' + CilOpCodes.Callvirt, //24 0041 callvirt instance uint8 '\u000f\u2002'::'\u0002'() + CilOpCodes.Callvirt, //25 0046 callvirt instance void '\u000e\u2000'::'\u0002'(uint8) + CilOpCodes.Ldloc_1, //26 004B ldloc.1 + CilOpCodes.Callvirt //27 004C callvirt instance uint8 '\u000e\u2000'::'\u0002'() + // .... + }; + + public bool InterchangeStlocOpCodes => true; + public bool InterchangeLdlocOpCodes => true; + public bool MatchEntireBody => false; + } +} diff --git a/src/EazyDevirt/PatternMatching/Patterns/ReadVMMethodPattern.cs b/src/EazyDevirt/PatternMatching/Patterns/ReadVMMethodPattern.cs new file mode 100644 index 0000000..cae118f --- /dev/null +++ b/src/EazyDevirt/PatternMatching/Patterns/ReadVMMethodPattern.cs @@ -0,0 +1,28 @@ +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Serialized; +using AsmResolver.PE.DotNet.Cil; +using EazyDevirt.Core.Abstractions.Interfaces; + +namespace EazyDevirt.PatternMatching.Patterns +{ + internal class ReadVMMethodPattern : IPattern + { + public IList Pattern => new List + { + CilOpCodes.Ldarg_0, //34 0052 ldarg.0 + CilOpCodes.Ldarg_0, //35 0053 ldarg.0 + CilOpCodes.Ldarg_0, //36 0054 ldarg.0 + CilOpCodes.Ldfld, //37 0055 ldfld class EazBinaryReader VMRuntime::'\b' + CilOpCodes.Call, //38 005A call instance class '\b\u2001' VMRuntime::'\u0002'(class EazBinaryReader) + CilOpCodes.Stfld //39 005F stfld class '\b\u2001' VMRuntime::'\b\u2000' + // ... + }; + + public bool MatchEntireBody => false; + public bool Verify(CilInstructionCollection instructions, int index = 0) + { + var resolveVMTypeCodeMethod = instructions[index + 4].Operand as SerializedMethodDefinition; + return resolveVMTypeCodeMethod?.Parameters.Count == 1; + } + } +} From c5b323e66e88c278a3084b4df4f53fd0fa0859e8 Mon Sep 17 00:00:00 2001 From: CursedSheep Date: Fri, 23 Jun 2023 00:45:40 +0800 Subject: [PATCH 2/9] Quick order fix for Operand Reader --- src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs index d75bd8a..02a3914 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs @@ -126,8 +126,8 @@ private List FindOperandResolver(DevirtualizationContext ctx) } else { - order.Add(ValueType.Position); order.Add(ValueType.Token); + order.Add(ValueType.Position); return order; } } From a96dfd8a9672781d6cd4c277a15177f2cc032f70 Mon Sep 17 00:00:00 2001 From: CursedSheep <44673993+CursedSheep@users.noreply.github.com> Date: Wed, 5 Jul 2023 17:24:00 +0800 Subject: [PATCH 3/9] Improved Order Analyzer Code --- .../Devirtualization/Pipeline/ReadOrderAnalyzer.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs index 02a3914..611cfcf 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs @@ -29,7 +29,8 @@ public ReadOrderAnalyzer(DevirtualizationContext ctx) : base(ctx) public override bool Run() { - this.Ctx.OperandReadOrder = FindOperandResolver(this.Ctx); + //find type resolver order + this.Ctx.OperandReadOrder = AnalyzeTypeResolverOrder(this.Ctx); if (this.Ctx.OperandReadOrder.Count == 0) { Ctx.Console.Error($"Failed to find Correct Reading order for Operand Reader!"); @@ -38,6 +39,7 @@ public override bool Run() Ctx.Console.Success("Found Correct Operand Read Order!"); + //Analyze VM Data Read Order MethodDefinition? vmFuncReader = FindMethodReadOrderFunction(this.Ctx); if (vmFuncReader == null) { @@ -45,7 +47,7 @@ public override bool Run() return false; } - var readOrder = AnalyzeReadOrder(vmFuncReader); + var readOrder = AnalyzeVMDataReadOrder(vmFuncReader); if (readOrder.Count != FieldOrder.Length) { @@ -63,7 +65,7 @@ public override bool Run() return true; } - private List AnalyzeReadOrder(MethodDefinition readFunc) + private List AnalyzeVMDataReadOrder(MethodDefinition readFunc) { List readOrder = new(); var funcRetType = readFunc?.Signature?.ReturnType.Resolve(); @@ -91,7 +93,7 @@ private List AnalyzeReadOrder(MethodDefinition readFunc) return readOrder; } - private List FindOperandResolver(DevirtualizationContext ctx) + private List AnalyzeTypeResolverOrder(DevirtualizationContext ctx) { List order = new(); foreach (var t in ctx.Module.GetAllTypes()) From 782aaee8f958851ddd249368741188f99d5161a3 Mon Sep 17 00:00:00 2001 From: CursedSheep <44673993+CursedSheep@users.noreply.github.com> Date: Wed, 5 Jul 2023 23:30:02 +0800 Subject: [PATCH 4/9] Implemented VM Operand Type Order Detection --- .../InlineOperands/VMInlineOperand.cs | 2 +- .../InlineOperands/VMInlineOperandData.cs | 7 +- .../DevirtualizationContext.cs | 2 + .../Pipeline/MethodDevirtualizer.cs | 5 +- .../Pipeline/ReadOrderAnalyzer.cs | 104 +++++++++++++++++- .../Util/TypeDefinitionExtensions.cs | 27 +++++ src/EazyDevirt/Util/Utils.cs | 17 +++ 7 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 src/EazyDevirt/Util/TypeDefinitionExtensions.cs create mode 100644 src/EazyDevirt/Util/Utils.cs diff --git a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperand.cs b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperand.cs index 7e2309f..c3751cc 100644 --- a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperand.cs +++ b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperand.cs @@ -100,7 +100,7 @@ public static VMInlineOperand ReadInternal(DevirtualizationContext ctx, BinaryRe if (ValueType == ValueType.Token) return new VMInlineOperand(ValueType, reader.ReadInt32()); else - return new VMInlineOperand(ValueType, VMInlineOperandData.Read(reader)); + return new VMInlineOperand(ValueType, VMInlineOperandData.Read(ctx, reader)); } public static VMInlineOperand ReadInternal(BinaryReader reader) => new(ValueType.Position, reader.ReadInt32()); diff --git a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs index 936cdcd..8879ec1 100644 --- a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs +++ b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using EazyDevirt.Devirtualization; +using System.Reflection; namespace EazyDevirt.Core.Architecture.InlineOperands; @@ -19,10 +20,10 @@ internal abstract record VMInlineOperandData(VMInlineOperandType Type) /// /// BinaryReader /// InlineOperandData - public static VMInlineOperandData Read(BinaryReader reader) + public static VMInlineOperandData Read(DevirtualizationContext ctx, BinaryReader reader) { var operandType = reader.ReadByte(); - return (VMInlineOperandType)operandType switch + return ctx.VMOperandTypeOrder[operandType] switch { VMInlineOperandType.Type => new VMTypeData(reader), VMInlineOperandType.Field => new VMFieldData(reader), diff --git a/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs b/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs index 933ae24..2e420e8 100644 --- a/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs +++ b/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs @@ -1,6 +1,7 @@ using AsmResolver.DotNet; using AsmResolver.PE.DotNet.Metadata.Tables; using EazyDevirt.Core.Architecture; +using EazyDevirt.Core.Architecture.InlineOperands; using EazyDevirt.Core.IO; using EazyDevirt.Devirtualization.Options; using EazyDevirt.Logging; @@ -42,6 +43,7 @@ internal record DevirtualizationContext public TypeDefinition VMDeclaringType { get; set; } public VMCipherStream VMStream { get; set; } public VMCipherStream VMResolverStream { get; set; } + public Dictionary VMOperandTypeOrder { get; set; } public List VMMethodReadOrder { get; set; } public List OperandReadOrder { get; set; } public int PositionCryptoKey { get; set; } diff --git a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs index eef02ba..0095dde 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs @@ -23,7 +23,10 @@ public override bool Run() Resolver = new Resolver(Ctx); foreach (var vmMethod in Ctx.VMMethods) { - VMStream.Seek(vmMethod.MethodKey, SeekOrigin.Begin); + if (vmMethod.EncodedMethodKey != "\"pY:J!Wtgk") + continue; + //vmMethod.MethodKey = VMCipherStream.DecodeMethodKey(vmMethod.EncodedMethodKey, Ctx.PositionCryptoKey); + //VMStream.Seek(vmMethod.MethodKey, SeekOrigin.Begin); ReadVMMethod(vmMethod); diff --git a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs index 611cfcf..955cc64 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs @@ -7,6 +7,8 @@ using AsmResolver.PE.DotNet.Cil; using ValueType = EazyDevirt.Core.Architecture.InlineOperands.ValueType; +using EazyDevirt.Util; +using EazyDevirt.Core.Architecture.InlineOperands; namespace EazyDevirt.Devirtualization { @@ -33,11 +35,29 @@ public override bool Run() this.Ctx.OperandReadOrder = AnalyzeTypeResolverOrder(this.Ctx); if (this.Ctx.OperandReadOrder.Count == 0) { - Ctx.Console.Error($"Failed to find Correct Reading order for Operand Reader!"); + Ctx.Console.Error($"Failed to find Correct Reading order for Type Resolver Reader!"); return false; } - Ctx.Console.Success("Found Correct Operand Read Order!"); + Ctx.Console.Success("Found Correct Type Resolver Read Order!"); + + var opDataBaseTdef = FindOperandDataBase(this.Ctx); + if (opDataBaseTdef == null) + { + Ctx.Console.Error($"Failed to find VM Operand Base"); + return false; + } + + //get all types that inherits the OperandDataBase + var inheritedTypes = opDataBaseTdef.GetAllInheriting(opDataBaseTdef); + if (inheritedTypes.Count == 0) + { + Ctx.Console.Error($"Failed to get inherited types of {opDataBaseTdef.FullName}"); + return false; + } + + Ctx.VMOperandTypeOrder = AnalyzeOperandTypes(inheritedTypes); + Ctx.Console.Success("Found Correct Operand Type Order!"); //Analyze VM Data Read Order MethodDefinition? vmFuncReader = FindMethodReadOrderFunction(this.Ctx); @@ -65,6 +85,86 @@ public override bool Run() return true; } + private Dictionary AnalyzeOperandTypes(List opTypes) + { + Dictionary opTypesOrder = new(); + + //constants + string Int32Type = "System.Int32"; + string BooleanType = "System.Boolean"; + string ByteType = "System.Byte"; + string StringType = "System.String"; + + foreach(var opType in opTypes) + { + int? typeConstant = GetVMOperandTypeCode(opType); + if (typeConstant == null) + throw new Exception("Operand Type Code should not be null!"); + + if (opType.Fields.Count == 1 && Utils.GetFieldCountFromRetType(opType, StringType) == 1) + opTypesOrder[typeConstant.Value] = VMInlineOperandType.UserString; + else if (opType.Fields.Count == 2 && Utils.GetFieldCountFromRetType(opType, Int32Type) == 2) + opTypesOrder[typeConstant.Value] = VMInlineOperandType.EazCall; + else if (opType.Fields.Count == 3 && Utils.GetFieldCountFromRetType(opType, StringType) == 1 && Utils.GetFieldCountFromRetType(opType, BooleanType) == 1) + opTypesOrder[typeConstant.Value] = VMInlineOperandType.Field; + else if (opType.Fields.Count == 6) + { + if (Utils.GetFieldCountFromRetType(opType, StringType) == 1 && Utils.GetFieldCountFromRetType(opType, ByteType) == 1) + opTypesOrder[typeConstant.Value] = VMInlineOperandType.Method; + else if (Utils.GetFieldCountFromRetType(opType, StringType) == 1 && Utils.GetFieldCountFromRetType(opType, BooleanType) == 2 && Utils.GetFieldCountFromRetType(opType, Int32Type) == 3) + opTypesOrder[typeConstant.Value] = VMInlineOperandType.Type; + } + } + + return opTypesOrder; + } + + private int? GetVMOperandTypeCode(TypeDefinition t) + { + foreach(var m in t.Methods) + { + if (!m.IsPublic || !m.IsVirtual || !m.IsSpecialName) + continue; + + if (!(m.Signature != null && m.CilMethodBody != null && m.Signature.ReturnsValue && m.Signature.ReturnType.ToString() == "System.Byte")) + continue; + + return m.CilMethodBody.Instructions[0].GetLdcI4Constant(); + } + + return null; + } + + private TypeDefinition? FindOperandDataBase(DevirtualizationContext ctx) + { + foreach (var t in ctx.Module.GetAllTypes()) + { + /* internal abstract class OperandBase + { + protected OperandBase() + { + } + + //Operand Code value corresponds to the switch value + public abstract byte GetOperandCode(); + } + */ + if (!t.IsAbstract || !t.IsNotPublic || t.Methods.Count != 2) + continue; + + //there's only one method in the type that's not a constructor (GetOperandCode) + MethodDefinition getOperandCodeFunc = t.Methods.First(g => !g.IsConstructor); + + //Need to confirm if the type is actually OperandDataBase + if (getOperandCodeFunc.IsAbstract && + getOperandCodeFunc.Signature is not null && getOperandCodeFunc.Signature.ReturnsValue && + getOperandCodeFunc.Signature.ReturnType.ToString() == "System.Byte") + return t; + } + + return null; + } + private List AnalyzeVMDataReadOrder(MethodDefinition readFunc) { List readOrder = new(); diff --git a/src/EazyDevirt/Util/TypeDefinitionExtensions.cs b/src/EazyDevirt/Util/TypeDefinitionExtensions.cs new file mode 100644 index 0000000..1488da7 --- /dev/null +++ b/src/EazyDevirt/Util/TypeDefinitionExtensions.cs @@ -0,0 +1,27 @@ +using AsmResolver.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EazyDevirt.Util +{ + internal static class TypeDefinitionExtensions + { + public static List GetAllInheriting(this TypeDefinition typeDefinition, TypeDefinition typeToFind) + { + List inheritedTypes = new(); + var module = typeDefinition.Module; + + if(module == null) + return inheritedTypes; + + foreach (var t in module.GetAllTypes()) + if (t.BaseType != null && t.BaseType.MetadataToken == typeToFind.MetadataToken) + inheritedTypes.Add(t); + + return inheritedTypes; + } + } +} diff --git a/src/EazyDevirt/Util/Utils.cs b/src/EazyDevirt/Util/Utils.cs new file mode 100644 index 0000000..82cac32 --- /dev/null +++ b/src/EazyDevirt/Util/Utils.cs @@ -0,0 +1,17 @@ +using AsmResolver.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EazyDevirt.Util +{ + internal class Utils + { + public static int GetFieldCountFromRetType(TypeDefinition t, string typeName) + { + return t.Fields.Count(x => x.Signature?.FieldType.ToString() == typeName); + } + } +} From 63075b5219548da31422bcbb968fd1687218d70d Mon Sep 17 00:00:00 2001 From: CursedSheep <44673993+CursedSheep@users.noreply.github.com> Date: Thu, 6 Jul 2023 00:22:49 +0800 Subject: [PATCH 5/9] Improved Read Order Analyzer Code --- src/EazyDevirt/Core/Architecture/VMMethod.cs | 5 ++-- src/EazyDevirt/Core/IO/Resolver.cs | 2 +- .../DevirtualizationContext.cs | 2 +- .../Pipeline/MethodDevirtualizer.cs | 2 +- .../Pipeline/ReadOrderAnalyzer.cs | 24 +++++++++---------- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/EazyDevirt/Core/Architecture/VMMethod.cs b/src/EazyDevirt/Core/Architecture/VMMethod.cs index d7224e6..26ce0a5 100644 --- a/src/EazyDevirt/Core/Architecture/VMMethod.cs +++ b/src/EazyDevirt/Core/Architecture/VMMethod.cs @@ -1,6 +1,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Code.Cil; using AsmResolver.PE.DotNet.Cil; +using EazyDevirt.Devirtualization; namespace EazyDevirt.Core.Architecture; @@ -47,9 +48,9 @@ internal record VMMethodInfo public ITypeDefOrRef DeclaringType { get; set; } public ITypeDefOrRef ReturnType { get; set; } - public VMMethodInfo(BinaryReader reader, List ReadOrder) + public VMMethodInfo(DevirtualizationContext ctx, BinaryReader reader) { - foreach (VMMethodField field in ReadOrder) + foreach (VMMethodField field in ctx.VMMethodReadOrder) { switch (field) { diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs index b04465f..f4f90cb 100644 --- a/src/EazyDevirt/Core/IO/Resolver.cs +++ b/src/EazyDevirt/Core/IO/Resolver.cs @@ -369,7 +369,7 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu { Ctx.VMResolverStream.Seek(position, SeekOrigin.Begin); - var methodInfo = new VMMethodInfo(VMStreamReader, this.Ctx.VMMethodReadOrder); + var methodInfo = new VMMethodInfo(this.Ctx, VMStreamReader); var declaringType = ResolveType(methodInfo.VMDeclaringType); if (declaringType is null) diff --git a/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs b/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs index 2e420e8..4aed8b7 100644 --- a/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs +++ b/src/EazyDevirt/Devirtualization/DevirtualizationContext.cs @@ -45,7 +45,7 @@ internal record DevirtualizationContext public VMCipherStream VMResolverStream { get; set; } public Dictionary VMOperandTypeOrder { get; set; } public List VMMethodReadOrder { get; set; } - public List OperandReadOrder { get; set; } + public Dictionary OperandReadOrder { get; set; } public int PositionCryptoKey { get; set; } public int MethodCryptoKey { get; set; } diff --git a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs index 0095dde..59bf4d0 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs @@ -40,7 +40,7 @@ public override bool Run() private void ReadVMMethod(VMMethod vmMethod) { - vmMethod.MethodInfo = new VMMethodInfo(VMStreamReader, this.Ctx.VMMethodReadOrder); + vmMethod.MethodInfo = new VMMethodInfo(this.Ctx, VMStreamReader); ReadExceptionHandlers(vmMethod); diff --git a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs index 955cc64..2f837a3 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs @@ -67,21 +67,21 @@ public override bool Run() return false; } - var readOrder = AnalyzeVMDataReadOrder(vmFuncReader); + var vmDataReadOrder = AnalyzeVMDataReadOrder(vmFuncReader); - if (readOrder.Count != FieldOrder.Length) + if (vmDataReadOrder.Count != FieldOrder.Length) { Ctx.Console.Error($"Failed to analyze VMMethod Read Order {vmFuncReader?.MetadataToken}"); return false; } - string orderStrFormat = ": " + string.Join(", ", readOrder.Select((d, i) => string.Format("[{0}] {1}", i + 1, d))); + string orderStrFormat = ": " + string.Join(", ", vmDataReadOrder.Select((d, i) => string.Format("[{0}] {1}", i + 1, d))); if (Ctx.Options.VeryVerbose) Ctx.Console.InfoStr(orderStrFormat, "Correct VMMethod Read Order"); Ctx.Console.Success("Found Correct Method Read Order!"); - this.Ctx.VMMethodReadOrder = readOrder; + this.Ctx.VMMethodReadOrder = vmDataReadOrder; return true; } @@ -193,9 +193,9 @@ private List AnalyzeVMDataReadOrder(MethodDefinition readFunc) return readOrder; } - private List AnalyzeTypeResolverOrder(DevirtualizationContext ctx) + private Dictionary AnalyzeTypeResolverOrder(DevirtualizationContext ctx) { - List order = new(); + Dictionary order = new(); foreach (var t in ctx.Module.GetAllTypes()) { foreach (var method in t.Methods) @@ -214,22 +214,22 @@ private List AnalyzeTypeResolverOrder(DevirtualizationContext ctx) { if (currInstr.GetLdcI4Constant() == 1) { - order.Add(ValueType.Position); - order.Add(ValueType.Token); + order[0] = ValueType.Position; + order[1] = ValueType.Token; return order; } else { //from older sample - order.Add(ValueType.Position); - order.Add(ValueType.Token); + order[0] = ValueType.Position; + order[1] = ValueType.Token; return order; } } else { - order.Add(ValueType.Token); - order.Add(ValueType.Position); + order[0] = ValueType.Token; + order[1] = ValueType.Position; return order; } } From 78413026ed21d713ff3f23752b6d0e5d2bcc319a Mon Sep 17 00:00:00 2001 From: CursedSheep <44673993+CursedSheep@users.noreply.github.com> Date: Thu, 6 Jul 2023 00:23:40 +0800 Subject: [PATCH 6/9] Remove testing code into commit --- .../Devirtualization/Pipeline/MethodDevirtualizer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs index 59bf4d0..fe7c849 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs @@ -23,11 +23,6 @@ public override bool Run() Resolver = new Resolver(Ctx); foreach (var vmMethod in Ctx.VMMethods) { - if (vmMethod.EncodedMethodKey != "\"pY:J!Wtgk") - continue; - //vmMethod.MethodKey = VMCipherStream.DecodeMethodKey(vmMethod.EncodedMethodKey, Ctx.PositionCryptoKey); - //VMStream.Seek(vmMethod.MethodKey, SeekOrigin.Begin); - ReadVMMethod(vmMethod); if (Ctx.Options.VeryVerbose) From 840f729ff774ec2e6f1ea6f370bf8f68ed787d73 Mon Sep 17 00:00:00 2001 From: CursedSheep <44673993+CursedSheep@users.noreply.github.com> Date: Thu, 6 Jul 2023 21:44:29 +0800 Subject: [PATCH 7/9] Fixed typo and order mistake --- src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs index 2f837a3..4a2f313 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/ReadOrderAnalyzer.cs @@ -24,8 +24,8 @@ public ReadOrderAnalyzer(DevirtualizationContext ctx) : base(ctx) VMMethodField.Locals, VMMethodField.Parameters, VMMethodField.Name, - VMMethodField.ReturnType, VMMethodField.VMDeclaringType, + VMMethodField.ReturnType, VMMethodField.BindingFlags }; @@ -111,7 +111,7 @@ private Dictionary AnalyzeOperandTypes(List Date: Thu, 6 Jul 2023 21:59:35 +0800 Subject: [PATCH 8/9] Added back seek --- src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs index fe7c849..4b31806 100644 --- a/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs +++ b/src/EazyDevirt/Devirtualization/Pipeline/MethodDevirtualizer.cs @@ -23,6 +23,7 @@ public override bool Run() Resolver = new Resolver(Ctx); foreach (var vmMethod in Ctx.VMMethods) { + VMStream.Seek(vmMethod.MethodKey, SeekOrigin.Begin); ReadVMMethod(vmMethod); if (Ctx.Options.VeryVerbose) From d7e3bf26094a0da100fd5e724edd3ce55d304e3a Mon Sep 17 00:00:00 2001 From: CursedSheep <44673993+CursedSheep@users.noreply.github.com> Date: Wed, 12 Jul 2023 23:29:55 +0800 Subject: [PATCH 9/9] Implemented SwitchAnalyzer SwitchAnalyzer is a class for reading the instructions of each statement in a switch statement --- src/EazyDevirt/Util/SwitchAnalyzer.cs | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/EazyDevirt/Util/SwitchAnalyzer.cs diff --git a/src/EazyDevirt/Util/SwitchAnalyzer.cs b/src/EazyDevirt/Util/SwitchAnalyzer.cs new file mode 100644 index 0000000..125823f --- /dev/null +++ b/src/EazyDevirt/Util/SwitchAnalyzer.cs @@ -0,0 +1,57 @@ +using AsmResolver.DotNet; +using AsmResolver.PE.DotNet.Cil; +using Echo.Platforms.AsmResolver; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EazyDevirt.Util +{ + internal class SwitchAnalyzer + { + public MethodDefinition CurrentMethod { get; private set; } + public CilInstruction SwitchInstruction { get; private set; } + + bool _read; + List caseCilInstructions { get; } + public int CaseCount => caseCilInstructions.Count; + + + public SwitchAnalyzer(MethodDefinition currentMethod, CilInstruction switchInstruction) + { + if (switchInstruction.OpCode != CilOpCodes.Switch) + throw new Exception("Invalid instruction! Instruction must have a switch opcode!"); + + if (switchInstruction.Operand == null) + throw new Exception("Invalid instruction! Switch instruction must have an operand!"); + + CurrentMethod = currentMethod; + SwitchInstruction = switchInstruction; + caseCilInstructions = new List(); + _read = false; + } + + public bool Read() + { + if (_read) + return false; + + var graph = this.CurrentMethod?.CilMethodBody?.ConstructStaticFlowGraph(); + var switchNode = graph?.Nodes.FirstOrDefault(node => node?.Contents.Footer == this.SwitchInstruction, null); + + //it is impossible for the function to not have a switch opcode + if (switchNode == null) + return false; + + foreach (var adjNode in switchNode.ConditionalEdges) + caseCilInstructions.Add(adjNode.Target.Contents.Instructions.ToArray()); + + return true; + } + + public CilInstruction[] GetCase(int caseIndex) + => caseCilInstructions[caseIndex]; + } +}