From 88c48264a1fa60d5283efcb91f7f8ca7123c9891 Mon Sep 17 00:00:00 2001 From: puff <33184334+puff@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:34:48 -0500 Subject: [PATCH 1/4] Use TypeNameParser for resolving types --- .../InlineOperands/VMInlineOperandData.cs | 42 ++---- src/EazyDevirt/Core/Architecture/TypeName.cs | 125 ------------------ src/EazyDevirt/Core/IO/Resolver.cs | 111 +++++----------- 3 files changed, 46 insertions(+), 232 deletions(-) delete mode 100644 src/EazyDevirt/Core/Architecture/TypeName.cs diff --git a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs index 8e90a0c..7b72707 100644 --- a/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs +++ b/src/EazyDevirt/Core/Architecture/InlineOperands/VMInlineOperandData.cs @@ -40,24 +40,20 @@ public static VMInlineOperandData Read(BinaryReader reader) internal record VMTypeData : VMInlineOperandData { public string Name { get; } - public bool HasGenericTypeArgs { get; } - public bool IsGenericParameterType { get; } - public int GenericArgumentIndex { get; } - public int GenericTypeArgumentIndex { get; } - public VMInlineOperand[] GenericTypes { get; } - - public TypeName TypeName { get; } + public bool HasGenericTypeParameters { get; } + public bool IsGenericParameterIndex { get; } + public int GenericMethodParameterIndex { get; } + public int GenericTypeParameterIndex { get; } + public VMInlineOperand[] GenericParameters { get; } public VMTypeData(BinaryReader reader) : base(VMInlineOperandType.Type) { Name = reader.ReadString(); - HasGenericTypeArgs = reader.ReadBoolean(); - IsGenericParameterType = reader.ReadBoolean(); - GenericArgumentIndex = reader.ReadInt32(); - GenericTypeArgumentIndex = reader.ReadInt32(); - GenericTypes = VMInlineOperand.ReadArrayInternal(reader); - - TypeName = new TypeName(Name); + HasGenericTypeParameters = reader.ReadBoolean(); + IsGenericParameterIndex = reader.ReadBoolean(); + GenericMethodParameterIndex = reader.ReadInt32(); + GenericTypeParameterIndex = reader.ReadInt32(); + GenericParameters = VMInlineOperand.ReadArrayInternal(reader); } } @@ -70,16 +66,6 @@ internal record VMFieldData : VMInlineOperandData public string Name { get; } public bool IsStatic { get; } - public BindingFlags BindingFlags - { - get - { - var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic; - bindingFlags |= IsStatic ? BindingFlags.Static : BindingFlags.Instance; - return bindingFlags; - } - } - public VMFieldData(BinaryReader reader) : base(VMInlineOperandType.Field) { DeclaringType = VMInlineOperand.ReadInternal(reader); @@ -97,11 +83,11 @@ internal record VMMethodData : VMInlineOperandData public byte Flags { get; } public bool IsStatic { get; } - public bool HasGenericArguments { get; } + public bool HasGenericParameters { get; } public string Name { get; } public VMInlineOperand ReturnType { get; } public VMInlineOperand[] Parameters { get; } - public VMInlineOperand[] GenericArguments { get; } + public VMInlineOperand[] GenericParameters { get; } public BindingFlags BindingFlags { @@ -119,11 +105,11 @@ public VMMethodData(BinaryReader reader) : base(VMInlineOperandType.Method) Flags = reader.ReadByte(); // these constants are also different between versions (#3) IsStatic = (Flags & 1) > 0; - HasGenericArguments = (Flags & 2) > 0; + HasGenericParameters = (Flags & 2) > 0; Name = reader.ReadString(); ReturnType = VMInlineOperand.ReadInternal(reader); Parameters = VMInlineOperand.ReadArrayInternal(reader); - GenericArguments = VMInlineOperand.ReadArrayInternal(reader); + GenericParameters = VMInlineOperand.ReadArrayInternal(reader); } } diff --git a/src/EazyDevirt/Core/Architecture/TypeName.cs b/src/EazyDevirt/Core/Architecture/TypeName.cs deleted file mode 100644 index b8e5e08..0000000 --- a/src/EazyDevirt/Core/Architecture/TypeName.cs +++ /dev/null @@ -1,125 +0,0 @@ - -using System.Reflection; - -namespace EazyDevirt.Core.Architecture; - - -// From saneki's eazdevirt - -/// -/// Convenience class for interpreting the type names found in the -/// encrypted virtualization resources file. -/// -public class TypeName -{ - /// - /// Full name as given in constructor. - /// - public string FullName { get; private set; } - - public TypeName(string fullName) - { - FullName = fullName; - } - - /// - /// Full assembly name. - /// - public string AssemblyFullName => - FullName[(FullName.IndexOf(", ", StringComparison.Ordinal) + 2)..]; - - /// - /// Assembly name. - /// - public AssemblyName AssemblyName => new(AssemblyFullName); - - /// - /// Type name without namespace. - /// - public string NameWithoutNamespace => Name.Contains('.') ? Name.Split('.').Last() : Name; - - /// - /// Namespace. - /// - public string Namespace => Name.Contains('.') - ? string.Join(".", - Name.Split('.').Reverse().Skip(1).Reverse().ToArray()) - : string.Empty; - - /// - /// Type name without assembly info. - /// - public string Name - { - get - { - if (_nameInitialized) return _name; - if (!FullName.Contains(", ")) return FullName; - - GetModifiersStack(FullName.Split(',')[0], out var fixedName); - _name = fixedName; - _nameInitialized = true; - return fixedName; - } - } - - private bool _nameInitialized; - private string _name = string.Empty; - - public Stack Modifiers => FullName.Contains(", ") - ? GetModifiersStack(FullName.Split(',')[0], out _) - : new Stack(); - - /// - /// Whether or not this name indicates the type is nested. - /// - public bool IsNested => Name.Contains('+'); - - /// - /// The parent type name if nested. If not nested, an empty string. - /// - public string ParentName => IsNested - ? string.Join("+", - Name.Split('+').Reverse().Skip(1).Reverse().ToArray()) - : string.Empty; - - /// - /// Nested parent name without namespace, or empty if not nested. - /// - public string ParentNameWithoutNamespace => IsNested - ? ParentName.Contains('.') ? ParentName.Split('.').Last() : ParentName - : string.Empty; - - /// - /// The nested child type name if nested. If not nested, an empty string. - /// - public string NestedName => IsNested ? Name.Split('+').Last() : string.Empty; - - /// - /// Get a modifiers stack from a deserialized type name, and also - /// provide the fixed name. - /// - /// Deserialized name - /// Fixed name - /// Modifiers stack - private static Stack GetModifiersStack(string rawName, out string fixedName) - { - var stack = new Stack(); - - while (true) - { - if (rawName.EndsWith("[]")) - stack.Push("[]"); - else if (rawName.EndsWith("*")) - stack.Push("*"); - else if (rawName.EndsWith("&")) - stack.Push("&"); - else break; - - rawName = rawName[..^stack.Peek().Length]; - } - - fixedName = rawName; - return stack; - } -} \ No newline at end of file diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs index 0b42a4e..d590123 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; @@ -22,21 +23,11 @@ public Resolver(DevirtualizationContext ctx) private Dictionary Cache { get; } - private TypeSignature ApplySigModifiers(TypeSignature baseTypeSig, Stack mods) + public ITypeDefOrRef? ResolveType(VMInlineOperand inlineOperand) { - while (mods.Count > 0) - { - var mod = mods.Pop(); - baseTypeSig = mod switch - { - "[]" => baseTypeSig.MakeSzArrayType(), - "*" => baseTypeSig.MakePointerType(), - "&" => baseTypeSig.MakeByReferenceType(), - _ => throw new Exception($"Unknown modifier: {mod}") - }; - } - - return baseTypeSig; + if (inlineOperand.IsToken) + return Ctx.Module.LookupMember(inlineOperand.Token); + return ResolveType(inlineOperand.Position); } public ITypeDefOrRef? ResolveType(int position) @@ -57,71 +48,33 @@ private TypeSignature ApplySigModifiers(TypeSignature baseTypeSig, Stack if (!inlineOperand.HasData || inlineOperand.Data is not VMTypeData data) throw new Exception("VM inline operand expected to have type data!"); - if (data.IsGenericParameterType) + if (data.IsGenericParameterIndex) { - if (data.GenericArgumentIndex != -1) + if (data.GenericMethodParameterIndex >= 0) { - var typeGenericParameterSignature = new GenericParameterSignature(Ctx.Module, GenericParameterType.Method, data.GenericArgumentIndex); - var typeGenericTypeDefOrRef = typeGenericParameterSignature.ToTypeDefOrRef(); - - Cache.Add(position, typeGenericTypeDefOrRef); - return typeGenericTypeDefOrRef; + var genericTypeDefOrRef = new GenericParameterSignature(Ctx.Module, GenericParameterType.Method, data.GenericMethodParameterIndex).ToTypeDefOrRef(); + Cache.Add(position, genericTypeDefOrRef); + return genericTypeDefOrRef; } - if (data.TypeGenericArgumentIndex != -1) + if (data.GenericTypeParameterIndex >= 0) { - var typeGenericParameterSignature = new GenericParameterSignature(Ctx.Module, GenericParameterType.Type, data.GenericArgumentIndex); - var typeGenericTypeDefOrRef = typeGenericParameterSignature.ToTypeDefOrRef(); - - Cache.Add(position, typeGenericTypeDefOrRef); - return typeGenericTypeDefOrRef; + var genericTypeDefOrRef = new GenericParameterSignature(Ctx.Module, GenericParameterType.Type, data.GenericTypeParameterIndex).ToTypeDefOrRef(); + Cache.Add(position, genericTypeDefOrRef); + return genericTypeDefOrRef; } } - - // Try to find type definition or reference - var typeDefOrRef = (ITypeDefOrRef?)Ctx.Module.GetAllTypes() - .FirstOrDefault(x => x.FullName == data.TypeName.Name) ?? - (ITypeDefOrRef?)Ctx.Module.GetImportedTypeReferences() - .FirstOrDefault(x => x.FullName == data.TypeName.Name && x.Scope?.Name == data.TypeName.AssemblyName.Name); - if (typeDefOrRef != null) - { - var typeSig = typeDefOrRef.ToTypeSignature(); - if (data.HasGenericTypes) - typeSig = typeDefOrRef - .MakeGenericInstanceType(data.GenericTypes.Select(g => ResolveType(g.Position)!.ToTypeSignature()) - .ToArray()); - - var typeSigWithModifiers = ApplySigModifiers(typeSig, data.TypeName.Modifiers).ToTypeDefOrRef().ImportWith(Ctx.Importer); - Cache.Add(position, typeSigWithModifiers); - return typeSigWithModifiers; - } - var assemblyRef = - Ctx.Module.AssemblyReferences.FirstOrDefault(x => x.Name == data.TypeName.AssemblyName.Name) ?? - new AssemblyReference(data.TypeName.AssemblyName.Name, data.TypeName.AssemblyName.Version!, - data.TypeName.AssemblyName.GetPublicKey() != null, - data.TypeName.AssemblyName.GetPublicKey() ?? data.TypeName.AssemblyName.GetPublicKeyToken()); - if (assemblyRef == null!) - { - Ctx.Console.Warning($"Failed to find vm type {data.Name} assembly reference!"); - return null!; - } + var typeDefOrRef = TypeNameParser.Parse(Ctx.Module, data.Name).GetUnderlyingTypeDefOrRef(); + if (typeDefOrRef is null) + throw new Exception($"Failed to parse vm type {data.Name}"); + + if (data.HasGenericTypeParameters) + typeDefOrRef = typeDefOrRef.MakeGenericInstanceType(data.GenericParameters.Select(x => ResolveType(x)!.ToTypeSignature()).ToArray()).ToTypeDefOrRef(); - var parentTypeRef = !data.TypeName.IsNested - ? assemblyRef.CreateTypeReference(data.TypeName.Namespace, data.TypeName.NameWithoutNamespace) - : assemblyRef.CreateTypeReference(data.TypeName.Namespace, data.TypeName.ParentNameWithoutNamespace); - var typeRef = !data.TypeName.IsNested - ? parentTypeRef - : parentTypeRef.CreateTypeReference(data.TypeName.NestedName); - var typeBaseSig = typeRef.ToTypeSignature(); - if (data.HasGenericTypes) - typeBaseSig = typeRef - .MakeGenericInstanceType(data.GenericTypes.Select(g => ResolveType(g.Position)!.ToTypeSignature()) - .ToArray()); - - var typeBaseSigWithModifiers = ApplySigModifiers(typeBaseSig, data.TypeName.Modifiers).ToTypeDefOrRef().ImportWith(Ctx.Importer); - Cache.Add(position, typeBaseSigWithModifiers); - return typeBaseSigWithModifiers; + var imported = typeDefOrRef.ImportWith(Ctx.Importer); + Cache.Add(position, imported); + return imported; } public IFieldDescriptor? ResolveField(int position) @@ -271,16 +224,16 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu // TODO: Fix resolving imported member references of methods returning a generic parameter && (ms.ReturnType is GenericParameterSignature or GenericInstanceTypeSignature || ms.ReturnType.FullName == returnType.FullName) - && ms.GenericParameterCount == data.GenericArguments.Length + && ms.GenericParameterCount == data.GenericParameters.Length && VerifyMethodParameters(ms, data)); if (importedMemberRef != null) { - if (data.HasGenericArguments) + if (data.HasGenericParameters) { var importedMemberRefGenerics = importedMemberRef .MakeGenericInstanceMethod( - data.GenericArguments.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray()) + data.GenericParameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray()) .ImportWith(Ctx.Importer); Cache.Add(position, importedMemberRefGenerics); @@ -297,10 +250,10 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu if (declaringType != null) { var methodDef = ResolveMethod(declaringType, data); - if (data.HasGenericArguments) + if (data.HasGenericParameters) { var methodDefGenerics = methodDef? - .MakeGenericInstanceMethod(data.GenericArguments + .MakeGenericInstanceMethod(data.GenericParameters .Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray()) .ImportWith(Ctx.Importer); @@ -317,7 +270,7 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu var declaringTypeSig = declaringTypeDefOrRef.ToTypeSignature(); var parameters = data.Parameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray(); - var genericArgs = data.GenericArguments.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray(); + var genericArgs = data.GenericParameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray(); var newParams = new List(); // convert generic parameters to their indexes (!!0, !0) foreach (var parameter in parameters) @@ -361,13 +314,13 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu var memberRef = declaringTypeDefOrRef .CreateMemberReference(data.Name, data.IsStatic ? MethodSignature.CreateStatic( - returnType.ToTypeSignature(), data.GenericArguments.Length, + returnType.ToTypeSignature(), data.GenericParameters.Length, newParams) : MethodSignature.CreateInstance( - returnType.ToTypeSignature(), data.GenericArguments.Length, + returnType.ToTypeSignature(), data.GenericParameters.Length, newParams)); - if (data.HasGenericArguments) + if (data.HasGenericParameters) { var memberRefGenerics = memberRef.MakeGenericInstanceMethod(genericArgs).ImportWith(Ctx.Importer); Cache.Add(position, memberRefGenerics); From 375d499878e2548993420b4571a4c2ec37651996 Mon Sep 17 00:00:00 2001 From: puff <33184334+puff@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:36:37 -0500 Subject: [PATCH 2/4] Check IsStatic when resolving fields --- src/EazyDevirt/Core/IO/Resolver.cs | 32 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs index d590123..20bd143 100644 --- a/src/EazyDevirt/Core/IO/Resolver.cs +++ b/src/EazyDevirt/Core/IO/Resolver.cs @@ -81,9 +81,9 @@ public Resolver(DevirtualizationContext ctx) { if (Cache.TryGetValue(position, out var result)) return (IFieldDescriptor?)result; - + Ctx.VMResolverStream.Seek(position, SeekOrigin.Begin); - + var inlineOperand = new VMInlineOperand(VMStreamReader); if (inlineOperand.IsToken) { @@ -91,20 +91,28 @@ public Resolver(DevirtualizationContext ctx) Cache.Add(position, lookup); return lookup; } - + if (!inlineOperand.HasData || inlineOperand.Data is not VMFieldData data) throw new Exception("VM inline operand expected to have field data!"); - - var declaringTypeSig = ResolveType(data.DeclaringType.Position); - var declaringType = declaringTypeSig?.Resolve(); - if (declaringType != null) + + var declaringType = ResolveType(data.DeclaringType.Position); + if (declaringType is null) { - var field = declaringType.Fields.FirstOrDefault(f => f.Name == data.Name)?.ImportWith(Ctx.Importer); - Cache.Add(position, field); - return field; + Ctx.Console.Error($"Unable to resolve vm field {data.Name} declaring type!"); + Cache.Add(position, null); + return null; } - - Ctx.Console.Error($"Unable to resolve vm field {data.Name} declaring type {declaringTypeSig?.Name} to a TypeDef!"); + + if (declaringType.Resolve() is { } declaringTypeDef) + { + var fieldDef = declaringTypeDef.Fields.FirstOrDefault(f => f.Name == data.Name && f.IsStatic == data.IsStatic)?.ImportWith(Ctx.Importer); + Cache.Add(position, fieldDef); + return fieldDef; + } + + // we can't create our own reference since we don't know the field's type. + // maybe it could be inferred from where it's being used, but that would require a lot of rework. + Ctx.Console.Error($"Unable to resolve vm field {declaringType?.Name}.{data.Name}"); return null; } From e44e4742611106a495f56131ad4ad99972d80cec Mon Sep 17 00:00:00 2001 From: puff <33184334+puff@users.noreply.github.com> Date: Tue, 5 Nov 2024 07:57:43 -0500 Subject: [PATCH 3/4] Update AsmResolver and Echo --- deps/Echo | 2 +- src/EazyDevirt/Core/IO/Resolver.cs | 3 +- src/EazyDevirt/Core/IO/VMBinaryReader.cs | 32 +++++++++++-------- src/EazyDevirt/EazyDevirt.csproj | 2 +- .../PatternMatching/Patterns/OpCodes/Arg.cs | 3 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/deps/Echo b/deps/Echo index 3db99c3..a8f5417 160000 --- a/deps/Echo +++ b/deps/Echo @@ -1 +1 @@ -Subproject commit 3db99c3e8150b9e4ce2f1fbdacd4fc0240391a7d +Subproject commit a8f54172ed088c63e04c5ef48ef394ae517758dc diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs index 20bd143..c27f4c0 100644 --- a/src/EazyDevirt/Core/IO/Resolver.cs +++ b/src/EazyDevirt/Core/IO/Resolver.cs @@ -1,7 +1,6 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; -using AsmResolver.DotNet.Signatures.Types; -using AsmResolver.DotNet.Signatures.Types.Parsing; +using AsmResolver.DotNet.Signatures.Parsing; using EazyDevirt.Core.Architecture; using EazyDevirt.Core.Architecture.InlineOperands; using EazyDevirt.Devirtualization; diff --git a/src/EazyDevirt/Core/IO/VMBinaryReader.cs b/src/EazyDevirt/Core/IO/VMBinaryReader.cs index 6d17f96..4344bb0 100644 --- a/src/EazyDevirt/Core/IO/VMBinaryReader.cs +++ b/src/EazyDevirt/Core/IO/VMBinaryReader.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using AsmResolver.DotNet; -using AsmResolver.DotNet.Signatures.Types; +using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Cil; using EazyDevirt.Core.Abstractions; using EazyDevirt.Devirtualization; @@ -30,6 +30,7 @@ internal class VMBinaryReader : VMBinaryReaderBase private static MethodDefinition _readDecimalMethodDef = null!; private static CilVirtualMachine _vm = null!; + private static CilThread _thread = null!; private static BitVector _instanceObj = null!; private static bool _initialized; @@ -40,6 +41,8 @@ public VMBinaryReader(Stream input, bool leaveOpen = false) : base(input, Encodi { _vm = new CilVirtualMachine(Ctx.Module, false); // 32 bit always breaks something, even on 32 bit only assemblies. _vm.Dispatcher.BeforeInstructionDispatch += DispatcherOnBeforeInstructionDispatch; + + _thread = _vm.CreateThread(); FindInstanceDefs(); if (_instanceType is null) @@ -245,11 +248,12 @@ private T ReadEmulated(byte[] bytes) var instanceObjectHandle = _instanceObj.AsObjectHandle(_vm); instanceObjectHandle.WriteField(_bufferFieldDef, _vm.ObjectMarshaller.ToBitVector(bytes)); - _vm.CallStack.Peek().WriteArgument(0, _instanceObj); - _vm.Run(); + + _thread.CallStack.Peek().WriteArgument(0, _instanceObj); + _thread.Run(); var typeSig = Ctx.Importer.ImportTypeSignature(typeof(T)); - var result = _vm.ObjectMarshaller.ToObject(_vm.CallStack.Peek().EvaluationStack.Pop(typeSig))!; + var result = _vm.ObjectMarshaller.ToObject(_thread.CallStack.Peek().EvaluationStack.Pop(typeSig))!; _vm.ObjectMapMemory.Clear(); return result; @@ -269,7 +273,7 @@ public override int ReadInt32() { var bytes = ReadBytes(4); - _vm.CallStack.Push(_readInt32MethodDef); + _thread.CallStack.Push(_readInt32MethodDef); return ReadEmulated(bytes); } @@ -278,7 +282,7 @@ public override int ReadInt32Special() { var bytes = ReadBytes(4); - _vm.CallStack.Push(_readInt32MethodDef); + _thread.CallStack.Push(_readInt32MethodDef); return ReadEmulated(bytes); } @@ -286,7 +290,7 @@ public override uint ReadUInt32() { var bytes = ReadBytes(4); - _vm.CallStack.Push(_readUInt32MethodDef); + _thread.CallStack.Push(_readUInt32MethodDef); return ReadEmulated(bytes); } @@ -294,7 +298,7 @@ public override long ReadInt64() { var bytes = ReadBytes(8); - _vm.CallStack.Push(_readInt64MethodDef); + _thread.CallStack.Push(_readInt64MethodDef); return ReadEmulated(bytes); } @@ -302,7 +306,7 @@ public override ulong ReadUInt64() { var bytes = ReadBytes(8); - _vm.CallStack.Push(_readUInt64MethodDef); + _thread.CallStack.Push(_readUInt64MethodDef); return ReadEmulated(bytes); } @@ -310,7 +314,7 @@ public override short ReadInt16() { var bytes = ReadBytes(2); - _vm.CallStack.Push(_readInt16MethodDef); + _thread.CallStack.Push(_readInt16MethodDef); return ReadEmulated(bytes); } @@ -318,7 +322,7 @@ public override ushort ReadUInt16() { var bytes = ReadBytes(2); - _vm.CallStack.Push(_readUInt16MethodDef); + _thread.CallStack.Push(_readUInt16MethodDef); return ReadEmulated(bytes); } @@ -326,7 +330,7 @@ public override float ReadSingle() { var bytes = ReadBytes(4); - _vm.CallStack.Push(_readSingleMethodDef); + _thread.CallStack.Push(_readSingleMethodDef); return ReadEmulated(bytes); } @@ -334,7 +338,7 @@ public override double ReadDouble() { var bytes = ReadBytes(8); - _vm.CallStack.Push(_readDoubleMethodDef); + _thread.CallStack.Push(_readDoubleMethodDef); return ReadEmulated(bytes); } @@ -343,7 +347,7 @@ public override decimal ReadDecimal() { var bytes = ReadBytes(16); - _vm.CallStack.Push(_readDecimalMethodDef); + _thread.CallStack.Push(_readDecimalMethodDef); return ReadEmulated(bytes); } diff --git a/src/EazyDevirt/EazyDevirt.csproj b/src/EazyDevirt/EazyDevirt.csproj index e815f77..3be6a62 100644 --- a/src/EazyDevirt/EazyDevirt.csproj +++ b/src/EazyDevirt/EazyDevirt.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/EazyDevirt/PatternMatching/Patterns/OpCodes/Arg.cs b/src/EazyDevirt/PatternMatching/Patterns/OpCodes/Arg.cs index 8627041..9086a8c 100644 --- a/src/EazyDevirt/PatternMatching/Patterns/OpCodes/Arg.cs +++ b/src/EazyDevirt/PatternMatching/Patterns/OpCodes/Arg.cs @@ -1,8 +1,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Serialized; using AsmResolver.PE.DotNet.Cil; -using AsmResolver.PE.DotNet.Metadata.Tables.Rows; -using EazyDevirt.Core.Abstractions; +using AsmResolver.PE.DotNet.Metadata.Tables; using EazyDevirt.Core.Abstractions.Interfaces; using EazyDevirt.Core.Architecture; using EazyDevirt.Devirtualization; From f8dd4d879a5a9a44a0b918d6813545648da7b6cb Mon Sep 17 00:00:00 2001 From: puff <33184334+puff@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:08:21 -0500 Subject: [PATCH 4/4] More accurate ResolveMethod --- src/EazyDevirt/Core/IO/Resolver.cs | 265 ++++++++---------- .../Options/DevirtualizationOptions.cs | 8 + .../Options/DevirtualizationOptionsBinder.cs | 7 +- src/EazyDevirt/Program.cs | 14 +- 4 files changed, 143 insertions(+), 151 deletions(-) diff --git a/src/EazyDevirt/Core/IO/Resolver.cs b/src/EazyDevirt/Core/IO/Resolver.cs index c27f4c0..c2580fc 100644 --- a/src/EazyDevirt/Core/IO/Resolver.cs +++ b/src/EazyDevirt/Core/IO/Resolver.cs @@ -64,14 +64,14 @@ public Resolver(DevirtualizationContext ctx) } } - var typeDefOrRef = TypeNameParser.Parse(Ctx.Module, data.Name).GetUnderlyingTypeDefOrRef(); - if (typeDefOrRef is null) + var typeSig = TypeNameParser.Parse(Ctx.Module, data.Name); + if (typeSig is null) throw new Exception($"Failed to parse vm type {data.Name}"); if (data.HasGenericTypeParameters) - typeDefOrRef = typeDefOrRef.MakeGenericInstanceType(data.GenericParameters.Select(x => ResolveType(x)!.ToTypeSignature()).ToArray()).ToTypeDefOrRef(); + typeSig = typeSig.MakeGenericInstanceType(data.GenericParameters.Select(x => ResolveType(x)!.ToTypeSignature()).ToArray()); - var imported = typeDefOrRef.ImportWith(Ctx.Importer); + var imported = typeSig.ToTypeDefOrRef().ImportWith(Ctx.Importer); Cache.Add(position, imported); return imported; } @@ -115,12 +115,6 @@ public Resolver(DevirtualizationContext ctx) return null; } - private bool VerifyMethodParameters(MethodSignatureBase method, VMMethodData data) => - method.ParameterTypes.Count == data.Parameters.Length && method.ParameterTypes - .Zip(data.Parameters).All(x => - x.First is GenericParameterSignature or GenericInstanceTypeSignature || x.First.FullName == - ResolveType(x.Second.Position)?.FullName); - private bool VerifyMethodParameters(MethodDefinition method, VMMethodInfo data) { var skip = 0; @@ -138,25 +132,6 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu x.First.ParameterType?.FullName == ResolveType(x.Second.VMType)?.FullName); } - private bool VerifyMethodParameters(MethodDefinition method, VMMethodData data) - { - var skip = 0; - if (method.Parameters.ThisParameter != null && data.Parameters.Length > 0 && - method.Parameters.ThisParameter.ParameterType.FullName == - ResolveType(data.Parameters[0].Position)?.FullName) - skip++; - - return (method.Parameters.Count == data.Parameters.Length || method.Parameters.Count == data.Parameters.Length - skip) && method.Parameters - .Zip(data.Parameters.Skip(skip)).All(x => - // TODO: Should probably resolve these generic parameter signatures and verify them too - x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignature || - (x.First.ParameterType is TypeSpecificationSignature tss && - (tss.BaseType is GenericParameterSignature or GenericInstanceTypeSignature || - tss.BaseType.FullName == ResolveType(x.Second.Position)?.FullName)) || - x.First.ParameterType?.FullName == ResolveType(x.Second.Position)?.FullName); - ; - } - private MethodDefinition? ResolveMethod(TypeDefinition? declaringType, VMMethodInfo data) => declaringType?.Methods.FirstOrDefault(m => m.Name == data.Name && (m.Signature?.ReturnType is GenericParameterSignature or GenericInstanceTypeSignature || @@ -167,16 +142,12 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu ResolveType(data.VMReturnType)?.FullName) && VerifyMethodParameters(m, data)); - private MethodDefinition? ResolveMethod(TypeDefinition? declaringType, VMMethodData data) => - declaringType?.Methods.FirstOrDefault(m => m.Name == data.Name - // TODO: Should probably resolve these generic parameter signatures and verify them too - && (m.Signature?.ReturnType is GenericParameterSignature or GenericInstanceTypeSignature || - (m.Signature?.ReturnType is TypeSpecificationSignature tss && - (tss.BaseType is GenericParameterSignature or GenericInstanceTypeSignature || - tss.BaseType.FullName == ResolveType(data.ReturnType.Position)?.FullName)) || - m.Signature?.ReturnType?.FullName == - ResolveType(data.ReturnType.Position)?.FullName) - && VerifyMethodParameters(m, data)); + private TypeSignature GetBaseTypeOfTypeSignature(TypeSignature ts) + { + while (ts is TypeSpecificationSignature tss) + ts = tss.BaseType; + return ts; + } public IMethodDescriptor? ResolveMethod(int position) { @@ -209,134 +180,138 @@ x.First.ParameterType is GenericParameterSignature or GenericInstanceTypeSignatu var declaringTypeDefOrRef = ResolveType(data.DeclaringType.Position); if (declaringTypeDefOrRef == null) { - Ctx.Console.Error($"Unable to resolve declaring type on vm method {data.Name}"); + Ctx.Console.Error($"Failed to resolve declaring type on vm method {data.Name}!"); return null; } var returnType = ResolveType(data.ReturnType.Position); if (returnType == null) { - Ctx.Console.Error($"Failed to resolve vm method {data.Name} return type!"); + Ctx.Console.Error($"Failed to resolve return type on vm method {data.Name}!"); return null; } - var declaringTypeDefOrRefUnadorned = declaringTypeDefOrRef.Resolve(); - if (declaringTypeDefOrRef.Scope?.GetAssembly()?.Name != Ctx.Module.Assembly?.Name) + var declaringTypeGenericContext = GenericContext.FromType(declaringTypeDefOrRef); + var parameters = data.Parameters.Select(x => ResolveType(x)?.ToTypeSignature()!).ToArray(); + + // Methods utilizing generics need to be resolved to get 100% accuracy. + // This is because Eazfuscator uses the actual generic type instead of the generic parameter type. + // Example: + // The signature we need is: + // !0 class TestClass`1::TestMethod(!0, string, !!1, !!0) + // However, the signature Eazfuscator gives us is: + // int32 class TestClass`1::TestMethod(int32, string, int32, string) + // We can't convert them into their generic parameter type because we don't know the position of the generic type in the arguments, so we could inadvertently replace the wrong argument: + // !0 class TestClass`1::TestMethod(!0, !!0, !!1, string) + var declaringTypeIsGeneric = declaringTypeDefOrRef is TypeSpecification { Signature: GenericInstanceTypeSignature { TypeArguments.Count: > 0 } declaringTypeGenericSig}; + if (data.HasGenericParameters || declaringTypeIsGeneric) { - var importedMemberRef = Ctx.Module.GetImportedMemberReferences().FirstOrDefault(x => - x.IsMethod - && x.DeclaringType?.FullName == (declaringTypeDefOrRefUnadorned is null ? declaringTypeDefOrRef.FullName : declaringTypeDefOrRefUnadorned.FullName) - && x.Name == data.Name - && x.Signature is MethodSignature ms - // TODO: Fix resolving imported member references of methods returning a generic parameter - && (ms.ReturnType is GenericParameterSignature or GenericInstanceTypeSignature || - ms.ReturnType.FullName == returnType.FullName) - && ms.GenericParameterCount == data.GenericParameters.Length - && VerifyMethodParameters(ms, data)); - - if (importedMemberRef != null) + var declaringTypeDef = declaringTypeDefOrRef.Resolve(); + if (declaringTypeDef is null) { - if (data.HasGenericParameters) + if (Ctx.Options.RequireDepsForGenericMethods) { - var importedMemberRefGenerics = importedMemberRef - .MakeGenericInstanceMethod( - data.GenericParameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray()) - .ImportWith(Ctx.Importer); - - Cache.Add(position, importedMemberRefGenerics); - return importedMemberRefGenerics; + Ctx.Console.Error($"Unable to resolve generic vm method {data.Name} declaring type (Assembly: {declaringTypeDefOrRef.Scope?.GetAssembly()?.FullName})!"); + return null; } - - var importedMemberRefImported = importedMemberRef.ImportWith(Ctx.Importer); - Cache.Add(position, importedMemberRefImported); - return importedMemberRefImported; - } - } - - var declaringType = declaringTypeDefOrRef.Resolve(); - if (declaringType != null) - { - var methodDef = ResolveMethod(declaringType, data); - if (data.HasGenericParameters) - { - var methodDefGenerics = methodDef? - .MakeGenericInstanceMethod(data.GenericParameters - .Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray()) - .ImportWith(Ctx.Importer); - - Cache.Add(position, methodDefGenerics); - return methodDefGenerics; - } - - var methodDefImported = methodDef?.ImportWith(Ctx.Importer); - Cache.Add(position, methodDefImported); - return methodDefImported; - } - - // stuff below should only execute on types that aren't able to be resolved (so hopefully never) - - var declaringTypeSig = declaringTypeDefOrRef.ToTypeSignature(); - var parameters = data.Parameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray(); - var genericArgs = data.GenericParameters.Select(g => ResolveType(g.Position)!.ToTypeSignature()).ToArray(); - var newParams = new List(); - // convert generic parameters to their indexes (!!0, !0) - foreach (var parameter in parameters) - { - // TODO: Search through all methods instead of just converting like this - // these might not resolve or convert correctly if there are other parameters that use the type specified in the generic arg - // a much better way would be to search for the method through declaringType.Methods and check the generics count and other stuff to ensure it's the correct method - for (var gi = 0; gi < genericArgs.Length; gi++) - { - var genericArg = genericArgs[gi]; + Ctx.Console.Warning($"Unable to resolve generic vm method {data.Name} declaring type (Assembly: {declaringTypeDefOrRef.Scope?.GetAssembly()?.FullName})! Method reference may be broken."); - // convert generic parameters into their index form (!!0) - if (SignatureComparer.Default.Equals(genericArg, parameter)) - newParams.Add(new GenericParameterSignature(GenericParameterType.Method, gi)); + // This is inaccurate and will probably break your method signatures. + var methodSignature = data.IsStatic + ? MethodSignature.CreateStatic(returnType.ToTypeSignature(), data.GenericParameters.Length, parameters) + : MethodSignature.CreateInstance(returnType.ToTypeSignature(), data.GenericParameters.Length, parameters); - // convert return type to its index form (!!0) if it matches - if (SignatureComparer.Default.Equals(genericArg, returnType.ToTypeSignature())) - returnType = new GenericParameterSignature(GenericParameterType.Method, gi).ToTypeDefOrRef(); + var methodRef = declaringTypeDefOrRef.CreateMemberReference(data.Name, methodSignature); + if (data.HasGenericParameters) + { + var importedMethodGenericInstance = methodRef + .MakeGenericInstanceMethod(data.GenericParameters + .Select(x => ResolveType(x)?.ToTypeSignature()!).ToArray()).ImportWith(Ctx.Importer); + Cache.Add(position, importedMethodGenericInstance); + return importedMethodGenericInstance.ImportWith(Ctx.Importer); + } + + var importedMethodRef = methodRef.ImportWith(Ctx.Importer); + Cache.Add(position, importedMethodRef); + return importedMethodRef; } - if (declaringTypeSig is GenericInstanceTypeSignature declaringTypeGenericSig) + // Look for matching methods + foreach (var method in declaringTypeDef.Methods) { - // convert generic type arguments in method parameters to their index form (!0) - var f = declaringTypeGenericSig.TypeArguments.FirstOrDefault(x => x.FullName == parameter.FullName); - if (f != null) - newParams.Add(new GenericParameterSignature(GenericParameterType.Type, - declaringTypeGenericSig.TypeArguments.IndexOf(f))); - else + if (method.IsStatic == data.IsStatic + && method.Name == data.Name + && method.GenericParameters.Count == data.GenericParameters.Length) { - // convert generic return type to its index form (!0) - f = declaringTypeGenericSig.TypeArguments.FirstOrDefault(x => x.FullName == returnType.FullName); - if (f != null) - returnType = new GenericParameterSignature(GenericParameterType.Type, - declaringTypeGenericSig.TypeArguments.IndexOf(f)).ToTypeDefOrRef(); + // Skip past ThisParameter when comparing method parameters + var skip = 0; + if (method.Parameters.ThisParameter != null + && data.Parameters.Length > 0 + && SignatureComparer.Default.Equals(method.Parameters.ThisParameter.ParameterType, parameters[0])) + skip++; + + // Check method parameters + if (method.Parameters.Count == parameters.Length + || method.Parameters.Count == parameters.Length - skip + && method.Parameters.Zip(parameters.Skip(skip)).All(x => SignatureComparer.Default.Equals(x.First.ParameterType, x.Second))) + { + // Found a potential match, now build signatures from both the real method and vm method data and compare them to ensure we have the correct method + + // Build signature from vm method data + var vmGenericParameters = data.GenericParameters.Select(x => ResolveType(x)?.ToTypeSignature()!) + .ToArray(); + var vmMethodSig = (data.IsStatic + ? MethodSignature.CreateStatic(returnType.ToTypeSignature(), vmGenericParameters.Length, + parameters) + : MethodSignature.CreateInstance(returnType.ToTypeSignature(), + vmGenericParameters.Length, parameters)) + .InstantiateGenericTypes(declaringTypeGenericContext); + + // Instantiate generic types for real method from vm method data + var realParameters = method.Parameters.Select(x => x.ParameterType).ToArray(); + for (var i = 0; i < parameters.Length; i++) + if (GetBaseTypeOfTypeSignature(parameters[i]) is GenericParameterSignature) + realParameters[i] = vmGenericParameters[i]; + + var realReturnType = method.Signature!.ReturnType; + if (GetBaseTypeOfTypeSignature(realReturnType) is GenericParameterSignature) + realReturnType = returnType.ToTypeSignature(); + + var realMethodSignature = (method.IsStatic + ? MethodSignature.CreateStatic(realReturnType, vmGenericParameters.Length, + realParameters) + : MethodSignature.CreateInstance(realReturnType, vmGenericParameters.Length, + realParameters)) + .InstantiateGenericTypes(declaringTypeGenericContext); + + // Compare instantiated real method signature with our signature built from vm method data + if (!SignatureComparer.Default.Equals(realMethodSignature, vmMethodSig)) + continue; + + if (data.HasGenericParameters) + { + + var genericInstanceMethod = declaringTypeDefOrRef.CreateMemberReference(method.Name, method.Signature) + .MakeGenericInstanceMethod(vmGenericParameters).ImportWith(Ctx.Importer); + Cache.Add(position, genericInstanceMethod); + return genericInstanceMethod; + } + + var importedMethod = declaringTypeDefOrRef.CreateMemberReference(method.Name, method.Signature).ImportWith(Ctx.Importer); + Cache.Add(position, importedMethod); + return importedMethod; + } } } - else - newParams.Add(parameter); } - - var memberRef = declaringTypeDefOrRef - .CreateMemberReference(data.Name, data.IsStatic - ? MethodSignature.CreateStatic( - returnType.ToTypeSignature(), data.GenericParameters.Length, - newParams) - : MethodSignature.CreateInstance( - returnType.ToTypeSignature(), data.GenericParameters.Length, - newParams)); - - if (data.HasGenericParameters) - { - var memberRefGenerics = memberRef.MakeGenericInstanceMethod(genericArgs).ImportWith(Ctx.Importer); - Cache.Add(position, memberRefGenerics); - return memberRefGenerics; - } - - var memberRefImported = memberRef.ImportWith(Ctx.Importer); - Cache.Add(position, memberRefImported); - return memberRefImported; + + var signature = data.IsStatic + ? MethodSignature.CreateStatic(returnType.ToTypeSignature(), data.GenericParameters.Length, parameters) + : MethodSignature.CreateInstance(returnType.ToTypeSignature(), data.GenericParameters.Length, parameters); + + var methodDefOrRef = (IMethodDefOrRef)declaringTypeDefOrRef.CreateMemberReference(data.Name, signature).ImportWith(Ctx.Importer); + Cache.Add(position, methodDefOrRef); + return methodDefOrRef; } public IMemberDescriptor? ResolveToken(int position) diff --git a/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptions.cs b/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptions.cs index aa26faa..e277da3 100644 --- a/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptions.cs +++ b/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptions.cs @@ -61,4 +61,12 @@ internal record DevirtualizationOptions /// This only matters if you're using the Save Anyway option /// public bool OnlySaveDevirted { get; init; } + + /// + /// Require dependencies when resolving generic methods + /// + /// + /// If this is disabled, methods utilizing generics (type or method args) may not have proper signatures if dependencies aren't able to be resolved + /// + public bool RequireDepsForGenericMethods { get; init; } } \ No newline at end of file diff --git a/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptionsBinder.cs b/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptionsBinder.cs index 88127c5..226c524 100644 --- a/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptionsBinder.cs +++ b/src/EazyDevirt/Devirtualization/Options/DevirtualizationOptionsBinder.cs @@ -13,10 +13,11 @@ internal class DevirtualizationOptionsBinder : BinderBase _keepTypesOption; private readonly Option _saveAnywayOption; private readonly Option _onlySaveDevirtedOption; + private readonly Option _requireDepsForGenericMethods; public DevirtualizationOptionsBinder(Argument assemblyArgument, Argument outputPathArgument, Option verbosityOption, Option preserveAllOption, Option noVerifyOption, Option keepTypesOption, Option saveAnywayOption, - Option onlySaveDevirtedOption) + Option onlySaveDevirtedOption, Option requireDepsForGenericMethods) { _assemblyArgument = assemblyArgument; _outputPathArgument = outputPathArgument; @@ -26,6 +27,7 @@ public DevirtualizationOptionsBinder(Argument assemblyArgument, Argume _keepTypesOption = keepTypesOption; _saveAnywayOption = saveAnywayOption; _onlySaveDevirtedOption = onlySaveDevirtedOption; + _requireDepsForGenericMethods = requireDepsForGenericMethods; } protected override DevirtualizationOptions GetBoundValue(BindingContext bindingContext) => @@ -38,6 +40,7 @@ protected override DevirtualizationOptions GetBoundValue(BindingContext bindingC NoVerify = bindingContext.ParseResult.GetValueForOption(_noVerifyOption), KeepTypes = bindingContext.ParseResult.GetValueForOption(_keepTypesOption), SaveAnyway = bindingContext.ParseResult.GetValueForOption(_saveAnywayOption), - OnlySaveDevirted = bindingContext.ParseResult.GetValueForOption(_onlySaveDevirtedOption) + OnlySaveDevirted = bindingContext.ParseResult.GetValueForOption(_onlySaveDevirtedOption), + RequireDepsForGenericMethods = bindingContext.ParseResult.GetValueForOption(_requireDepsForGenericMethods) }; } \ No newline at end of file diff --git a/src/EazyDevirt/Program.cs b/src/EazyDevirt/Program.cs index 7941761..4a98a6b 100644 --- a/src/EazyDevirt/Program.cs +++ b/src/EazyDevirt/Program.cs @@ -88,6 +88,10 @@ private static Parser BuildParser() var onlySaveDevirtedOption = new Option(new[] { "--only-save-devirted"}, "Only saves successfully devirtualized methods (This option only matters if you use the save anyway option)"); onlySaveDevirtedOption.SetDefaultValue(false); + var requireDepsForGenerics = new Option(new[] { "--require-deps-for-generics"}, "Require dependencies when resolving generic methods for accuracy"); + requireDepsForGenerics.SetDefaultValue(true); + + var rootCommand = new RootCommand("is an open-source tool that automatically restores the original IL code " + "from an assembly virtualized with Eazfuscator.NET") { @@ -98,12 +102,14 @@ private static Parser BuildParser() noVerifyOption, keepTypesOption, saveAnywayOption, - onlySaveDevirtedOption + onlySaveDevirtedOption, + requireDepsForGenerics }; - - rootCommand.SetHandler(Run, + + rootCommand.SetHandler(Run, new DevirtualizationOptionsBinder(inputArgument, outputArgument, verbosityOption, - preserveAllOption, noVerifyOption, keepTypesOption, saveAnywayOption, onlySaveDevirtedOption)); + preserveAllOption, noVerifyOption, keepTypesOption, saveAnywayOption, onlySaveDevirtedOption, + requireDepsForGenerics)); return new CommandLineBuilder(rootCommand) .UseDefaults()