diff --git a/Directory.Build.props b/Directory.Build.props index 614e3282c..cd1dada47 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,8 @@ https://github.com/Washi1337/AsmResolver git 10 - 5.3.0 + 5.4.0 + true diff --git a/appveyor.yml b/appveyor.yml index 81316e3d6..a550e2f6d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ - master image: Visual Studio 2022 - version: 5.3.0-master-build.{build} + version: 5.4.0-master-build.{build} configuration: Release skip_commits: @@ -23,7 +23,7 @@ deploy: provider: NuGet api_key: - secure: L3fXsS7umzD8zwAvTsdGxOg/E6tQ4IR4MfwBAcO8elE7ZwjZ8HO8UPwjiWbp4RMw + secure: orcP0C1iuBVKxnv/uAUehgU1KEI/lCpbSxDqbckd3sZ7XxcuENj6PrExs6SdJIf1 skip_symbols: false artifact: /.*\.nupkg/ @@ -33,7 +33,7 @@ - development image: Visual Studio 2022 - version: 5.3.0-dev-build.{build} + version: 5.4.0-dev-build.{build} configuration: Release skip_commits: diff --git a/docs/guides/dotnet/basics.md b/docs/guides/dotnet/basics.md index a1e79b54a..3e145a98a 100644 --- a/docs/guides/dotnet/basics.md +++ b/docs/guides/dotnet/basics.md @@ -24,7 +24,16 @@ references in the `KnownCorLibs` class to target another version of the library. ``` csharp -var module = new ModuleDefinition("MyModule.exe", KnownCorLibs.SystemRuntime_v4_2_2_0); +var module = new ModuleDefinition("MyModule.dll", KnownCorLibs.SystemRuntime_v4_2_2_0); +``` + +If you have a .NET runtime identifier as specified in the +`TargetFrameworkAttribute` of an assembly, you can use the `DotNetRuntimeInfo` +structure to get the corresponding default corlib: + +``` csharp +var runtime = DotNetRuntimeInfo.Parse(".NETCoreApp,Version=v3.1"); +var module = new ModuleDefinition("MyModule.dll", runtime.GetDefaultCorLib()); ``` ## Opening a .NET module diff --git a/docs/guides/dotnet/bundles.md b/docs/guides/dotnet/bundles.md index c4243cc2b..3e2e4da49 100644 --- a/docs/guides/dotnet/bundles.md +++ b/docs/guides/dotnet/bundles.md @@ -29,15 +29,11 @@ The major version number refers to the file format that should be used when saving the manifest. Below an overview of the values that are recognized by the CLR: - --------------------------------------------------- - .NET Version Number Bundle File Format Version - ---------------------- ---------------------------- - .NET Core 3.1 1 - - .NET 5.0 2 - - .NET 6.0 6 - --------------------------------------------------- +| Minimum .NET Version Number | Bundle File Format Version | +|-----------------------------|-----------------------------| +| .NET Core 3.1 | 1 | +| .NET 5.0 | 2 | +| .NET 6.0 | 6 | To create a new bundle with a specific bundle identifier, use the overloaded constructor @@ -108,8 +104,12 @@ var manifest = BundleManifest.FromDataSource(contents, bundleAddress); Constructing new bundled executable files requires a template file that AsmResolver can base the final output on. This is similar how .NET -compilers themselves do this as well. By default, the .NET SDK installs -template binaries in one of the following directories: +compilers themselves do this as well. + +### Using the .NET SDK's template + +By default, the .NET SDK installs template apphost binaries in one of the +following directories: - `/sdk//AppHostTemplate` - `/packs/Microsoft.NETCore.App.Host.//runtimes//native` @@ -150,6 +150,15 @@ manifest.WriteUsingTemplate( imagePathToCopyHeadersFrom: @"C:\Path\To\Original\HelloWorld.exe")); ``` +> [!NOTE] +> `BundleManifest` and `BundlerParameters` also define overloads of the +> `WriteUsingTemplate` and `FromTemplate` / `FromExistingBundle` +> respectively, taking `byte[]`, `IDataSource` or `IPEImage` instances +> instead of file paths. + + +### Using an input binary as template + If you do not have access to a template file (e.g., if the SDK is not installed) but have another existing PE file that was packaged in a similar fashion, it is then possible to use this file as a template @@ -179,10 +188,6 @@ manifest.WriteUsingTemplate( > the input file to determine the parameters for patching the input file. > As heuristics are not perfect, this is not guaranteed to always work. -`BundleManifest` and `BundlerParameters` also define overloads of the -`WriteUsingTemplate` and `FromTemplate` / `FromExistingBundle` -respectively, taking `byte[]`, `IDataSource` or `IPEImage` instances -instead of file paths. ## Managing Files diff --git a/docs/guides/dotnet/cloning.md b/docs/guides/dotnet/cloning.md index 4ec2d8e7c..37b54fd21 100644 --- a/docs/guides/dotnet/cloning.md +++ b/docs/guides/dotnet/cloning.md @@ -103,9 +103,8 @@ cloner.Include(rectangleType); cloner.Include(vectorType); ``` -`Include` returns the same `MemberCloner` instance. It is therefore also -possible to create a long method chain of members to include in the -cloning process. +`Include` returns the same `MemberCloner` instance, allowing for more fluent +syntax: ``` csharp cloner @@ -201,7 +200,7 @@ All references to methods defined in the `System.Runtime.CompilerServices` namespace will then be mapped to the appropriate method definitions if they exist in the target module. -See [Common Caveats using the Importer](/guides/dotnet/importing.html#common-caveats-using-the-importer) +See [Common Caveats using the Importer](importing.md#common-caveats-using-the-importer) for more information on reference importing and its caveats. ## Post-processing of cloned members @@ -255,7 +254,24 @@ var cloner = new MemberCloner(destinationModule, (original, cloned) => { }); ``` -## Injecting the cloned members +Multiple listeners can also be chained together using the `AddListener` method: + +```csharp +var cloner = new MemberCloner(destinationModule); +cloner.AddListener(new MyListener1()); +cloner.AddListener(new MyListener2()); +``` + +This can also be used in the fluent syntax form: + +```csharp +var cloner = new MemberCloner(destinationModule) + .AddListener(new MyListener1()) + .AddListener(new MyListener2()); +``` + + +## Injecting cloned members The `Clone` method returns a `MemberCloneResult`, which contains a register of all members cloned by the member cloner. @@ -288,17 +304,37 @@ foreach (var clonedType in clonedTypes) destinationModule.TopLevelTypes.Add(clonedType); ``` -However, since injecting the cloned top level types is a very common -use-case for the cloner, AsmResolver defines the -`InjectTypeClonerListener` class that implements a cloner listener that -injects all top-level types automatically into the destination module. -In such a case, the code can be reduced to the following: +Injecting the cloned top level types is a very common use-case for the cloner. +AsmResolver defines the `InjectTypeClonerListener` class that implements a +cloner listener that injects all top-level types automatically into +the destination module. In such a case, the code can be reduced to the following: ``` csharp -new MemberCloner(destinationModule, new InjectTypeClonerListener(destinationModule)) +new MemberCloner(destinationModule) .Include(rectangleType) .Include(vectorType) + .AddListener(new InjectTypeClonerListener(destinationModule)) .Clone(); // `destinationModule` now contains copies of `rectangleType` and `vectorType`. ``` + +AsmResolver provides another built-in listener `AssignTokensClonerListener` that +also allows for preemptively assigning new metadata tokens automatically, should +this be necessary. + +``` csharp +new MemberCloner(destinationModule) + .Include(rectangleType) + .Include(vectorType) + .AddListener(new InjectTypeClonerListener(destinationModule)) + .AddListener(new AssignTokensClonerListener(destinationModule)) + .Clone(); + +// `destinationModule` now contains copies of `rectangleType` and `vectorType` +// and all its members have been assigned a metadata token preemptively. +``` + +The `AssignTokensClonerListener` uses the target module's `TokenAllocator`. See +[Metadata Token Allocation](token-allocation.md) for more information on how this +class operates. \ No newline at end of file diff --git a/docs/guides/dotnet/dynamic-methods.md b/docs/guides/dotnet/dynamic-methods.md index 290fc4d12..d97ac225c 100644 --- a/docs/guides/dotnet/dynamic-methods.md +++ b/docs/guides/dotnet/dynamic-methods.md @@ -67,6 +67,6 @@ contextModule.GetOrCreateModuleType().Methods.Add(definition); contextModule.Write("Program.patched.dll"); ``` -See [Obtaining methods and fields](/guides/dotnet/member-tree.html#obtaining-methods-and-fields) +See [Obtaining methods and fields](member-tree.md#obtaining-methods-and-fields) and [CIL Method Bodies](managed-method-bodies.md) for more information on how to use `MethodDefinition` objects. diff --git a/docs/guides/dotnet/managed-method-bodies.md b/docs/guides/dotnet/managed-method-bodies.md index b0771a84f..6cda54423 100644 --- a/docs/guides/dotnet/managed-method-bodies.md +++ b/docs/guides/dotnet/managed-method-bodies.md @@ -290,7 +290,7 @@ body.Instructions[i].ReplaceWith(CilOpCodes.Ldc_I4, 1234); ``` Since it is very common to replace instructions with a -[nop]{.title-ref}, AsmResolver also defines a special `ReplaceWithNop` +`nop`, AsmResolver also defines a special `ReplaceWithNop` helper function: ``` csharp diff --git a/docs/guides/dotnet/unmanaged-method-bodies.md b/docs/guides/dotnet/unmanaged-method-bodies.md index 9d931c300..d72caa73c 100644 --- a/docs/guides/dotnet/unmanaged-method-bodies.md +++ b/docs/guides/dotnet/unmanaged-method-bodies.md @@ -54,7 +54,7 @@ module.IsBit32Required = true; As per ECMA-335 specification, a method definition can only represent a native function via Platform Invoke (P/Invoke). While P/Invoke is usually used for importing functions from external libraries (such as -[kernel32.dll]{.title-ref}), it is also needed for implementing native +`kernel32.dll`), it is also needed for implementing native methods that are defined within the current .NET module itself. Therefore, to be able to assign a valid native body to a method, the right flags need to be set in both the `Attributes` and `ImplAttributes` diff --git a/docs/guides/faq.md b/docs/guides/faq.md index dddf2448c..4cdb63368 100644 --- a/docs/guides/faq.md +++ b/docs/guides/faq.md @@ -25,9 +25,9 @@ writing). AsmResolver often can ignore and recover from kinds of errors, but this is not enabled by default. To enable these, please refer to -[Advanced PE Image Reading](/guides/peimage/advanced-pe-reading.html#custom-error-handling) (PE) -or [Advanced Module Reading](/guides/dotnet/advanced-module-reading.html#pe-image-reading-parameters) (.NET), -and [Image Builder Diagnostics](/guides/dotnet/advanced-pe-image-building.html#image-builder-diagnostics) (.NET). +[Advanced PE Image Reading](peimage/advanced-pe-reading.md#custom-error-handling) (PE) +or [Advanced Module Reading](dotnet/advanced-module-reading.md#pe-image-reading-parameters) (.NET), +and [Image Builder Diagnostics](dotnet/advanced-pe-image-building.md#image-builder-diagnostics) (.NET). Be careful with ignoring errors though. Especially for disabling writer verification can cause the output to not work anymore unless you know what you are doing. diff --git a/docs/guides/pefile/sections.md b/docs/guides/pefile/sections.md index d8ddf9530..407239374 100644 --- a/docs/guides/pefile/sections.md +++ b/docs/guides/pefile/sections.md @@ -64,7 +64,7 @@ Some sections (such as `.data` or `.bss`) contain uninitialized data, and might be resized in virtual memory at runtime. As such, the virtual size of the contents might be different than its physical size. To make dynamically sized sections, it is possible to use the `VirtualSegment` -to decorate a normal [ISegment]{.title-ref} with a different virtual +to decorate a normal `ISegment` with a different virtual size. ``` csharp @@ -76,7 +76,7 @@ file.Sections.Add(section); ``` For more advanced section building, see -[Building Sections](/guides/peimage/pe-building.html#building-sections) +[Building Sections](../peimage/pe-building.md#building-sections) [Reading and Writing File Segments](../core/segments.md). ## Updating Section Offsets diff --git a/docs/guides/peimage/basics.md b/docs/guides/peimage/basics.md index 2853fd618..8b0e0bf75 100644 --- a/docs/guides/peimage/basics.md +++ b/docs/guides/peimage/basics.md @@ -17,8 +17,8 @@ var peImage = new PEImage(); ## Opening a PE image -Opening an image can be done through one of the [FromXXX]{.title-ref} -methods from the `PEImage` class: +Opening an image can be done through one of the `FromXXX` methods from the +`PEImage` class: ``` csharp byte[] raw = ... diff --git a/docs/guides/peimage/dotnet.md b/docs/guides/peimage/dotnet.md index fd491a183..f4106ef61 100644 --- a/docs/guides/peimage/dotnet.md +++ b/docs/guides/peimage/dotnet.md @@ -321,7 +321,7 @@ var fieldRva = new FieldRvaRow(myData.ToReference(), 0); ## TypeReference Hash (TRH) -Similar to the :ref:`pe-import-hash`, the TypeReference Hash (TRH) can be used +Similar to the [Import Hash](imports.md#import-hash), the TypeReference Hash (TRH) can be used to help identify malware families written in a .NET language. However, unlike the Import Hash, the TRH is based on the names of all imported type references instead of the symbols specified in the imports directory of the PE. This is a diff --git a/docs/guides/peimage/exports.md b/docs/guides/peimage/exports.md index 3d70c6cd2..a00d3fb75 100644 --- a/docs/guides/peimage/exports.md +++ b/docs/guides/peimage/exports.md @@ -4,8 +4,7 @@ Dynamically linked libraries (DLLs) often expose symbols through defining exports in the exports directory. The `IPEImage` interface exposes the `Exports` property, exposing a -mutable instance of [ExportDirectory]{.title-ref}, which defines the -following properties: +mutable instance of `ExportDirectory`, which defines the following properties: - `Name`: The name of the dynamically linked library. - `BaseOrdinal`: The base ordinal of all exported symbols. diff --git a/docs/guides/peimage/imports.md b/docs/guides/peimage/imports.md index 6f7d28cd7..a0699039b 100644 --- a/docs/guides/peimage/imports.md +++ b/docs/guides/peimage/imports.md @@ -78,4 +78,4 @@ While the Import Hash can be a good identifier for native PE images, for .NET images this is not the case. .NET images usually only import a single external symbol (either `mscoree.dll!_CorExeMain` or `mscoree.dll!_CorDllMain`), and as such they will almost always have the -exact same Import Hash. See [TypeReference Hash (TRH)](/guides/peimage/dotnet.html#typereference-hash-trh) for an alternative for .NET images. +exact same Import Hash. See [TypeReference Hash (TRH)](dotnet.md#typereference-hash-trh) for an alternative for .NET images. diff --git a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj index 5519f663e..f3e66f4b9 100644 --- a/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj +++ b/src/AsmResolver.DotNet.Dynamic/AsmResolver.DotNet.Dynamic.csproj @@ -23,6 +23,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj index 344e5b44a..79a63a67f 100644 --- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj +++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj @@ -24,11 +24,15 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/AsmResolver.DotNet/AssemblyDefinition.cs b/src/AsmResolver.DotNet/AssemblyDefinition.cs index e0b488f06..814fe9f9e 100644 --- a/src/AsmResolver.DotNet/AssemblyDefinition.cs +++ b/src/AsmResolver.DotNet/AssemblyDefinition.cs @@ -245,7 +245,7 @@ public virtual bool TryGetTargetFramework(out DotNetRuntimeInfo info) if (ctor?.DeclaringType is not null && ctor.DeclaringType.IsTypeOf("System.Runtime.Versioning", nameof(TargetFrameworkAttribute)) - && CustomAttributes[i].Signature?.FixedArguments[0].Element is string name + && CustomAttributes[i].Signature?.FixedArguments[0].Element?.ToString() is { } name && DotNetRuntimeInfo.TryParse(name, out info)) { return true; diff --git a/src/AsmResolver.DotNet/AssemblyDescriptor.cs b/src/AsmResolver.DotNet/AssemblyDescriptor.cs index 2447823a7..1a0cf1136 100644 --- a/src/AsmResolver.DotNet/AssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/AssemblyDescriptor.cs @@ -54,12 +54,16 @@ public string FullName { get { - var publicKeyToken = GetPublicKeyToken(); + string cultureString = !Utf8String.IsNullOrEmpty(Culture) + ? Culture + : "neutral"; + + byte[]? publicKeyToken = GetPublicKeyToken(); string publicKeyTokenString = publicKeyToken is not null ? string.Join(string.Empty, publicKeyToken.Select(x => x.ToString("x2"))) : "null"; - return $"{Name}, Version={Version}, PublicKeyToken={publicKeyTokenString}"; + return $"{Name}, Version={Version}, Culture={cultureString}, PublicKeyToken={publicKeyTokenString}"; } } diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs index 52e6c3df8..09a9f3afa 100644 --- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs +++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs @@ -41,7 +41,7 @@ private uint AddResolutionScope(IResolutionScope? scope, bool allowDuplicates, b TableIndex.AssemblyRef => AddAssemblyReference(scope as AssemblyReference, allowDuplicates, preserveRid), TableIndex.TypeRef => AddTypeReference(scope as TypeReference, allowDuplicates, preserveRid), TableIndex.ModuleRef => AddModuleReference(scope as ModuleReference, allowDuplicates, preserveRid), - TableIndex.Module => 0, + TableIndex.Module => new MetadataToken(TableIndex.Module, 1), _ => throw new ArgumentOutOfRangeException(nameof(scope)) }; diff --git a/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs b/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs new file mode 100644 index 000000000..5a1651641 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/AssignTokensClonerListener.cs @@ -0,0 +1,33 @@ +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Provides an implementation of a that preemptively assigns new metadata + /// tokens to the cloned metadata members using the target module's . + /// + public class AssignTokensClonerListener : MemberClonerListener + { + /// + /// Creates a new instance of the token allocator listener. + /// + /// The module that will contain the cloned members. + public AssignTokensClonerListener(ModuleDefinition targetModule) + { + TargetModule = targetModule; + } + + /// + /// Gets the module that will contain the cloned members. + /// + public ModuleDefinition TargetModule + { + get; + } + + /// + public override void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) + { + TargetModule.TokenAllocator.AssignNextAvailableToken((MetadataMember) cloned); + base.OnClonedMember(original, cloned); + } + } +} diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs index 78df400d3..d96da9443 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Fields.cs @@ -42,8 +42,8 @@ private void DeepCopyFields(MemberCloneContext context) { DeepCopyField(context, field); var clonedMember = (FieldDefinition)context.ClonedMembers[field]; - _clonerListener.OnClonedMember(field, clonedMember); - _clonerListener.OnClonedField(field, clonedMember); + _listeners.OnClonedMember(field, clonedMember); + _listeners.OnClonedField(field, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs index af6085a14..205ccf9f5 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Methods.cs @@ -57,8 +57,8 @@ private void DeepCopyMethods(MemberCloneContext context) { DeepCopyMethod(context, method); var clonedMember = (MethodDefinition)context.ClonedMembers[method]; - _clonerListener.OnClonedMember(method, clonedMember); - _clonerListener.OnClonedMethod(method, clonedMember); + _listeners.OnClonedMember(method, clonedMember); + _listeners.OnClonedMethod(method, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs index 7d3e5ea6b..53140cfca 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.Semantics.cs @@ -18,8 +18,8 @@ private void DeepCopyProperties(MemberCloneContext context) declaringType.Properties.Add(clonedProperty); } var clonedMember = clonedProperty; - _clonerListener.OnClonedMember(property, clonedMember); - _clonerListener.OnClonedProperty(property, clonedMember); + _listeners.OnClonedMember(property, clonedMember); + _listeners.OnClonedProperty(property, clonedMember); } } @@ -55,8 +55,8 @@ private void DeepCopyEvents(MemberCloneContext context) declaringType.Events.Add(clonedEvent); } var clonedMember = clonedEvent; - _clonerListener.OnClonedMember(@event, clonedMember); - _clonerListener.OnClonedEvent(@event, clonedMember); + _listeners.OnClonedMember(@event, clonedMember); + _listeners.OnClonedEvent(@event, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs index 64acfe3a5..cf9862e78 100644 --- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs +++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs @@ -17,7 +17,7 @@ namespace AsmResolver.DotNet.Cloning /// public partial class MemberCloner { - private readonly IMemberClonerListener _clonerListener; + private readonly MemberClonerListenerList _listeners; private readonly Func? _importerFactory; private readonly ModuleDefinition _targetModule; @@ -32,7 +32,7 @@ public partial class MemberCloner /// /// The target module to copy the members into. public MemberCloner(ModuleDefinition targetModule) - : this(targetModule, (original, cloned) => { }) + : this(targetModule, null, null) { } @@ -79,7 +79,9 @@ public MemberCloner(ModuleDefinition targetModule, { _targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule)); _importerFactory = importerFactory; - _clonerListener = clonerListener ?? CallbackClonerListener.EmptyInstance; + _listeners = new MemberClonerListenerList(); + if (clonerListener is not null) + _listeners.Add(clonerListener); } /// @@ -262,6 +264,28 @@ public MemberCloner Include(EventDefinition @event) return this; } + /// + /// Adds a member cloner listener to the cloner. + /// + /// The listener to add. + /// The metadata cloner that the listener is added to. + public MemberCloner AddListener(Action listener) + { + _listeners.Add(new CallbackClonerListener(listener)); + return this; + } + + /// + /// Adds a member cloner listener to the cloner. + /// + /// The listener to add. + /// The metadata cloner that the listener is added to. + public MemberCloner AddListener(IMemberClonerListener listener) + { + _listeners.Add(listener); + return this; + } + /// /// Clones all included members. /// @@ -313,8 +337,8 @@ private void DeepCopyTypes(MemberCloneContext context) { DeepCopyType(context, type); var clonedMember = (TypeDefinition)context.ClonedMembers[type]; - _clonerListener.OnClonedMember(type, clonedMember); - _clonerListener.OnClonedType(type, clonedMember); + _listeners.OnClonedMember(type, clonedMember); + _listeners.OnClonedType(type, clonedMember); } } diff --git a/src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs b/src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs new file mode 100644 index 000000000..6385b78a1 --- /dev/null +++ b/src/AsmResolver.DotNet/Cloning/MemberClonerListenerList.cs @@ -0,0 +1,52 @@ +using System.Collections.ObjectModel; + +namespace AsmResolver.DotNet.Cloning +{ + /// + /// Wraps a list of s into a single instance of . + /// + public class MemberClonerListenerList : Collection, IMemberClonerListener + { + /// + public void OnClonedMember(IMemberDefinition original, IMemberDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedMember(original, cloned); + } + + /// + public void OnClonedType(TypeDefinition original, TypeDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedType(original, cloned); + } + + /// + public void OnClonedMethod(MethodDefinition original, MethodDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedMethod(original, cloned); + } + + /// + public void OnClonedField(FieldDefinition original, FieldDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedField(original, cloned); + } + + /// + public void OnClonedProperty(PropertyDefinition original, PropertyDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedProperty(original, cloned); + } + + /// + public void OnClonedEvent(EventDefinition original, EventDefinition cloned) + { + for (int i = 0; i < Items.Count; i++) + Items[i].OnClonedEvent(original, cloned); + } + } +} diff --git a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs index b09c1e09f..eeabe48e1 100644 --- a/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs +++ b/src/AsmResolver.DotNet/Code/Native/NativeSymbolsProvider.cs @@ -23,13 +23,9 @@ public class NativeSymbolsProvider : INativeSymbolsProvider private readonly List _floatingExportedSymbols = new(); /// - public ISymbol ImportSymbol(ISymbol symbol) - { - if (symbol is ImportedSymbol importedSymbol) - return GetImportedSymbol(importedSymbol); - - throw new NotSupportedException($"Symbols of type {symbol.GetType()} are not supported."); - } + public ISymbol ImportSymbol(ISymbol symbol) => symbol is ImportedSymbol importedSymbol + ? GetImportedSymbol(importedSymbol) + : symbol; private ImportedSymbol GetImportedSymbol(ImportedSymbol symbol) { diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs index 0d625ed02..e80d5cae4 100644 --- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs +++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs @@ -166,8 +166,11 @@ public TypeResolution(IAssemblyResolver resolver) public TypeDefinition? ResolveTypeReference(TypeReference? reference) { - var scope = reference?.Scope; - if (reference?.Name is null || scope is null || _scopeStack.Contains(scope)) + if (reference is null) + return null; + + var scope = reference.Scope ?? reference.Module; + if (reference.Name is null || scope is null || _scopeStack.Contains(scope)) return null; _scopeStack.Push(scope); @@ -241,13 +244,6 @@ public TypeResolution(IAssemblyResolver resolver) private TypeDefinition? FindTypeInModule(ModuleDefinition module, Utf8String? ns, Utf8String name) { - for (int i = 0; i < module.ExportedTypes.Count; i++) - { - var exportedType = module.ExportedTypes[i]; - if (exportedType.IsTypeOfUtf8(ns, name)) - return ResolveExportedType(exportedType); - } - for (int i = 0; i < module.TopLevelTypes.Count; i++) { var type = module.TopLevelTypes[i]; @@ -255,6 +251,13 @@ public TypeResolution(IAssemblyResolver resolver) return type; } + for (int i = 0; i < module.ExportedTypes.Count; i++) + { + var exportedType = module.ExportedTypes[i]; + if (exportedType.IsTypeOfUtf8(ns, name)) + return ResolveExportedType(exportedType); + } + return null; } diff --git a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs index d289f45f5..00d733f67 100644 --- a/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs +++ b/src/AsmResolver.DotNet/DotNetRuntimeInfo.cs @@ -68,6 +68,16 @@ public Version Version /// public bool IsNetStandard => Name == NetStandard; + /// + /// Parses the framework name as provided in . + /// + /// The full runtime name. + /// The parsed version info. + public static DotNetRuntimeInfo Parse(string frameworkName) + { + return TryParse(frameworkName, out var info) ? info : throw new FormatException(); + } + /// /// Attempts to parse the framework name as provided in . /// @@ -89,6 +99,13 @@ public static bool TryParse(string frameworkName, out DotNetRuntimeInfo info) return true; } + /// + /// Obtains a reference to the default core lib reference of this runtime. + /// + /// The reference to the default core lib. + /// The runtime information is invalid or unsupported. + public AssemblyReference GetDefaultCorLib() => KnownCorLibs.FromRuntimeInfo(this); + /// public override string ToString() => $"{Name},Version=v{Version}"; } diff --git a/src/AsmResolver.DotNet/KnownCorLibs.cs b/src/AsmResolver.DotNet/KnownCorLibs.cs index fbc0d3893..8cbf51825 100644 --- a/src/AsmResolver.DotNet/KnownCorLibs.cs +++ b/src/AsmResolver.DotNet/KnownCorLibs.cs @@ -24,7 +24,7 @@ public static class KnownCorLibs /// References mscorlib.dll, Version=2.0.0.0, PublicKeyToken=B77A5C561934E089. This is used by .NET assemblies /// targeting the .NET Framework 2.0, 3.0 and 3.5. /// - public static readonly AssemblyReference MsCorLib_v2_0_0_0 = new AssemblyReference("mscorlib", + public static readonly AssemblyReference MsCorLib_v2_0_0_0 = new("mscorlib", new Version(2, 0, 0, 0), false, new byte[] { 0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89 @@ -34,7 +34,7 @@ public static class KnownCorLibs /// References mscorlib.dll, Version=4.0.0.0, PublicKeyToken=B77A5C561934E089. This is used by .NET assemblies /// targeting the .NET Framework 4.0 and later. /// - public static readonly AssemblyReference MsCorLib_v4_0_0_0 = new AssemblyReference("mscorlib", + public static readonly AssemblyReference MsCorLib_v4_0_0_0 = new("mscorlib", new Version(4, 0, 0, 0), false, new byte[] { 0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89 @@ -44,7 +44,7 @@ public static class KnownCorLibs /// References System.Private.CoreLib.dll, Version=4.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET /// assemblies targeting .NET Core 1.0 and later. /// - public static readonly AssemblyReference SystemPrivateCoreLib_v4_0_0_0 = new AssemblyReference("System.Private.CoreLib", + public static readonly AssemblyReference SystemPrivateCoreLib_v4_0_0_0 = new("System.Private.CoreLib", new Version(4, 0, 0, 0), false, new byte[] { 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E @@ -54,7 +54,7 @@ public static class KnownCorLibs /// References System.Private.CoreLib.dll, Version=5.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET /// assemblies targeting .NET 5.0. /// - public static readonly AssemblyReference SystemPrivateCoreLib_v5_0_0_0 = new AssemblyReference("System.Private.CoreLib", + public static readonly AssemblyReference SystemPrivateCoreLib_v5_0_0_0 = new("System.Private.CoreLib", new Version(5, 0, 0, 0), false, new byte[] { 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E @@ -64,7 +64,7 @@ public static class KnownCorLibs /// References System.Private.CoreLib.dll, Version=6.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET /// assemblies targeting .NET 6.0. /// - public static readonly AssemblyReference SystemPrivateCoreLib_v6_0_0_0 = new AssemblyReference("System.Private.CoreLib", + public static readonly AssemblyReference SystemPrivateCoreLib_v6_0_0_0 = new("System.Private.CoreLib", new Version(6, 0, 0, 0), false, new byte[] { 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E @@ -74,37 +74,77 @@ public static class KnownCorLibs /// References System.Private.CoreLib.dll, Version=7.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET /// assemblies targeting .NET 7.0. /// - public static readonly AssemblyReference SystemPrivateCoreLib_v7_0_0_0 = new AssemblyReference("System.Private.CoreLib", + public static readonly AssemblyReference SystemPrivateCoreLib_v7_0_0_0 = new("System.Private.CoreLib", new Version(7, 0, 0, 0), false, new byte[] { 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E }); + /// + /// References System.Private.CoreLib.dll, Version=8.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET + /// assemblies targeting .NET 8.0. + /// + public static readonly AssemblyReference SystemPrivateCoreLib_v8_0_0_0 = new("System.Private.CoreLib", + new Version(8, 0, 0, 0), false, new byte[] + { + 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E + }); + + /// + /// References System.Runtime.dll, Version=4.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET standard 1.0 and 1.1. + /// + public static readonly AssemblyReference SystemRuntime_v4_0_0_0 = new("System.Runtime", + new Version(4, 0, 0, 0), false, new byte[] + { + 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A + }); + + /// + /// References System.Runtime.dll, Version=4.0.10.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET standard 1.2. + /// + public static readonly AssemblyReference SystemRuntime_v4_0_10_0 = new("System.Runtime", + new Version(4, 0, 10, 0), false, new byte[] + { + 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A + }); + /// /// References System.Runtime.dll, Version=4.0.20.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET standard 1.3 and 1.4. /// - public static readonly AssemblyReference SystemRuntime_v4_0_20_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v4_0_20_0 = new("System.Runtime", new Version(4, 0, 20, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A }); /// - /// References System.Runtime.dll, Version=4.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET - /// assemblies targeting .NET standard 1.5, 1.6 and 1.7. + /// References System.Runtime.dll, Version=4.1.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET standard 1.5, 1.6 and 1.7, and .NET Core 1.0 and 1.1. /// - public static readonly AssemblyReference SystemRuntime_v4_1_0_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v4_1_0_0 = new("System.Runtime", new Version(4, 1, 0, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A }); + /// + /// References System.Runtime.dll, Version=4.2.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET Core 2.0. + /// + public static readonly AssemblyReference SystemRuntime_v4_2_0_0 = new("System.Runtime", + new Version(4, 2, 0, 0), false, new byte[] + { + 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A + }); + /// /// References System.Runtime.dll, Version=4.2.1.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET - /// assemblies targeting .NET Core 2.1. + /// assemblies targeting .NET Core 2.1 and 3.0. /// - public static readonly AssemblyReference SystemRuntime_v4_2_1_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v4_2_1_0 = new("System.Runtime", new Version(4, 2, 1, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -114,7 +154,7 @@ public static class KnownCorLibs /// References System.Runtime.dll, Version=4.2.2.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET Core 3.1. /// - public static readonly AssemblyReference SystemRuntime_v4_2_2_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v4_2_2_0 = new("System.Runtime", new Version(4, 2, 2, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -124,7 +164,7 @@ public static class KnownCorLibs /// References System.Runtime.dll, Version=5.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET 5.0. /// - public static readonly AssemblyReference SystemRuntime_v5_0_0_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v5_0_0_0 = new("System.Runtime", new Version(5, 0, 0, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -134,7 +174,7 @@ public static class KnownCorLibs /// References System.Runtime.dll, Version=6.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET 6.0. /// - public static readonly AssemblyReference SystemRuntime_v6_0_0_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v6_0_0_0 = new("System.Runtime", new Version(6, 0, 0, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A @@ -144,17 +184,27 @@ public static class KnownCorLibs /// References System.Runtime.dll, Version=7.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET /// assemblies targeting .NET 7.0. /// - public static readonly AssemblyReference SystemRuntime_v7_0_0_0 = new AssemblyReference("System.Runtime", + public static readonly AssemblyReference SystemRuntime_v7_0_0_0 = new("System.Runtime", new Version(7, 0, 0, 0), false, new byte[] { 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A }); + /// + /// References System.Runtime.dll, Version=8.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET + /// assemblies targeting .NET 8.0. + /// + public static readonly AssemblyReference SystemRuntime_v8_0_0_0 = new("System.Runtime", + new Version(8, 0, 0, 0), false, new byte[] + { + 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A + }); + /// /// References netstandard.dll, Version=2.0.0.0, PublicKeyToken=CC7B13FFCD2DDD51. This is used by .NET /// assemblies targeting .NET standard 2.0. /// - public static readonly AssemblyReference NetStandard_v2_0_0_0 = new AssemblyReference("netstandard", + public static readonly AssemblyReference NetStandard_v2_0_0_0 = new("netstandard", new Version(2, 0, 0, 0), false, new byte[] { 0xCC, 0x7B, 0x13, 0xFF, 0xCD, 0x2D, 0xDD, 0x51 @@ -164,7 +214,7 @@ public static class KnownCorLibs /// References netstandard.dll, Version=2.1.0.0, PublicKeyToken=CC7B13FFCD2DDD51. This is used by .NET /// assemblies targeting .NET standard 2.1. /// - public static readonly AssemblyReference NetStandard_v2_1_0_0 = new AssemblyReference("netstandard", + public static readonly AssemblyReference NetStandard_v2_1_0_0 = new("netstandard", new Version(2, 1, 0, 0), false, new byte[] { 0xCC, 0x7B, 0x13, 0xFF, 0xCD, 0x2D, 0xDD, 0x51 @@ -178,20 +228,80 @@ static KnownCorLibs() NetStandard_v2_1_0_0, MsCorLib_v2_0_0_0, MsCorLib_v4_0_0_0, + SystemRuntime_v4_0_0_0, + SystemRuntime_v4_0_10_0, SystemRuntime_v4_0_20_0, SystemRuntime_v4_1_0_0, + SystemRuntime_v4_2_0_0, SystemRuntime_v4_2_1_0, SystemRuntime_v4_2_2_0, SystemRuntime_v5_0_0_0, SystemRuntime_v6_0_0_0, SystemRuntime_v7_0_0_0, + SystemRuntime_v8_0_0_0, SystemPrivateCoreLib_v4_0_0_0, SystemPrivateCoreLib_v5_0_0_0, SystemPrivateCoreLib_v6_0_0_0, - SystemPrivateCoreLib_v7_0_0_0 + SystemPrivateCoreLib_v7_0_0_0, + SystemPrivateCoreLib_v8_0_0_0, }; KnownCorLibNames = new HashSet(KnownCorLibReferences.Select(r => r.Name!.Value)); } + + /// + /// Obtains a reference to the default core lib reference for the provided .NET target runtime. + /// + /// The runtime to target. + /// The reference to the default core lib. + /// The runtime information is invalid or unsupported. + public static AssemblyReference FromRuntimeInfo(DotNetRuntimeInfo runtimeInfo) + { + if (runtimeInfo.IsNetFramework) + return SelectFrameworkCorLib(runtimeInfo.Version); + + if (runtimeInfo.IsNetStandard) + return SelectNetStandardCorLib(runtimeInfo.Version); + + if (runtimeInfo.IsNetCoreApp) + return SelectNetCoreCorLib(runtimeInfo.Version); + + throw new ArgumentException($"Invalid or unsupported runtime version {runtimeInfo}."); + } + + private static AssemblyReference SelectFrameworkCorLib(Version version) => version.Major < 4 + ? MsCorLib_v2_0_0_0 + : MsCorLib_v4_0_0_0; + + private static AssemblyReference SelectNetStandardCorLib(Version version) + { + return (version.Major, version.Minor) switch + { + (1, 0 or 1) => SystemRuntime_v4_0_0_0, + (1, 2) => SystemRuntime_v4_0_10_0, + (1, 3 or 4) => SystemRuntime_v4_0_20_0, + (1, 5 or 6 or 7) => SystemRuntime_v4_1_0_0, + (2, 0) => NetStandard_v2_0_0_0, + (2, 1) => NetStandard_v2_1_0_0, + _ => throw new ArgumentException($"Invalid or unsupported .NET standard version {version}.") + }; + } + + private static AssemblyReference SelectNetCoreCorLib(Version version) + { + return (version.Major, version.Minor) switch + { + (1, 0 or 1) => SystemRuntime_v4_1_0_0, + (2, 0) => SystemRuntime_v4_2_0_0, + (2, 1) or (3, 0) => SystemRuntime_v4_2_1_0, + (3, 1) => SystemRuntime_v4_2_2_0, + (5, 0) => SystemRuntime_v5_0_0_0, + (6, 0) => SystemRuntime_v6_0_0_0, + (7, 0) => SystemRuntime_v7_0_0_0, + (8, 0) => SystemRuntime_v8_0_0_0, + _ => throw new ArgumentException($"Invalid or unsupported .NET or .NET Core version {version}.") + }; + } + } } diff --git a/src/AsmResolver.DotNet/Memory/TypeAlignmentDetector.cs b/src/AsmResolver.DotNet/Memory/TypeAlignmentDetector.cs index 1d876e020..4accf40db 100644 --- a/src/AsmResolver.DotNet/Memory/TypeAlignmentDetector.cs +++ b/src/AsmResolver.DotNet/Memory/TypeAlignmentDetector.cs @@ -51,8 +51,8 @@ public uint VisitCorLibType(CorLibTypeSignature signature) }; } - public uint VisitCustomModifierType(CustomModifierTypeSignature signature) => - signature.BaseType.AcceptVisitor(this); + public uint VisitCustomModifierType(CustomModifierTypeSignature signature) + => signature.BaseType.AcceptVisitor(this); public uint VisitGenericInstanceType(GenericInstanceTypeSignature signature) { @@ -67,15 +67,24 @@ public uint VisitGenericInstanceType(GenericInstanceTypeSignature signature) return result; } - public uint VisitGenericParameter(GenericParameterSignature signature) => - _currentGenericContext.GetTypeArgument(signature).AcceptVisitor(this); + public uint VisitGenericParameter(GenericParameterSignature signature) + { + var resolved = _currentGenericContext.GetTypeArgument(signature); + + // GenericContext::GetTypeArgument returns the same object if it could not be resolved to a concrete + // type argument. This means the generic type was not instantiated yet, or the type argument is invalid. + if (ReferenceEquals(resolved, signature)) + throw new ArgumentException($"The type parameter {signature} could not be resolved to a concrete type argument in the current context."); + + return resolved.AcceptVisitor(this); + } public uint VisitPinnedType(PinnedTypeSignature signature) => signature.BaseType.AcceptVisitor(this); public uint VisitPointerType(PointerTypeSignature signature) => PointerSize; - public uint VisitSentinelType(SentinelTypeSignature signature) => - throw new ArgumentException("Sentinel types do not have a size."); + public uint VisitSentinelType(SentinelTypeSignature signature) + => throw new ArgumentException("Sentinel types do not have a size."); public uint VisitSzArrayType(SzArrayTypeSignature signature) => PointerSize; @@ -94,8 +103,8 @@ public uint VisitTypeDefOrRef(ITypeDefOrRef type) }; } - private uint VisitTypeReference(TypeReference type) => - VisitTypeDefinition(type.Resolve() ?? throw new ArgumentException($"Could not resolve {type.SafeToString()}.")); + private uint VisitTypeReference(TypeReference type) + => VisitTypeDefinition(type.Resolve() ?? throw new ArgumentException($"Could not resolve {type.SafeToString()}.")); public uint VisitTypeDefinition(TypeDefinition type) { diff --git a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs index 2ae1e422c..3dae49f64 100644 --- a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs +++ b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs @@ -112,32 +112,41 @@ public TypeMemoryLayout VisitGenericInstanceType(GenericInstanceTypeSignature si } /// - public TypeMemoryLayout VisitGenericParameter(GenericParameterSignature signature) => - _currentGenericContext.GetTypeArgument(signature).AcceptVisitor(this); + public TypeMemoryLayout VisitGenericParameter(GenericParameterSignature signature) + { + var resolved = _currentGenericContext.GetTypeArgument(signature); + + // GenericContext::GetTypeArgument returns the same object if it could not be resolved to a concrete + // type argument. This means the generic type was not instantiated yet, or the type argument is invalid. + if (ReferenceEquals(resolved, signature)) + throw new ArgumentException($"The type parameter {signature} could not be resolved to a concrete type argument in the current context."); + + return resolved.AcceptVisitor(this); + } /// - public TypeMemoryLayout VisitPinnedType(PinnedTypeSignature signature) => - CreatePointerLayout(signature); + public TypeMemoryLayout VisitPinnedType(PinnedTypeSignature signature) + => CreatePointerLayout(signature); /// - public TypeMemoryLayout VisitPointerType(PointerTypeSignature signature) => - CreatePointerLayout(signature); + public TypeMemoryLayout VisitPointerType(PointerTypeSignature signature) + => CreatePointerLayout(signature); /// - public TypeMemoryLayout VisitSentinelType(SentinelTypeSignature signature) => - throw new ArgumentException("Sentinel types do not have a size."); + public TypeMemoryLayout VisitSentinelType(SentinelTypeSignature signature) + => throw new ArgumentException("Sentinel types do not have a size."); /// - public TypeMemoryLayout VisitSzArrayType(SzArrayTypeSignature signature) => - CreatePointerLayout(signature); + public TypeMemoryLayout VisitSzArrayType(SzArrayTypeSignature signature) + => CreatePointerLayout(signature); /// - public TypeMemoryLayout VisitTypeDefOrRef(TypeDefOrRefSignature signature) => - VisitTypeDefOrRef(signature.Type); + public TypeMemoryLayout VisitTypeDefOrRef(TypeDefOrRefSignature signature) + => VisitTypeDefOrRef(signature.Type); /// - public TypeMemoryLayout VisitFunctionPointerType(FunctionPointerTypeSignature signature) => - CreatePointerLayout(signature); + public TypeMemoryLayout VisitFunctionPointerType(FunctionPointerTypeSignature signature) + => CreatePointerLayout(signature); /// /// Visits an instance of a class. @@ -150,10 +159,16 @@ public TypeMemoryLayout VisitTypeDefOrRef(ITypeDefOrRef type) { TableIndex.TypeRef => VisitTypeReference((TypeReference) type), TableIndex.TypeDef => VisitTypeDefinition((TypeDefinition) type), + TableIndex.TypeSpec => VisitTypeSpecification((TypeSpecification) type), _ => throw new ArgumentException("Invalid type.") }; } + private TypeMemoryLayout VisitTypeSpecification(TypeSpecification type) + { + return type.Signature!.AcceptVisitor(this); + } + private TypeMemoryLayout VisitTypeReference(TypeReference type) => VisitTypeDefinition(type.Resolve() ?? throw new ArgumentException( $"Could not resolve type {type.SafeToString()}.")); diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs index 439a28e43..e539a0197 100644 --- a/src/AsmResolver.DotNet/ModuleDefinition.cs +++ b/src/AsmResolver.DotNet/ModuleDefinition.cs @@ -74,7 +74,7 @@ public static ModuleDefinition FromBytes(byte[] buffer) => /// The module. /// Occurs when the image does not contain a valid .NET metadata directory. public static ModuleDefinition FromBytes(byte[] buffer, ModuleReaderParameters readerParameters) => - FromImage(PEImage.FromBytes(buffer, readerParameters.PEReaderParameters)); + FromImage(PEImage.FromBytes(buffer, readerParameters.PEReaderParameters), readerParameters); /// /// Reads a .NET module from the provided input file. @@ -1195,14 +1195,17 @@ protected IAssemblyResolver CreateAssemblyResolver(IFileService fileService) when string.IsNullOrEmpty(DotNetCorePathProvider.DefaultInstallationPath): resolver = new DotNetFrameworkAssemblyResolver(fileService); break; + case DotNetRuntimeInfo.NetStandard when DotNetCorePathProvider.Default.TryGetLatestStandardCompatibleVersion( runtime.Version, out var coreVersion): resolver = new DotNetCoreAssemblyResolver(fileService, coreVersion); break; + case DotNetRuntimeInfo.NetCoreApp: resolver = new DotNetCoreAssemblyResolver(fileService, runtime.Version); break; + default: resolver = new DotNetFrameworkAssemblyResolver(fileService); break; diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs index 1f0821c10..c9c2b26c7 100644 --- a/src/AsmResolver.DotNet/ReferenceImporter.cs +++ b/src/AsmResolver.DotNet/ReferenceImporter.cs @@ -30,20 +30,12 @@ public ModuleDefinition TargetModule get; } - private static void AssertTypeIsValid(ITypeDefOrRef? type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - if (type.Scope is null) - throw new ArgumentException("Cannot import types that are not added to a module."); - } - /// /// Imports a resolution scope. /// /// The resolution scope to import. /// The imported resolution scope. - public IResolutionScope ImportScope(IResolutionScope? scope) + public IResolutionScope ImportScope(IResolutionScope scope) { if (scope is null) throw new ArgumentNullException(nameof(scope)); @@ -181,14 +173,16 @@ public ITypeDefOrRef ImportType(ITypeDefOrRef type) /// The imported type. protected virtual ITypeDefOrRef ImportType(TypeDefinition type) { - AssertTypeIsValid(type); - + if (type is null) + throw new ArgumentNullException(nameof(type)); if (type.IsImportedInModule(TargetModule)) return type; + if (((ITypeDescriptor) type).Scope is not { } scope) + throw new ArgumentException("Cannot import a type that has not been added to a module."); return new TypeReference( TargetModule, - ImportScope(((ITypeDescriptor) type).Scope), + ImportScope(scope), type.Namespace, type.Name); } @@ -200,12 +194,18 @@ protected virtual ITypeDefOrRef ImportType(TypeDefinition type) /// The imported type. protected virtual ITypeDefOrRef ImportType(TypeReference type) { - AssertTypeIsValid(type); - + if (type is null) + throw new ArgumentNullException(nameof(type)); if (type.IsImportedInModule(TargetModule)) return type; - return new TypeReference(TargetModule, ImportScope(type.Scope!), type.Namespace, type.Name); + return new TypeReference( + TargetModule, + type.Scope is not null + ? ImportScope(type.Scope) + : null, + type.Namespace, + type.Name); } /// @@ -215,7 +215,8 @@ protected virtual ITypeDefOrRef ImportType(TypeReference type) /// The imported type. protected virtual ITypeDefOrRef ImportType(TypeSpecification type) { - AssertTypeIsValid(type); + if (type is null) + throw new ArgumentNullException(nameof(type)); if (type.Signature is null) throw new ArgumentNullException(nameof(type)); diff --git a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs index 082c7f483..e339755a0 100644 --- a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs +++ b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs @@ -10,9 +10,21 @@ namespace AsmResolver.DotNet /// public class ReflectionAssemblyDescriptor : AssemblyDescriptor { - private readonly ModuleDefinition _parentModule; + private readonly ModuleDefinition? _parentModule; private readonly AssemblyName _assemblyName; + /// + /// Creates a new instance of the class. + /// + /// The assembly name to import. + public ReflectionAssemblyDescriptor(AssemblyName assemblyName) + : base(new MetadataToken(TableIndex.AssemblyRef, 0)) + { + _parentModule = null; + _assemblyName = assemblyName; + Version = assemblyName.Version ?? new Version(); + } + /// /// Creates a new instance of the class. /// @@ -46,6 +58,6 @@ public override AssemblyReference ImportWith(ReferenceImporter importer) => public override byte[]? GetPublicKeyToken() => _assemblyName.GetPublicKeyToken(); /// - public override AssemblyDefinition? Resolve() => _parentModule.MetadataResolver.AssemblyResolver.Resolve(this); + public override AssemblyDefinition? Resolve() => _parentModule?.MetadataResolver.AssemblyResolver.Resolve(this); } } diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs index 488090231..464553d67 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs @@ -71,10 +71,18 @@ public SerializedModuleDefinition(IPEImage peImage, ModuleReaderParameters reade // Find assembly definition and corlib assembly. Assembly = FindParentAssembly(); CorLibTypeFactory = CreateCorLibTypeFactory(); - OriginalTargetRuntime = DetectTargetRuntime(); - MetadataResolver = new DefaultMetadataResolver(CreateAssemblyResolver( - readerParameters.PEReaderParameters.FileService)); + + // Initialize metadata resolution engines. + var resolver = CreateAssemblyResolver(readerParameters.PEReaderParameters.FileService); + if (!string.IsNullOrEmpty(readerParameters.WorkingDirectory) + && resolver is AssemblyResolverBase resolverBase + && !resolverBase.SearchDirectories.Contains(readerParameters.WorkingDirectory!)) + { + resolverBase.SearchDirectories.Add(readerParameters.WorkingDirectory!); + } + + MetadataResolver = new DefaultMetadataResolver(resolver); // Prepare lazy RID lists. _fieldLists = new LazyRidListRelation(metadata, TableIndex.Field, TableIndex.TypeDef, diff --git a/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs b/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs index 4e0cb839b..75bf2122a 100644 --- a/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs +++ b/src/AsmResolver.DotNet/Serialized/SerializedTypeReference.cs @@ -41,7 +41,7 @@ public SerializedTypeReference(ModuleReaderContext context, MetadataToken token, protected override IResolutionScope? GetScope() { if (_row.ResolutionScope == 0) - return _context.ParentModule; + return null; var tablesStream = _context.TablesStream; var decoder = tablesStream.GetIndexEncoder(CodedIndex.ResolutionScope); diff --git a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj index 98b14d222..b89b4cfe0 100644 --- a/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj +++ b/src/AsmResolver.PE.File/AsmResolver.PE.File.csproj @@ -25,6 +25,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE.File/Headers/SectionHeader.cs b/src/AsmResolver.PE.File/Headers/SectionHeader.cs index f2a72e7d0..a5de4f389 100644 --- a/src/AsmResolver.PE.File/Headers/SectionHeader.cs +++ b/src/AsmResolver.PE.File/Headers/SectionHeader.cs @@ -19,35 +19,6 @@ public class SectionHeader : SegmentBase, IOffsetConverter private string _name; - /// - /// Reads a single section header at the current position of the provided input stream. - /// - /// The input stream to read from. - /// The section header that was read. - public static SectionHeader FromReader(ref BinaryStreamReader reader) - { - ulong offset = reader.Offset; - uint rva = reader.Rva; - - var nameBytes = new byte[8]; - reader.ReadBytes(nameBytes, 0, nameBytes.Length); - - return new SectionHeader(Encoding.UTF8.GetString(nameBytes).Replace("\0", ""), 0) - { - Offset = offset, - Rva = rva, - VirtualSize = reader.ReadUInt32(), - VirtualAddress = reader.ReadUInt32(), - SizeOfRawData = reader.ReadUInt32(), - PointerToRawData = reader.ReadUInt32(), - PointerToRelocations = reader.ReadUInt32(), - PointerToLineNumbers = reader.ReadUInt32(), - NumberOfRelocations = reader.ReadUInt16(), - NumberOfLineNumbers = reader.ReadUInt16(), - Characteristics = (SectionFlags) reader.ReadUInt32() - }; - } - /// /// Creates a new section header with the provided name. /// @@ -194,6 +165,46 @@ public SectionFlags Characteristics set; } + /// + /// Reads a single section header at the current position of the provided input stream. + /// + /// The input stream to read from. + /// The section header that was read. + public static SectionHeader FromReader(ref BinaryStreamReader reader) + { + ulong offset = reader.Offset; + uint rva = reader.Rva; + + // Read name field. + byte[] nameBytes = new byte[8]; + reader.ReadBytes(nameBytes, 0, nameBytes.Length); + + // Interpret as UTF-8, discarding all invalid UTF-8 characters. + string name = Encoding.UTF8.GetString(nameBytes).Replace("\xfffd", ""); + + // Trim to last null-byte if it exists. + int index = name.IndexOf('\0'); + if (index >= 0) + name = name.Remove(index); + + return new SectionHeader(name, 0) + { + Offset = offset, + Rva = rva, + + // Read remainder of section header. + VirtualSize = reader.ReadUInt32(), + VirtualAddress = reader.ReadUInt32(), + SizeOfRawData = reader.ReadUInt32(), + PointerToRawData = reader.ReadUInt32(), + PointerToRelocations = reader.ReadUInt32(), + PointerToLineNumbers = reader.ReadUInt32(), + NumberOfRelocations = reader.ReadUInt16(), + NumberOfLineNumbers = reader.ReadUInt16(), + Characteristics = (SectionFlags) reader.ReadUInt32() + }; + } + /// public override uint GetPhysicalSize() => SectionHeaderSize; diff --git a/src/AsmResolver.PE.File/PEFile.cs b/src/AsmResolver.PE.File/PEFile.cs index 09d15695b..f41e15716 100644 --- a/src/AsmResolver.PE.File/PEFile.cs +++ b/src/AsmResolver.PE.File/PEFile.cs @@ -282,8 +282,9 @@ public bool TryCreateDataDirectoryReader(DataDirectory dataDirectory, out Binary /// public BinaryStreamReader CreateReaderAtFileOffset(uint fileOffset) { - var section = GetSectionContainingOffset(fileOffset); - return section.CreateReader(fileOffset); + return !TryCreateReaderAtFileOffset(fileOffset, out var reader) + ? throw new ArgumentOutOfRangeException(nameof(fileOffset)) + : reader; } /// @@ -295,6 +296,14 @@ public bool TryCreateReaderAtFileOffset(uint fileOffset, out BinaryStreamReader return true; } + if (EofData is IReadableSegment eofData + && fileOffset >= eofData.Offset + && fileOffset < eofData.Offset + eofData.GetPhysicalSize()) + { + reader = eofData.CreateReader(fileOffset); + return true; + } + reader = default; return false; } @@ -315,6 +324,14 @@ public bool TryCreateReaderAtFileOffset(uint fileOffset, uint size, out BinarySt return true; } + if (EofData is IReadableSegment eofData + && fileOffset >= eofData.Offset + && fileOffset < eofData.Offset + eofData.GetPhysicalSize()) + { + reader = eofData.CreateReader(fileOffset, size); + return true; + } + reader = default; return false; } @@ -389,33 +406,41 @@ public bool TryCreateReaderAtRva(uint rva, uint size, out BinaryStreamReader rea /// public void UpdateHeaders() { - var oldSections = Sections.Select(x => x.CreateHeader()).ToList(); - - FileHeader.NumberOfSections = (ushort) Sections.Count; - var relocation = new RelocationParameters(OptionalHeader.ImageBase, 0, 0, OptionalHeader.Magic == OptionalHeaderMagic.PE32); - FileHeader.UpdateOffsets(relocation.WithOffsetRva( - DosHeader.NextHeaderOffset + 4, - DosHeader.NextHeaderOffset + 4)); - OptionalHeader.UpdateOffsets(relocation.WithOffsetRva( - FileHeader.Offset + FileHeader.GetPhysicalSize(), - FileHeader.Rva + FileHeader.GetVirtualSize())); + // Update offsets of PE headers. + FileHeader.UpdateOffsets( + relocation.WithAdvance(DosHeader.NextHeaderOffset + sizeof(uint)) + ); + OptionalHeader.UpdateOffsets( + relocation.WithAdvance((uint) FileHeader.Offset + FileHeader.GetPhysicalSize()) + ); + // Sync file header fields with actual observed values. + FileHeader.NumberOfSections = (ushort) Sections.Count; FileHeader.SizeOfOptionalHeader = (ushort) OptionalHeader.GetPhysicalSize(); - OptionalHeader.SizeOfHeaders = (uint) (OptionalHeader.Offset - + FileHeader.SizeOfOptionalHeader - + SectionHeader.SectionHeaderSize * (uint) Sections.Count) - .Align(OptionalHeader.FileAlignment); + // Compute headers size, and update offsets of extra data. + uint peHeadersSize = (uint) OptionalHeader.Offset + + FileHeader.SizeOfOptionalHeader + + SectionHeader.SectionHeaderSize * (uint) Sections.Count; + ExtraSectionData?.UpdateOffsets(relocation.WithAdvance(peHeadersSize)); + + uint totalHeadersSize = peHeadersSize + (ExtraSectionData?.GetPhysicalSize() ?? 0); + OptionalHeader.SizeOfHeaders = totalHeadersSize.Align(OptionalHeader.FileAlignment); + + // Re-align sections and directories. + var oldSections = Sections.Select(x => x.CreateHeader()).ToList(); AlignSections(); - AlignDataDirectoryEntries(oldSections); + AlignDataDirectoryEntries(oldSections);; + // Determine full size of image. var lastSection = Sections[Sections.Count - 1]; OptionalHeader.SizeOfImage = lastSection.Rva - + lastSection.GetVirtualSize().Align(OptionalHeader.SectionAlignment); + + lastSection.GetVirtualSize().Align(OptionalHeader.SectionAlignment); + // Update EOF data offsets. EofData?.UpdateOffsets(relocation.WithOffsetRva( lastSection.Offset + lastSection.GetPhysicalSize(), OptionalHeader.SizeOfImage)); diff --git a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj index 219f54c0d..c32f0f81b 100644 --- a/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj +++ b/src/AsmResolver.PE.Win32Resources/AsmResolver.PE.Win32Resources.csproj @@ -18,6 +18,13 @@ bin\Release\AsmResolver.PE.Win32Resources.xml + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs b/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs index 9904b1a1a..0a97b0fe2 100644 --- a/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs +++ b/src/AsmResolver.PE.Win32Resources/Icon/IconResource.cs @@ -98,7 +98,7 @@ public void WriteToDirectory(IResourceDirectory rootDirectory) foreach (var (groupEntry, iconEntry) in entry.Value.GetIconEntries()) { newIconDirectory.Entries.Add(new ResourceDirectory(groupEntry.Id) - {Entries = {new ResourceData(0u, iconEntry)}}); + {Entries = {new ResourceData(1033, iconEntry)}}); } } diff --git a/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs b/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs index 3d88a0b1a..0db9a0e10 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/StringTable.cs @@ -73,6 +73,53 @@ public class StringTable : VersionTableEntry, IEnumerable public const string SpecialBuildKey = "SpecialBuild"; + private readonly Dictionary _entries = new(); + + /// + /// Creates a new string table. + /// + /// The language identifier. + /// The code page. + public StringTable(ushort languageIdentifier, ushort codePage) + { + LanguageIdentifier = languageIdentifier; + CodePage = codePage; + } + + /// + public override string Key => $"{LanguageIdentifier:x4}{CodePage:x4}"; + + /// + /// Gets or sets the language identifier of this string table. + /// + public ushort LanguageIdentifier + { + get; + set; + } + + /// + /// Gets or sets the code page of this string table. + /// + public ushort CodePage + { + get; + set; + } + + /// + protected override VersionTableValueType ValueType => VersionTableValueType.Binary; + + /// + /// Gets or sets the value of a single field in the string table. + /// + /// The name of the field in the string table. + public string this[string key] + { + get => _entries[key]; + set => _entries[key] = value; + } + /// /// Reads a single StringTable structure from the provided input stream. /// @@ -122,53 +169,6 @@ private static KeyValuePair ReadEntry(ref BinaryStreamReader rea return new KeyValuePair(header.Key, value); } - private readonly Dictionary _entries = new Dictionary(); - - /// - /// Creates a new string table. - /// - /// The language identifier. - /// The code page. - public StringTable(ushort languageIdentifier, ushort codePage) - { - LanguageIdentifier = languageIdentifier; - CodePage = codePage; - } - - /// - public override string Key => $"{LanguageIdentifier:X4}{CodePage:X4}"; - - /// - /// Gets or sets the language identifier of this string table. - /// - public ushort LanguageIdentifier - { - get; - set; - } - - /// - /// Gets or sets the code page of this string table. - /// - public ushort CodePage - { - get; - set; - } - - /// - protected override VersionTableValueType ValueType => VersionTableValueType.Binary; - - /// - /// Gets or sets the value of a single field in the string table. - /// - /// The name of the field in the string table. - public string this[string key] - { - get => _entries[key]; - set => _entries[key] = value; - } - /// /// Adds (or overrides) a field to the string table. /// diff --git a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs index 666591d7c..24a2d32c1 100644 --- a/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs +++ b/src/AsmResolver.PE.Win32Resources/Version/VersionInfoResource.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using AsmResolver.IO; @@ -15,12 +16,99 @@ public class VersionInfoResource : VersionTableEntry, IWin32Resource /// public const string VsVersionInfoKey = "VS_VERSION_INFO"; + private FixedVersionInfo _fixedVersionInfo = new(); + private readonly Dictionary _entries = new(); + /// - /// Obtains the version info resource from the provided root win32 resources directory. + /// Creates a new empty version info resource targeting the English (United States) language identifier. + /// + public VersionInfoResource() + : this(0) + { + } + + /// + /// Creates a new empty version info resource. + /// + /// The language identifier the resource version info is targeting. + public VersionInfoResource(int lcid) + { + Lcid = lcid; + } + + /// + /// Gets the language identifier the resource version info is targeting. + /// + public int Lcid + { + get; + } + + /// + public override string Key => VsVersionInfoKey; + + /// + protected override VersionTableValueType ValueType => VersionTableValueType.Binary; + + /// + /// Gets the fixed version info stored in this version resource. + /// + public FixedVersionInfo FixedVersionInfo + { + get => _fixedVersionInfo; + set => _fixedVersionInfo = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// Gets or sets a version table entry by its name. + /// + /// The name of the child. + public VersionTableEntry this[string name] + { + get => _entries[name]; + set => _entries[name] = value; + } + + /// + /// Obtains all version info resources from the provided root win32 resources directory. + /// + /// The root resources directory to extract the version info from. + /// The version info resource, or null if none was found. + public static IEnumerable FindAllFromDirectory(IResourceDirectory rootDirectory) + { + if (!rootDirectory.TryGetDirectory(ResourceType.Version, out var versionDirectory)) + return Enumerable.Empty(); + + var categoryDirectory = versionDirectory + .Entries + .OfType() + .FirstOrDefault(); + + if (categoryDirectory is null) + return Enumerable.Empty(); + + return categoryDirectory.Entries + .OfType() + .Select(FromResourceData); + } + + /// + /// Obtains the first version info resource from the provided root win32 resources directory. /// /// The root resources directory to extract the version info from. /// The version info resource, or null if none was found. public static VersionInfoResource? FromDirectory(IResourceDirectory rootDirectory) + { + return FindAllFromDirectory(rootDirectory).FirstOrDefault(); + } + + /// + /// Obtains the version info resource from the provided root win32 resources directory. + /// + /// The root resources directory to extract the version info from. + /// The language identifier to get the version info from. + /// The version info resource, or null if none was found. + public static VersionInfoResource? FromDirectory(IResourceDirectory rootDirectory, int lcid) { if (!rootDirectory.TryGetDirectory(ResourceType.Version, out var versionDirectory)) return null; @@ -33,19 +121,30 @@ public class VersionInfoResource : VersionTableEntry, IWin32Resource var dataEntry = categoryDirectory ?.Entries .OfType() - .FirstOrDefault(); + .FirstOrDefault(x => x.Id == lcid); if (dataEntry is null) return null; + return FromResourceData(dataEntry); + } + + /// + /// Obtains the version info resource from the provided resource data entry. + /// + /// The data entry to extract the version info from. + /// The extracted version info resource. + public static VersionInfoResource FromResourceData(IResourceData dataEntry) + { if (dataEntry.CanRead) { var dataReader = dataEntry.CreateReader(); - return FromReader(ref dataReader); + return FromReader((int) dataEntry.Id, ref dataReader); } if (dataEntry.Contents is VersionInfoResource resource) return resource; + throw new ArgumentException("Version resource data is not readable."); } @@ -57,7 +156,18 @@ public class VersionInfoResource : VersionTableEntry, IWin32Resource /// /// Occurs when the input stream does not point to a valid version resource. /// - public static VersionInfoResource FromReader(ref BinaryStreamReader reader) + public static VersionInfoResource FromReader(ref BinaryStreamReader reader) => FromReader(0, ref reader); + + /// + /// Reads a version resource from an input stream. + /// + /// The language identifier to get the version info from. + /// The input stream. + /// The parsed version resource. + /// + /// Occurs when the input stream does not point to a valid version resource. + /// + public static VersionInfoResource FromReader(int lcid, ref BinaryStreamReader reader) { ulong start = reader.Offset; @@ -66,7 +176,7 @@ public static VersionInfoResource FromReader(ref BinaryStreamReader reader) if (header.Key != VsVersionInfoKey) throw new FormatException($"Input stream does not point to a {VsVersionInfoKey} entry."); - var result = new VersionInfoResource(); + var result = new VersionInfoResource(lcid); // Read fixed version info. reader.Align(4); @@ -97,34 +207,6 @@ private static VersionTableEntry ReadNextEntry(ref BinaryStreamReader reader) }; } - private FixedVersionInfo _fixedVersionInfo = new FixedVersionInfo(); - private readonly Dictionary _entries = new Dictionary(); - - /// - public override string Key => VsVersionInfoKey; - - /// - protected override VersionTableValueType ValueType => VersionTableValueType.Binary; - - /// - /// Gets the fixed version info stored in this version resource. - /// - public FixedVersionInfo FixedVersionInfo - { - get => _fixedVersionInfo; - set => _fixedVersionInfo = value ?? throw new ArgumentNullException(nameof(value)); - } - - /// - /// Gets or sets a version table entry by its name. - /// - /// The name of the child. - public VersionTableEntry this[string name] - { - get => _entries[name]; - set => _entries[name] = value; - } - /// /// Gets a collection of entries stored in the version resource. /// @@ -181,28 +263,32 @@ public override uint GetPhysicalSize() protected override void WriteValue(IBinaryStreamWriter writer) { FixedVersionInfo.Write(writer); - writer.Align(4); foreach (var entry in _entries.Values) + { + writer.Align(4); entry.Write(writer); + } } /// public void WriteToDirectory(IResourceDirectory rootDirectory) { - // Construct new directory. - var newVersionDirectory = new ResourceDirectory(ResourceType.Version) + // Add version directory if it doesn't exist yet. + if (!rootDirectory.TryGetDirectory(ResourceType.Version, out var versionDirectory)) { - Entries = - { - new ResourceDirectory(1) - { - Entries = {new ResourceData(0u, this)} - } - } - }; + versionDirectory = new ResourceDirectory(ResourceType.Version); + rootDirectory.Entries.Add(versionDirectory); + } + + // Add category directory if it doesn't exist yet. + if (!versionDirectory.TryGetDirectory(1, out var categoryDirectory)) + { + categoryDirectory = new ResourceDirectory(1); + versionDirectory.Entries.Add(categoryDirectory); + } - // Insert. - rootDirectory.AddOrReplaceEntry(newVersionDirectory); + // Insert / replace data entry. + categoryDirectory.AddOrReplaceEntry(new ResourceData((uint) Lcid, this)); } } } diff --git a/src/AsmResolver.PE/AsmResolver.PE.csproj b/src/AsmResolver.PE/AsmResolver.PE.csproj index a008308e1..b867a61e3 100644 --- a/src/AsmResolver.PE/AsmResolver.PE.csproj +++ b/src/AsmResolver.PE/AsmResolver.PE.csproj @@ -24,6 +24,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver.PE/Certificates/AttributeCertificate.cs b/src/AsmResolver.PE/Certificates/AttributeCertificate.cs new file mode 100644 index 000000000..23a0c224a --- /dev/null +++ b/src/AsmResolver.PE/Certificates/AttributeCertificate.cs @@ -0,0 +1,75 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// When derived from this class, represents a single certificate in the attribute certificate data directory of + /// a signed portable executable file. + /// + public abstract class AttributeCertificate : SegmentBase + { + /// + /// Defines the size of a single header of a certificate. + /// + public const uint HeaderSize = + sizeof(uint) // dwLength + + sizeof(ushort) // wRevision + + sizeof(ushort) // wCertificateType + ; + + /// + /// Gets the revision of the file format that is used for this certificate. + /// + public abstract CertificateRevision Revision + { + get; + } + + /// + /// Gets the type of the certificate. + /// + public abstract CertificateType Type + { + get; + } + + /// + /// Gets a value indicating whether the raw contents of the signature can be read using a + /// instance. + /// + public abstract bool CanRead + { + get; + } + + /// + /// Creates a binary reader that reads the raw contents of the stored signature. + /// + /// The reader. + public abstract BinaryStreamReader CreateContentReader(); + + /// + public override uint GetPhysicalSize() => HeaderSize + GetContentsSize(); + + /// + /// Gets the total size in bytes of the certificate contents itself, excluding the header. + /// + /// The number of bytes. + protected abstract uint GetContentsSize(); + + /// + public override void Write(IBinaryStreamWriter writer) + { + writer.WriteUInt32(GetPhysicalSize()); + writer.WriteUInt16((ushort) Revision); + writer.WriteUInt16((ushort) Type); + WriteContents(writer); + } + + /// + /// Writes the contents of the certificate to the provided output stream. + /// + /// The output stream. + protected abstract void WriteContents(IBinaryStreamWriter writer); + } +} diff --git a/src/AsmResolver.PE/Certificates/CertificateCollection.cs b/src/AsmResolver.PE/Certificates/CertificateCollection.cs new file mode 100644 index 000000000..65b07150c --- /dev/null +++ b/src/AsmResolver.PE/Certificates/CertificateCollection.cs @@ -0,0 +1,65 @@ +using System.Collections.ObjectModel; +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// Represents the collection of attribute certificates that are stored in a signed portable executable file. + /// + public class CertificateCollection : Collection, ISegment + { + /// + public ulong Offset + { + get; + private set; + } + + /// + public uint Rva + { + get; + private set; + } + + /// + public bool CanUpdateOffsets => true; + + /// + public void UpdateOffsets(in RelocationParameters parameters) + { + Offset = parameters.Offset; + Rva = parameters.Rva; + + var current = parameters; + for (int i = 0; i < Items.Count; i++) + { + var certificate = Items[i]; + certificate.UpdateOffsets(current); + current.Advance(certificate.GetPhysicalSize().Align(8)); + } + } + + /// + public uint GetPhysicalSize() + { + uint size = 0; + for (int i = 0; i < Items.Count; i++) + size += Items[i].GetPhysicalSize().Align(8); + return size; + } + + /// + public uint GetVirtualSize() => GetPhysicalSize(); + + /// + public void Write(IBinaryStreamWriter writer) + { + for (int i = 0; i < Items.Count; i++) + { + Items[i].Write(writer); + writer.Align(8); + } + } + } +} diff --git a/src/AsmResolver.PE/Certificates/CertificateRevision.cs b/src/AsmResolver.PE/Certificates/CertificateRevision.cs new file mode 100644 index 000000000..c004b809b --- /dev/null +++ b/src/AsmResolver.PE/Certificates/CertificateRevision.cs @@ -0,0 +1,18 @@ +namespace AsmResolver.PE.Certificates +{ + /// + /// Provides members describing all possible file format revisions that can be used in an attribute certificate entry. + /// + public enum CertificateRevision : ushort + { + /// + /// Indicates version 1.0 of the file format is used. + /// + Revision_v1_0 = 0x0100, + + /// + /// Indicates version 2.0 of the file format is used. + /// + Revision_v2_0 = 0x0200 + } +} diff --git a/src/AsmResolver.PE/Certificates/CertificateType.cs b/src/AsmResolver.PE/Certificates/CertificateType.cs new file mode 100644 index 000000000..5d3eb83e4 --- /dev/null +++ b/src/AsmResolver.PE/Certificates/CertificateType.cs @@ -0,0 +1,29 @@ +namespace AsmResolver.PE.Certificates +{ + /// + /// Provides members describing all possible certificate types a single attribute certificate in a signed portable + /// executable file can contain. + /// + public enum CertificateType : ushort + { + /// + /// Indicates the contents of the attribute is a X.509 certificate. + /// + X509 = 1, + + /// + /// Indicates the contents of the attribute is a PKCS#7 SignedData structure. + /// + PkcsSignedData = 2, + + /// + /// Reserved. + /// + Reserved1 = 3, + + /// + /// Indicates the contents of the attribute is a Terminal Server Protocol stack certificate. + /// + TsStackSigned = 4 + } +} diff --git a/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs b/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs new file mode 100644 index 000000000..3f9ecd92b --- /dev/null +++ b/src/AsmResolver.PE/Certificates/CustomAttributeCertificate.cs @@ -0,0 +1,80 @@ +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// Represents an attribute certificate that contains a custom or unsupported certificate type or file format. + /// + [DebuggerDisplay("{Type}")] + public class CustomAttributeCertificate : AttributeCertificate + { + /// + /// Creates a new custom attribute certificate with the provided contents. + /// + /// The certificate type to store. + /// The certificate data. + public CustomAttributeCertificate(CertificateType type, IReadableSegment? contents) + : this(CertificateRevision.Revision_v2_0, type, contents) + { + } + + /// + /// Creates a new custom attribute certificate with the provided contents. + /// + /// The revision of the file format to use. + /// The certificate type to store. + /// The certificate data. + public CustomAttributeCertificate(CertificateRevision revision, CertificateType type, IReadableSegment? contents) + { + Revision = revision; + Type = type; + Contents = contents; + } + + /// + public override CertificateRevision Revision + { + get; + } + + /// + public override CertificateType Type + { + get; + } + + /// + /// Gets or sets the raw contents of the certificate. + /// + public IReadableSegment? Contents + { + get; + set; + } + + /// + [MemberNotNullWhen(true, nameof(Contents))] + public override bool CanRead => Contents is not null; + + /// + public override BinaryStreamReader CreateContentReader() => CanRead + ? Contents.CreateReader() + : throw new InvalidOperationException("Signature cannot be read."); + + /// + public override void UpdateOffsets(in RelocationParameters parameters) + { + base.UpdateOffsets(in parameters); + Contents?.UpdateOffsets(parameters.WithAdvance(HeaderSize)); + } + + /// + protected override uint GetContentsSize() => Contents?.GetPhysicalSize() ?? 0; + + /// + protected override void WriteContents(IBinaryStreamWriter writer) => Contents?.Write(writer); + } +} diff --git a/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs b/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs new file mode 100644 index 000000000..60992e9ab --- /dev/null +++ b/src/AsmResolver.PE/Certificates/DefaultCertificateReader.cs @@ -0,0 +1,21 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// Provides a default implementation of an attribute certificate reader. This reader defaults to instantiating + /// if an unknown or unsupported file format is encountered. + /// + public class DefaultCertificateReader : ICertificateReader + { + /// + public AttributeCertificate ReadCertificate( + PEReaderContext context, + CertificateRevision revision, + CertificateType type, + BinaryStreamReader reader) + { + return new CustomAttributeCertificate(revision, type, reader.ReadSegment(reader.Length)); + } + } +} diff --git a/src/AsmResolver.PE/Certificates/ICertificateReader.cs b/src/AsmResolver.PE/Certificates/ICertificateReader.cs new file mode 100644 index 000000000..1fc1b9053 --- /dev/null +++ b/src/AsmResolver.PE/Certificates/ICertificateReader.cs @@ -0,0 +1,25 @@ +using AsmResolver.IO; + +namespace AsmResolver.PE.Certificates +{ + /// + /// Provides members for reading and interpreting the contents of an attribute certificate in a signed + /// portable executable file. + /// + public interface ICertificateReader + { + /// + /// Reads a single attribute certificate. + /// + /// The context in which the reader is situated in. + /// The file format revision to use. + /// The type of certificate to read. + /// The reader pointing to the start of the raw data of the certificate. + /// The read attribute certificate. + AttributeCertificate ReadCertificate( + PEReaderContext context, + CertificateRevision revision, + CertificateType type, + BinaryStreamReader reader); + } +} diff --git a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs index 8475d8805..f2c32be92 100644 --- a/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs +++ b/src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs @@ -154,6 +154,31 @@ public IFieldRvaDataReader FieldRvaDataReader } } + /// + /// Creates a new managed PE file builder with default settings. + /// + public ManagedPEFileBuilder() + : this(ThrowErrorListener.Instance) + { + } + + /// + /// Creates a new managed PE file builder with the provided error listener. + /// + public ManagedPEFileBuilder(IErrorListener errorListener) + { + ErrorListener = errorListener; + } + + /// + /// Gets or sets the object responsible for recording diagnostic information during the building process. + /// + public IErrorListener ErrorListener + { + get; + set; + } + /// protected override ManagedPEBuilderContext CreateContext(IPEImage image) => new(image); @@ -436,7 +461,7 @@ protected override uint GetSectionAlignment(PEFile peFile, IPEImage image, Manag protected override uint GetImageBase(PEFile peFile, IPEImage image, ManagedPEBuilderContext context) => (uint) image.ImageBase; - private static void ProcessRvasInMetadataTables(ManagedPEBuilderContext context) + private void ProcessRvasInMetadataTables(ManagedPEBuilderContext context) { var dotNetSegment = context.DotNetSegment; var tablesStream = dotNetSegment.DotNetDirectory.Metadata?.GetStream(); @@ -447,7 +472,7 @@ private static void ProcessRvasInMetadataTables(ManagedPEBuilderContext context) AddFieldRvasToTable(context); } - private static void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesStream tablesStream) + private void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesStream tablesStream) { var methodTable = tablesStream.GetTable(); for (int i = 0; i < methodTable.Count; i++) @@ -472,7 +497,7 @@ private static void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesSt } } - private static ISegment? GetMethodBodySegment(MethodDefinitionRow methodRow) + private ISegment? GetMethodBodySegment(MethodDefinitionRow methodRow) { if (methodRow.Body.IsBounded) return methodRow.Body.GetSegment(); @@ -485,13 +510,13 @@ private static void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesSt return CilRawMethodBody.FromReader(ThrowErrorListener.Instance, ref reader); } - throw new NotImplementedException("Native unbounded method bodies cannot be reassembled yet."); + ErrorListener.NotSupported("Native unbounded method bodies cannot be reassembled yet."); } return null; } - private static void AddFieldRvasToTable(ManagedPEBuilderContext context) + private void AddFieldRvasToTable(ManagedPEBuilderContext context) { var directory = context.DotNetSegment.DotNetDirectory; var fieldRvaTable = directory.Metadata! @@ -504,18 +529,21 @@ private static void AddFieldRvasToTable(ManagedPEBuilderContext context) var table = context.DotNetSegment.FieldRvaTable; var reader = context.FieldRvaDataReader; - for (int i = 0; i < fieldRvaTable.Count; i++) + for (uint rid = 1; rid <= fieldRvaTable.Count; rid++) { + ref var row = ref fieldRvaTable.GetRowRef(rid); var data = reader.ResolveFieldData( - ThrowErrorListener.Instance, + ErrorListener, context.Platform, directory, - fieldRvaTable[i]); + row + ); if (data is null) continue; table.Add(data); + row.Data = data.ToReference(); } } } diff --git a/src/AsmResolver.PE/Exports/Builder/ExportAddressTableBuffer.cs b/src/AsmResolver.PE/Exports/Builder/ExportAddressTableBuffer.cs index 8963bb8ce..73043915b 100644 --- a/src/AsmResolver.PE/Exports/Builder/ExportAddressTableBuffer.cs +++ b/src/AsmResolver.PE/Exports/Builder/ExportAddressTableBuffer.cs @@ -10,6 +10,16 @@ namespace AsmResolver.PE.Exports.Builder public class ExportAddressTableBuffer : SegmentBase { private readonly List _entries = new(); + private readonly NameTableBuffer _nameTableBuffer; + + /// + /// Constructs a new export address table buffer. + /// + /// The name table buffer to use for obtaining the RVAs of forwarder symbols. + public ExportAddressTableBuffer(NameTableBuffer nameTableBuffer) + { + _nameTableBuffer = nameTableBuffer; + } /// /// Adds a single symbol to the address table buffer. @@ -24,7 +34,11 @@ public class ExportAddressTableBuffer : SegmentBase public override void Write(IBinaryStreamWriter writer) { foreach (var entry in _entries) - writer.WriteUInt32(entry.Address.Rva); + { + writer.WriteUInt32(entry.IsForwarder + ? _nameTableBuffer.GetNameRva(entry.ForwarderName) + : entry.Address.Rva); + } } } } diff --git a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs index 8409daf39..a754c7327 100644 --- a/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs +++ b/src/AsmResolver.PE/Exports/Builder/ExportDirectoryBuffer.cs @@ -40,8 +40,8 @@ public class ExportDirectoryBuffer : SegmentBase public ExportDirectoryBuffer() { // Initialize table buffers. - _addressTableBuffer = new ExportAddressTableBuffer(); _nameTableBuffer = new NameTableBuffer(); + _addressTableBuffer = new ExportAddressTableBuffer(_nameTableBuffer); _ordinalNamePointerTable = new OrdinalNamePointerTableBuffer(_nameTableBuffer); _contentsBuilder = new SegmentBuilder @@ -77,8 +77,12 @@ public void AddDirectory(IExportDirectory exportDirectory) { _addressTableBuffer.AddSymbol(symbol); _ordinalNamePointerTable.AddSymbol(symbol); + if (symbol.IsByName) _nameTableBuffer.AddName(symbol.Name); + + if (symbol.IsForwarder) + _nameTableBuffer.AddName(symbol.ForwarderName); } } diff --git a/src/AsmResolver.PE/Exports/ExportedSymbol.cs b/src/AsmResolver.PE/Exports/ExportedSymbol.cs index 472f96828..2e52cf49e 100644 --- a/src/AsmResolver.PE/Exports/ExportedSymbol.cs +++ b/src/AsmResolver.PE/Exports/ExportedSymbol.cs @@ -1,4 +1,6 @@ +using System; using System.Diagnostics.CodeAnalysis; +using System.IO; using AsmResolver.Collections; namespace AsmResolver.PE.Exports @@ -29,6 +31,19 @@ public ExportedSymbol(ISegmentReference address, string? name) Address = address; } + /// + /// Creates a new forwarder symbol that is exported by name. + /// + /// The reference to the segment representing the symbol. + /// The name of the symbol. + /// The name of the forwarded symbol. + public ExportedSymbol(ISegmentReference address, string? name, string? forwarderName) + { + Name = name; + Address = address; + ForwarderName = forwarderName; + } + /// /// Gets the export directory this symbol was added to (if available). /// @@ -82,6 +97,7 @@ public string? Name /// /// For exported functions, this reference points to the first instruction that is executed. /// For exported fields, this reference points to the first byte of data that this field consists of. + /// For forwarded symbols, this reference points to the name of the symbol the forwarder is referencing. /// public ISegmentReference Address { @@ -89,15 +105,47 @@ public ISegmentReference Address set; } - /// - public override string ToString() + /// + /// When the symbol is a forwarder symbol, gets or sets the full name of the symbol that this export is forwarded to. + /// + /// + /// For exports by name, this name should be in the format ModuleName.ExportName. + /// For exports by ordinal, this name should be in the format ModuleName.#1. + /// Failure in doing so will make the Windows PE loader not able to resolve the forwarder symbol. + /// + public string? ForwarderName + { + get; + set; + } + + /// + /// Gets a value indicating whether the symbol is forwarded to another external symbol. + /// + [MemberNotNullWhen(true, nameof(ForwarderName))] + public bool IsForwarder => ForwarderName is not null; + + /// + /// Obtains a name that can be used as a in another export. + /// + /// The name. + public string FormatNameAsForwarderSymbol() => FormatFullName('.'); + + private string FormatFullName(char separator) { string displayName = Name ?? $"#{Ordinal.ToString()}"; - return ParentDirectory is null - ? displayName - : $"{ParentDirectory.Name}!{displayName}"; + if (ParentDirectory is not {Name: { } parentName}) + return displayName; + + if (string.Equals(Path.GetExtension(parentName), ".dll", StringComparison.OrdinalIgnoreCase)) + parentName = Path.GetFileNameWithoutExtension(parentName); + + return $"{parentName}{separator}{displayName}"; } + /// + public override string ToString() => FormatFullName('!'); + /// public ISegmentReference GetReference() => Address; } diff --git a/src/AsmResolver.PE/Exports/SerializedExportDirectory.cs b/src/AsmResolver.PE/Exports/SerializedExportDirectory.cs index 431188b85..03a530291 100644 --- a/src/AsmResolver.PE/Exports/SerializedExportDirectory.cs +++ b/src/AsmResolver.PE/Exports/SerializedExportDirectory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using AsmResolver.IO; +using AsmResolver.PE.File.Headers; namespace AsmResolver.PE.Exports { @@ -10,6 +11,7 @@ namespace AsmResolver.PE.Exports public class SerializedExportDirectory : ExportDirectory { private readonly PEReaderContext _context; + private readonly DataDirectory _dataDirectory; private readonly uint _nameRva; private readonly uint _numberOfFunctions; private readonly uint _numberOfNames; @@ -27,6 +29,7 @@ public SerializedExportDirectory(PEReaderContext context, ref BinaryStreamReader if (!reader.IsValid) throw new ArgumentNullException(nameof(reader)); _context = context ?? throw new ArgumentNullException(nameof(context)); + _dataDirectory = new DataDirectory(reader.StartRva, reader.Length); ExportFlags = reader.ReadUInt32(); TimeDateStamp = reader.ReadUInt32(); @@ -87,7 +90,15 @@ protected override IList GetExports() string? name = null; ordinalNameTable?.TryGetValue(i, out name); - result.Add(new ExportedSymbol(_context.File.GetReferenceToRva(rva), name)); + string? forwarderName = null; + if (rva >= _dataDirectory.VirtualAddress + && rva < _dataDirectory.VirtualAddress + _dataDirectory.Size + && _context.File.TryCreateReaderAtRva(rva, out var forwarderReader)) + { + forwarderName = forwarderReader.ReadAsciiString(); + } + + result.Add(new ExportedSymbol(_context.File.GetReferenceToRva(rva), name, forwarderName)); } return result; diff --git a/src/AsmResolver.PE/IPEImage.cs b/src/AsmResolver.PE/IPEImage.cs index 4a29efd91..ab54a3150 100644 --- a/src/AsmResolver.PE/IPEImage.cs +++ b/src/AsmResolver.PE/IPEImage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using AsmResolver.PE.Certificates; using AsmResolver.PE.Debug; using AsmResolver.PE.DotNet; using AsmResolver.PE.Exceptions; @@ -182,5 +183,13 @@ IList DebugData get; set; } + + /// + /// Gets a collection of attribute certificates that were added to the executable. + /// + CertificateCollection Certificates + { + get; + } } } diff --git a/src/AsmResolver.PE/PEImage.cs b/src/AsmResolver.PE/PEImage.cs index 7849e172f..7f2b49481 100644 --- a/src/AsmResolver.PE/PEImage.cs +++ b/src/AsmResolver.PE/PEImage.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using AsmResolver.IO; +using AsmResolver.PE.Certificates; using AsmResolver.PE.Debug; using AsmResolver.PE.DotNet; using AsmResolver.PE.Exceptions; @@ -28,6 +29,7 @@ public class PEImage : IPEImage private readonly LazyVariable _dotNetDirectory; private IList? _debugData; private readonly LazyVariable _tlsDirectory; + private CertificateCollection? _certificates; /// /// Opens a PE image from a specific file on the disk. @@ -301,6 +303,17 @@ public ITlsDirectory? TlsDirectory set => _tlsDirectory.SetValue(value); } + /// + public CertificateCollection Certificates + { + get + { + if (_certificates is null) + Interlocked.CompareExchange(ref _certificates, GetCertificates(), null); + return _certificates; + } + } + /// /// Obtains the list of modules that were imported into the PE. /// @@ -372,5 +385,15 @@ public ITlsDirectory? TlsDirectory /// This method is called upon initialization of the property. /// protected virtual ITlsDirectory? GetTlsDirectory() => null; + + /// + /// Obtains the data directory containing the attribute certificates table of the executable. + /// + /// The attribute certificates. + /// + /// This method is called upon initialization of the property. + /// + protected virtual CertificateCollection GetCertificates() => new(); + } } diff --git a/src/AsmResolver.PE/PEReaderParameters.cs b/src/AsmResolver.PE/PEReaderParameters.cs index b8b695046..cb8ee0573 100644 --- a/src/AsmResolver.PE/PEReaderParameters.cs +++ b/src/AsmResolver.PE/PEReaderParameters.cs @@ -1,5 +1,6 @@ using System; using AsmResolver.IO; +using AsmResolver.PE.Certificates; using AsmResolver.PE.Debug; using AsmResolver.PE.DotNet.Metadata; @@ -26,6 +27,7 @@ public PEReaderParameters(IErrorListener errorListener) { MetadataStreamReader = new DefaultMetadataStreamReader(); DebugDataReader = new DefaultDebugDataReader(); + CertificateReader = new DefaultCertificateReader(); ErrorListener = errorListener ?? throw new ArgumentNullException(nameof(errorListener)); } @@ -56,6 +58,16 @@ public IDebugDataReader DebugDataReader set; } + /// + /// Gets or sets the object responsible for reading certificates (such as authenticode signatures) in the + /// security data directory of the input PE file. + /// + public ICertificateReader CertificateReader + { + get; + set; + } + /// /// Gets the service to use for reading any additional files from the disk while reading the portable executable. /// diff --git a/src/AsmResolver.PE/SerializedPEImage.cs b/src/AsmResolver.PE/SerializedPEImage.cs index 041d5579b..4be7e1cb2 100644 --- a/src/AsmResolver.PE/SerializedPEImage.cs +++ b/src/AsmResolver.PE/SerializedPEImage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using AsmResolver.PE.Certificates; using AsmResolver.PE.Debug; using AsmResolver.PE.DotNet; using AsmResolver.PE.Exceptions; @@ -146,5 +147,46 @@ protected override IList GetDebugData() return new SerializedTlsDirectory(ReaderContext, ref reader); } + + /// + protected override CertificateCollection GetCertificates() + { + var result = new CertificateCollection(); + + var dataDirectory = PEFile.OptionalHeader.GetDataDirectory(DataDirectoryIndex.CertificateDirectory); + if (!dataDirectory.IsPresentInPE) + return result; + + // Certificate directory interprets the VirtualAddress of the data directory as a file offset as opposed to + // an RVA. Hence, we cannot use the normal TryCreateDataDirectoryReader method. + if (!PEFile.TryCreateReaderAtFileOffset(dataDirectory.VirtualAddress, dataDirectory.Size, out var reader)) + return result; + + result.UpdateOffsets(new RelocationParameters(dataDirectory.VirtualAddress, dataDirectory.VirtualAddress)); + + var certificateReader = ReaderContext.Parameters.CertificateReader; + + while (reader.CanRead(AttributeCertificate.HeaderSize)) + { + // Read header. + uint length = reader.ReadUInt32(); + var revision = (CertificateRevision) reader.ReadUInt16(); + var type = (CertificateType) reader.ReadUInt16(); + + // Bound contents reader to just the contents as indicated by the header. + var contentsReader = reader.ForkRelative( + AttributeCertificate.HeaderSize, + length - AttributeCertificate.HeaderSize); + + // Read it. + result.Add(certificateReader.ReadCertificate(ReaderContext, revision, type, contentsReader)); + + // Advance to next certificate in the table. + reader.RelativeOffset += length - AttributeCertificate.HeaderSize; + reader.Align(8); + } + + return result; + } } } diff --git a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj index b2802c3ce..d597389f5 100644 --- a/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj +++ b/src/AsmResolver.Symbols.Pdb/AsmResolver.Symbols.Pdb.csproj @@ -26,6 +26,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/AsmResolver/AsmResolver.csproj b/src/AsmResolver/AsmResolver.csproj index b7685bc8a..ea9a1dcc6 100644 --- a/src/AsmResolver/AsmResolver.csproj +++ b/src/AsmResolver/AsmResolver.csproj @@ -22,6 +22,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj index 5ce11d3bf..9357b967b 100644 --- a/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj +++ b/test/AsmResolver.Benchmarks/AsmResolver.Benchmarks.csproj @@ -7,7 +7,7 @@ - + diff --git a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj index b71b3c0a6..816b9b8fa 100644 --- a/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj +++ b/test/AsmResolver.DotNet.Dynamic.Tests/AsmResolver.DotNet.Dynamic.Tests.csproj @@ -8,10 +8,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj index 40011e503..1f798a757 100644 --- a/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj +++ b/test/AsmResolver.DotNet.Tests/AsmResolver.DotNet.Tests.csproj @@ -9,9 +9,10 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.DotNet.Tests/AssemblyReferenceTest.cs b/test/AsmResolver.DotNet.Tests/AssemblyReferenceTest.cs index 24a282457..7c524b2d2 100644 --- a/test/AsmResolver.DotNet.Tests/AssemblyReferenceTest.cs +++ b/test/AsmResolver.DotNet.Tests/AssemblyReferenceTest.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using System.Runtime.InteropServices; using Xunit; @@ -13,7 +14,7 @@ public void ReadName() var assemblyRef = module.AssemblyReferences[0]; Assert.Equal("mscorlib", assemblyRef.Name); } - + [Fact] public void ReadVersion() { @@ -21,7 +22,7 @@ public void ReadVersion() var assemblyRef = module.AssemblyReferences[0]; Assert.Equal(new Version(4,0,0,0), assemblyRef.Version); } - + [Fact] public void ReadPublicKeyOrToken() { @@ -33,6 +34,29 @@ public void ReadPublicKeyOrToken() Assert.Equal(expectedToken, assemblyRef.GetPublicKeyToken()); } + [Fact] + public void GetFullNameCultureNeutralAssembly() + { + var name = new AssemblyName(KnownCorLibs.MsCorLib_v4_0_0_0.FullName); + Assert.Equal("mscorlib", name.Name); + Assert.Equal(new Version(4,0,0,0), name.Version); + Assert.Equal(string.Empty, name.CultureName); + Assert.Equal(new byte[] {0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89}, name.GetPublicKeyToken()); + } + + [Fact] + public void GetFullNameCultureNonNeutralAssembly() + { + var assembly = new AssemblyReference("SomeAssembly", new Version(1, 2, 3, 4)); + assembly.Culture = "en-US"; + + var name = new AssemblyName(assembly.FullName); + Assert.Equal("SomeAssembly", name.Name); + Assert.Equal(new Version(1,2,3,4), name.Version); + Assert.Equal("en-US", name.CultureName); + Assert.Equal(Array.Empty(), name.GetPublicKeyToken()); + } + [Fact] public void CorLibResolution() { @@ -44,4 +68,4 @@ public void CorLibResolution() Assert.Equal(assemblyDef.Version, assemblyDef.Version); } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs index 050c8991a..88b68064c 100644 --- a/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs +++ b/test/AsmResolver.DotNet.Tests/Cloning/MetadataClonerTest.cs @@ -76,7 +76,7 @@ private static MethodDefinition CloneMethod(MethodBase methodBase, out MethodDef return clonedMethod; } - private static FieldDefinition CloneIntializerField(FieldInfo field, out FieldDefinition originalFieldDef) + private static FieldDefinition CloneInitializerField(FieldInfo field, out FieldDefinition originalFieldDef) { var sourceModule = ModuleDefinition.FromFile(field.Module.Assembly.Location); originalFieldDef = (FieldDefinition) sourceModule.LookupMember(field.MetadataToken); @@ -279,10 +279,10 @@ public void CloneConstant() public void CloneFieldRva() { var clonedInitializerField = - CloneIntializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field); + CloneInitializerField(typeof(InitialValues).GetField(nameof(InitialValues.ByteArray)), out var field); - var originalData = ((IReadableSegment) field.FieldRva).ToArray(); - var newData = ((IReadableSegment) clonedInitializerField.FieldRva).ToArray(); + var originalData = ((IReadableSegment) field.FieldRva!).ToArray(); + var newData = ((IReadableSegment) clonedInitializerField.FieldRva!).ToArray(); Assert.Equal(originalData, newData); } @@ -391,5 +391,22 @@ public void CloneAndInject() Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes)); } + [Fact] + public void CloneAndInjectAndAssignToken() + { + var sourceModule = ModuleDefinition.FromFile(typeof(Miscellaneous).Assembly.Location); + var targetModule = PrepareTempModule(); + + var type = sourceModule.TopLevelTypes.First(t => t.Name == nameof(Miscellaneous)); + + var result = new MemberCloner(targetModule) + .Include(type) + .AddListener(new InjectTypeClonerListener(targetModule)) + .AddListener(new AssignTokensClonerListener(targetModule)) + .Clone(); + + Assert.All(result.ClonedTopLevelTypes, t => Assert.Contains(t, targetModule.TopLevelTypes)); + Assert.All(result.ClonedMembers, m => Assert.NotEqual(0u, ((IMetadataMember) m).MetadataToken.Rid)); + } } } diff --git a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs index e59af51e6..5a12c217c 100644 --- a/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs +++ b/test/AsmResolver.DotNet.Tests/Code/Native/NativeMethodBodyTest.cs @@ -3,16 +3,21 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using AsmResolver.DotNet.Code.Native; using AsmResolver.DotNet.Signatures; +using AsmResolver.Patching; using AsmResolver.PE; using AsmResolver.PE.Code; using AsmResolver.PE.DotNet; +using AsmResolver.PE.DotNet.Builder; using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; +using AsmResolver.PE.File; using AsmResolver.PE.File.Headers; using AsmResolver.PE.Imports; +using AsmResolver.PE.Platforms; using AsmResolver.Tests.Runners; using Xunit; @@ -33,17 +38,18 @@ private static NativeMethodBody CreateDummyBody(bool isVoid, bool is32Bit) { var module = ModuleDefinition.FromBytes(Properties.Resources.TheAnswer_NetFx); - module.Attributes &= ~DotNetDirectoryFlags.ILOnly; + module.IsILOnly = false; if (is32Bit) { module.PEKind = OptionalHeaderMagic.PE32; module.MachineType = MachineType.I386; - module.Attributes |= DotNetDirectoryFlags.Bit32Required; + module.IsBit32Required = true; } else { module.PEKind = OptionalHeaderMagic.PE32Plus; module.MachineType = MachineType.Amd64; + module.IsBit32Required = false; } var method = module @@ -53,7 +59,7 @@ private static NativeMethodBody CreateDummyBody(bool isVoid, bool is32Bit) method.ImplAttributes |= MethodImplAttributes.Unmanaged | MethodImplAttributes.Native | MethodImplAttributes.PreserveSig; - method.DeclaringType.Methods.Remove(method); + method.DeclaringType!.Methods.Remove(method); module.GetOrCreateModuleType().Methods.Add(method); return method.NativeMethodBody = new NativeMethodBody(method); @@ -81,7 +87,7 @@ public void NativeMethodBodyShouldResultInRawCodeSegment() }; // Serialize module to PE image. - var module = body.Owner.Module; + var module = body.Owner.Module!; var image = module.ToPEImage(); // Lookup method row. @@ -288,26 +294,79 @@ public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint // Define local symbol. var messageSymbol = new NativeLocalSymbol(body, symbolOffset); + InjectCallToNativeBody(body, messageSymbol, fixupOffset, fixupType); + + // Verify. + _fixture + .GetRunner() + .RebuildAndRun(body.Owner.Module!, "StringPointer.exe", $"Hello, world!{Environment.NewLine}"); + } + + [SkippableTheory] + [InlineData( + true, + new byte[] {0xB8, 0x00, 0x00, 0x00, 0x00}, // mov eax, message + 1u, AddressFixupType.Absolute32BitAddress)] + [InlineData( + false, + new byte[] {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // mov rax, message + 2u, AddressFixupType.Absolute64BitAddress)] + public void NativeBodyWithGlobalSymbol(bool is32Bit, byte[] movInstruction, uint fixupOffset, AddressFixupType fixupType) + { + Skip.IfNot(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), NonWindowsPlatform); + + // Create native body. + var code = new List(movInstruction) + { + 0xc3 // ret + }; + + var body = CreateDummyBody(false, is32Bit); + body.Code = code.ToArray(); + + // Define new symbol. + var messageSegment = new DataSegment(Encoding.Unicode.GetBytes("Hello, world!\0")); + var messageSymbol = new Symbol(messageSegment.ToReference()); + + InjectCallToNativeBody(body, messageSymbol, fixupOffset, fixupType); + + // Add symbol to new section. + var image = body.Owner.Module!.ToPEImage(); + var file = new ManagedPEFileBuilder().CreateFile(image); + file.Sections.Add(new PESection( + ".asmres", + SectionFlags.MemoryRead | SectionFlags.ContentInitializedData, + messageSegment)); + + // Verify. + _fixture + .GetRunner() + .RebuildAndRun(file, "StringPointer.exe", $"Hello, world!{Environment.NewLine}"); + } + + private static void InjectCallToNativeBody(NativeMethodBody body, ISymbol messageSymbol, uint fixupOffset, AddressFixupType fixupType) + { // Fixup address in mov instruction. body.AddressFixups.Add(new AddressFixup(fixupOffset, fixupType, messageSymbol)); // Update main to call native method, convert the returned pointer to a String, and write to stdout. - var module = body.Owner.Module; - body.Owner!.Signature!.ReturnType = body.Owner.Module!.CorLibTypeFactory.IntPtr; - var stringConstructor = new MemberReference( - module!.CorLibTypeFactory.String.Type, - ".ctor", - MethodSignature.CreateInstance( - module.CorLibTypeFactory.Void, - module.CorLibTypeFactory.Char.MakePointerType()) - ); - var writeLine = new MemberReference( - new TypeReference(module, module.CorLibTypeFactory.CorLibScope, "System", "Console"), - "WriteLine", - MethodSignature.CreateStatic( - module.CorLibTypeFactory.Void, - module.CorLibTypeFactory.String) - ); + var module = body.Owner.Module!; + body.Owner.Signature!.ReturnType = module.CorLibTypeFactory.IntPtr; + + var stringConstructor = module.CorLibTypeFactory.String.Type + .CreateMemberReference(".ctor", MethodSignature.CreateInstance( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.Char.MakePointerType() + )) + .ImportWith(module.DefaultImporter); + + var writeLine = module.CorLibTypeFactory.CorLibScope + .CreateTypeReference("System", "Console") + .CreateMemberReference("WriteLine", MethodSignature.CreateStatic( + module.CorLibTypeFactory.Void, + module.CorLibTypeFactory.String + )) + .ImportWith(module.DefaultImporter); var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; instructions.Clear(); @@ -315,11 +374,6 @@ public void NativeBodyWithLocalSymbols(bool is32Bit, byte[] movInstruction, uint instructions.Add(CilOpCodes.Newobj, stringConstructor); instructions.Add(CilOpCodes.Call, writeLine); instructions.Add(CilOpCodes.Ret); - - // Verify. - _fixture - .GetRunner() - .RebuildAndRun(module, "StringPointer.exe", $"Hello, world!{Environment.NewLine}"); } } } diff --git a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs index ae0e5db79..352ddb998 100644 --- a/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs +++ b/test/AsmResolver.DotNet.Tests/CustomAttributeTest.cs @@ -140,7 +140,7 @@ public void FixedStringArgument(bool rebuild, bool access) Assert.Empty(attribute.Signature.NamedArguments); var argument = attribute.Signature.FixedArguments[0]; - Assert.Equal("String fixed arg", argument.Element); + Assert.Equal("String fixed arg", Assert.IsAssignableFrom(argument.Element)); } [Theory] @@ -232,7 +232,7 @@ public void NamedStringArgument(bool rebuild, bool access) var argument = attribute.Signature.NamedArguments[0]; Assert.Equal(nameof(TestCaseAttribute.StringValue), argument.MemberName); - Assert.Equal("String named arg", argument.Argument.Element); + Assert.Equal("String named arg", Assert.IsAssignableFrom(argument.Argument.Element)); } [Theory] diff --git a/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs b/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs new file mode 100644 index 000000000..206a808cf --- /dev/null +++ b/test/AsmResolver.DotNet.Tests/DotNetRuntimeInfoTest.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using AsmResolver.DotNet.Signatures; +using Xunit; + +namespace AsmResolver.DotNet.Tests +{ + public class DotNetRuntimeInfoTest + { + [Theory] + [InlineData(".NETFramework,Version=v2.0", "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + [InlineData(".NETFramework,Version=v3.5", "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + [InlineData(".NETFramework,Version=v4.0", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + [InlineData(".NETStandard,Version=v1.0", "System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [InlineData(".NETStandard,Version=v2.0", "netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51")] + [InlineData(".NETCoreApp,Version=v2.0", "System.Runtime, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + [InlineData(".NETCoreApp,Version=v5.0", "System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + public void DefaultCorLib(string name, string expectedCorLib) + { + Assert.Equal( + new ReflectionAssemblyDescriptor(new AssemblyName(expectedCorLib)), + (AssemblyDescriptor) DotNetRuntimeInfo.Parse(name).GetDefaultCorLib(), + SignatureComparer.Default); + } + } +} diff --git a/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs b/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs index 7d15df344..a8a0d1fb4 100644 --- a/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs +++ b/test/AsmResolver.DotNet.Tests/Memory/SequentialStructLayoutTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; using AsmResolver.DotNet.Memory; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; @@ -51,62 +52,81 @@ public void SingleFieldSequentialStructPack1() => [Fact] public void MultipleFieldsSequentialStructDefaultPack() => VerifySize(); - + [Fact] public void MultipleFieldsSequentialStructPack1() => VerifySize(); - + [Fact] public void LargeAndSmallFieldSequentialDefaultPack() => VerifySize(); - + [Fact] public void NestedStruct1() => VerifySize(); - + [Fact] public void NestedStruct2() => VerifySize(); - + [Fact] public void NestedStructWithEnclosingPack1() => VerifySize(); - + [Fact] public void NestedStructWithNestedPack1() => VerifySize(); - + [Fact] public void NestedStructInNestedStruct() => VerifySize(); - + [Fact] - public void ThreeLevelsNestingSequentialStructDefaultPack() => + public void ThreeLevelsNestingSequentialStructDefaultPack() => VerifySize(); - + [Fact] - public void ThreeLevelsNestingSequentialStructPack1() => + public void ThreeLevelsNestingSequentialStructPack1() => VerifySize(); - + [Fact] public void ExplicitlySizedEmptyStruct() => VerifySize(); - + [Fact] public void ExplicitlySizedSingleField() => VerifySize(); - + [Fact] public void ExplicitlySizedSmallerExplicitSizeThanActualSize() => VerifySize(); - + [Fact] public void StructWithPrimitiveFieldSmallerThanPack() => VerifySize(); - + [Fact] public void StructWithStructFieldSmallerThanPack() => VerifySize(); - + [Fact] public void PackLargerThanLargestField() => VerifySize(); - + [Fact] public void PackLargerThanLargestFieldWithImplicitAlignment() => VerifySize(); + + [Fact] + public void GenericStruct() => VerifySize>(); + + [Fact] + public void GenericNestedStruct() => VerifySize.NestedStruct>(); + + [Fact] + public void UninstantiatedGenericStructShouldThrow() + { + Assert.Throws(() => + { + var type = Module.LookupMember( + typeof(SequentialTestStructs.GenericStruct<,>).MetadataToken + ); + + type.GetImpliedMemoryLayout(IntPtr.Size == 4); + }); + } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/Memory/SequentialTestStructs.cs b/test/AsmResolver.DotNet.Tests/Memory/SequentialTestStructs.cs index a4b75b9f3..b24d05764 100644 --- a/test/AsmResolver.DotNet.Tests/Memory/SequentialTestStructs.cs +++ b/test/AsmResolver.DotNet.Tests/Memory/SequentialTestStructs.cs @@ -9,7 +9,7 @@ public static class SequentialTestStructs [StructLayout(LayoutKind.Sequential)] public struct EmptyStruct { - + } [StructLayout(LayoutKind.Sequential)] @@ -134,7 +134,7 @@ public struct FixedSizeStruct133 public struct PackLargerThanLargestField { public FixedSizeStruct133 Field1; - + public FixedSizeStruct133 Field2; } @@ -148,8 +148,23 @@ public struct FixedSizeStruct133WithField public struct PackLargerThanLargestFieldWithImplicitAlignment { public FixedSizeStruct133WithField Field1; - + public FixedSizeStruct133WithField Field2; } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct GenericStruct + { + public T1 Field1; + public T2 Field2; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct NestedStruct + { + public T1 Field1; + public T2 Field2; + } + } + } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/Memory/StructLayoutTestBase.cs b/test/AsmResolver.DotNet.Tests/Memory/StructLayoutTestBase.cs index 26a4c5f4e..9f885581a 100644 --- a/test/AsmResolver.DotNet.Tests/Memory/StructLayoutTestBase.cs +++ b/test/AsmResolver.DotNet.Tests/Memory/StructLayoutTestBase.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Runtime.CompilerServices; using AsmResolver.DotNet.Memory; using Xunit; @@ -17,11 +18,8 @@ protected ModuleDefinition Module get; } - protected TypeDefinition FindTestType(Type type) - { - return (TypeDefinition) Module.LookupMember(type.MetadataToken); - } - + private ITypeDescriptor FindTestType(Type type) => Module.DefaultImporter.ImportType(type); + protected void VerifySize() { var type = FindTestType(typeof(T)); @@ -30,4 +28,4 @@ protected void VerifySize() } } -} \ No newline at end of file +} diff --git a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs index 06d4d5ca7..e2dd55a28 100644 --- a/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs +++ b/test/AsmResolver.DotNet.Tests/MetadataResolverTest.cs @@ -4,6 +4,7 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables; using Xunit; namespace AsmResolver.DotNet.Tests @@ -52,6 +53,7 @@ public void ResolveSystemObjectNetCore() var reference = new TypeReference(module.CorLibTypeFactory.CorLibScope, "System", "Object"); var definition = _coreResolver.ResolveType(reference); + Assert.NotNull(definition); Assert.True(definition.IsTypeOf(reference.Namespace, reference.Name)); } @@ -69,8 +71,8 @@ public void ResolveType() { var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); - var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly), - typeof(TopLevelClass1).Namespace, typeof(TopLevelClass1).Name); + var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly!), + typeof(TopLevelClass1).Namespace, nameof(TopLevelClass1)); var definition = _coreResolver.ResolveType(topLevelClass1); Assert.Equal((ITypeDefOrRef) topLevelClass1, definition, _comparer); @@ -104,7 +106,7 @@ public void ResolveTypeReferenceThenChangeDefAndResolveAgain() ITypeDefOrRef expected = new TypeReference(module.CorLibTypeFactory.CorLibScope, "System", "Object"); var reference = new TypeReference(module.CorLibTypeFactory.CorLibScope, "System", "Object"); - var definition = _fwResolver.ResolveType(reference); + var definition = _fwResolver.ResolveType(reference)!; Assert.Equal(expected, definition, _comparer); definition.Name = "String"; Assert.NotEqual(expected, _fwResolver.ResolveType(reference), _comparer); @@ -115,9 +117,9 @@ public void ResolveNestedType() { var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); - var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly), - typeof(TopLevelClass1).Namespace, typeof(TopLevelClass1).Name); - var nested1 = new TypeReference(topLevelClass1,null, typeof(TopLevelClass1.Nested1).Name); + var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly!), + typeof(TopLevelClass1).Namespace, nameof(TopLevelClass1)); + var nested1 = new TypeReference(topLevelClass1,null, nameof(TopLevelClass1.Nested1)); var definition = _coreResolver.ResolveType(nested1); @@ -129,16 +131,52 @@ public void ResolveNestedNestedType() { var module = ModuleDefinition.FromFile(typeof(TopLevelClass1).Assembly.Location); - var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly), - typeof(TopLevelClass1).Namespace, typeof(TopLevelClass1).Name); - var nested1 = new TypeReference(topLevelClass1,null, typeof(TopLevelClass1.Nested1).Name); - var nested1nested1 = new TypeReference(nested1,null, typeof(TopLevelClass1.Nested1.Nested1Nested1).Name); + var topLevelClass1 = new TypeReference(new AssemblyReference(module.Assembly!), + typeof(TopLevelClass1).Namespace, nameof(TopLevelClass1)); + var nested1 = new TypeReference(topLevelClass1,null, nameof(TopLevelClass1.Nested1)); + var nested1nested1 = new TypeReference(nested1,null, nameof(TopLevelClass1.Nested1.Nested1Nested1)); var definition = _fwResolver.ResolveType(nested1nested1); Assert.Equal((ITypeDefOrRef) nested1nested1, definition, _comparer); } + [Fact] + public void ResolveTypeWithModuleScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefModuleScope); + var reference = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 2)); + + var definition = reference.Resolve(); + + Assert.NotNull(definition); + Assert.Same(module, definition.Module); + } + + [Fact] + public void ResolveTypeWithNullScopeCurrentModule() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_CurrentModule); + var reference = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 2)); + + var definition = reference.Resolve(); + + Assert.NotNull(definition); + Assert.Same(module, definition.Module); + } + + [Fact] + public void ResolveTypeWithNullScopeExportedType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_ExportedType); + var reference = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 1)); + + var definition = reference.Resolve(); + + Assert.NotNull(definition); + Assert.Equal("mscorlib", definition.Module!.Assembly!.Name); + } + [Fact] public void ResolveConsoleWriteLineMethod() { @@ -187,13 +225,13 @@ public void ResolveExportedMemberReference() var resolver = (AssemblyResolverBase) module.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); - resolver = (AssemblyResolverBase) assembly1.ManifestModule.MetadataResolver.AssemblyResolver; + resolver = (AssemblyResolverBase) assembly1.ManifestModule!.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); // Resolve - var instructions = module.ManagedEntryPointMethod.CilMethodBody.Instructions; - Assert.NotNull(((IMethodDescriptor) instructions[0].Operand).Resolve()); + var instructions = module.ManagedEntryPointMethod!.CilMethodBody!.Instructions; + Assert.NotNull(((IMethodDescriptor) instructions[0].Operand!).Resolve()); } [Fact] @@ -208,10 +246,10 @@ public void MaliciousExportedTypeLoop() var resolver = (AssemblyResolverBase) module.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); - resolver = (AssemblyResolverBase) assembly1.ManifestModule.MetadataResolver.AssemblyResolver; + resolver = (AssemblyResolverBase) assembly1.ManifestModule!.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); - resolver = (AssemblyResolverBase) assembly2.ManifestModule.MetadataResolver.AssemblyResolver; + resolver = (AssemblyResolverBase) assembly2.ManifestModule!.MetadataResolver.AssemblyResolver; resolver.AddToCache(assembly1, assembly1); resolver.AddToCache(assembly2, assembly2); diff --git a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs index f8e171460..8d992d293 100644 --- a/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs +++ b/test/AsmResolver.DotNet.Tests/ModuleDefinitionTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using AsmResolver.DotNet.Serialized; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.TestCases.NestedClasses; using AsmResolver.IO; @@ -48,6 +49,64 @@ public void LoadFromDynamicModule() Assert.Equal("ActualLibrary.dll", module.Name); } + [Fact] + public void LoadFromFileShouldPopulateResolverSearchDirectories() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + var module = ModuleDefinition.FromFile(path); + + Assert.Contains( + Path.GetDirectoryName(path), + ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver).SearchDirectories); + } + + [Fact] + public void LoadFromBytesShouldLeaveSearchDirectoriesEmpty() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + var module = ModuleDefinition.FromBytes(File.ReadAllBytes(path)); + + Assert.DoesNotContain( + Path.GetDirectoryName(path), + ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver).SearchDirectories); + } + + [Fact] + public void LoadFromBytesWithWorkingDirectoryShouldPopulateSearchDirectories() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + var module = ModuleDefinition.FromBytes( + File.ReadAllBytes(path), + new ModuleReaderParameters(Path.GetDirectoryName(path))); + + Assert.Contains( + Path.GetDirectoryName(path), + ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver).SearchDirectories); + } + + [Fact] + public void LoadFromFileWithSameWorkingDirectoryShouldNotPopulateSearchDirectoriesTwice() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + var module = ModuleDefinition.FromFile(path, + new ModuleReaderParameters(Path.GetDirectoryName(path))); + + Assert.Equal(1, ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver) + .SearchDirectories.Count(x => x == Path.GetDirectoryName(path))); + } + + [Fact] + public void LoadFromFileWithDifferentWorkingDirectoryShouldPopulateSearchDirectoriesTwice() + { + string path = typeof(ModuleDefinitionTest).Assembly.Location; + string otherPath = @"C:\other\path"; + var module = ModuleDefinition.FromFile(path, new ModuleReaderParameters(otherPath)); + + var searchDirectories = ((AssemblyResolverBase) module.MetadataResolver.AssemblyResolver).SearchDirectories; + Assert.Contains(Path.GetDirectoryName(path), searchDirectories); + Assert.Contains(otherPath, searchDirectories); + } + [Fact] public void ReadNameTest() { diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs index e67584486..5f2f0400f 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.Designer.cs @@ -387,5 +387,26 @@ public static byte[] ArgListTest { return ((byte[])(obj)); } } + + public static byte[] TypeRefModuleScope { + get { + object obj = ResourceManager.GetObject("TypeRefModuleScope", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] TypeRefNullScope_CurrentModule { + get { + object obj = ResourceManager.GetObject("TypeRefNullScope_CurrentModule", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] TypeRefNullScope_ExportedType { + get { + object obj = ResourceManager.GetObject("TypeRefNullScope_ExportedType", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx index 8ebc736a8..5edb05e41 100644 --- a/test/AsmResolver.DotNet.Tests/Properties/Resources.resx +++ b/test/AsmResolver.DotNet.Tests/Properties/Resources.resx @@ -165,4 +165,13 @@ ..\Resources\ArgListTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\TypeRefModuleScope.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\TypeRefNullScope_CurrentModule.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\TypeRefNullScope_ExportedType.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.DotNet.Tests/Resources/TypeRefModuleScope.exe b/test/AsmResolver.DotNet.Tests/Resources/TypeRefModuleScope.exe new file mode 100644 index 000000000..e9d316359 Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/TypeRefModuleScope.exe differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_CurrentModule.exe b/test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_CurrentModule.exe new file mode 100644 index 000000000..3ade50d67 Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_CurrentModule.exe differ diff --git a/test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_ExportedType.exe b/test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_ExportedType.exe new file mode 100644 index 000000000..5bf8a8be2 Binary files /dev/null and b/test/AsmResolver.DotNet.Tests/Resources/TypeRefNullScope_ExportedType.exe differ diff --git a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs index 5c26a3518..d3afe8f78 100644 --- a/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs +++ b/test/AsmResolver.DotNet.Tests/Signatures/TypeSignatureTest.cs @@ -4,6 +4,7 @@ using AsmResolver.DotNet.Signatures.Types; using AsmResolver.DotNet.TestCases.Generics; using AsmResolver.DotNet.TestCases.Types; +using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Xunit; @@ -619,5 +620,38 @@ public void IgnorePinnedModifiers() Assert.True(type1.IsCompatibleWith(type2)); Assert.True(type2.IsCompatibleWith(type1)); } + + [Fact] + public void GetModuleOfTypeDefOrRef() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var signature = module.GetOrCreateModuleType().ToTypeSignature(); + Assert.Same(module, signature.Module); + } + + [Fact] + public void GetModuleOfTypeDefOrRefWithNullScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_CurrentModule); + var signature = module + .LookupMember(new MetadataToken(TableIndex.TypeRef, 2)) + .ToTypeSignature(); + + Assert.Null(signature.Scope); + Assert.Same(module, signature.Module); + } + + [Fact] + public void GetModuleOfSpecificationTypeWithNullScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_CurrentModule); + var signature = module + .LookupMember(new MetadataToken(TableIndex.TypeRef, 2)) + .ToTypeSignature() + .MakeSzArrayType(); + + Assert.Null(signature.Scope); + Assert.Same(module, signature.Module); + } } } diff --git a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs index a0a5ae2e5..002801db9 100644 --- a/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs +++ b/test/AsmResolver.DotNet.Tests/TypeReferenceTest.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AsmResolver.DotNet.Signatures; using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE.DotNet.Metadata.Tables; @@ -15,15 +16,150 @@ public class TypeReferenceTest public void ReadAssemblyRefScope() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); - var typeRef = (TypeReference) module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); - Assert.Equal("mscorlib", typeRef.Scope.Name); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal("mscorlib", scope.Name); + } + + [Fact] + public void WriteAssemblyRefScope() + { + var module = new ModuleDefinition("SomeModule"); + module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( + "SomeField", + FieldAttributes.Static, + new TypeDefOrRefSignature(new TypeReference( + new AssemblyReference("SomeAssembly", new Version(1, 0, 0, 0)), + "SomeNamespace", + "SomeName") + ).ImportWith(module.DefaultImporter) + )); + + var image = module.ToPEImage(); + + var newModule = ModuleDefinition.FromImage(image); + var typeRef = newModule.GetOrCreateModuleType().Fields.First().Signature!.FieldType.GetUnderlyingTypeDefOrRef()!; + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal("SomeAssembly", scope.Name); + } + + [Fact] + public void ReadTypeRefScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 4)); + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal("DebuggableAttribute", scope.Name); + } + + [Fact] + public void WriteTypeRefScope() + { + var module = new ModuleDefinition("SomeModule"); + module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( + "SomeField", + FieldAttributes.Static, + new TypeDefOrRefSignature(new TypeReference( + new TypeReference( + new AssemblyReference("SomeAssembly", new Version(1, 0, 0, 0)), + "SomeNamespace", + "SomeName"), + null, + "SomeNestedType" + )).ImportWith(module.DefaultImporter) + )); + + var image = module.ToPEImage(); + + var newModule = ModuleDefinition.FromImage(image); + var typeRef = newModule.GetOrCreateModuleType().Fields.First().Signature!.FieldType.GetUnderlyingTypeDefOrRef()!; + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal("SomeName", scope.Name); + } + + [Fact] + public void ReadModuleScope() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefModuleScope); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 2)); + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Same(module, scope); + } + + [Fact] + public void WriteModuleScope() + { + var module = new ModuleDefinition("SomeModule"); + module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( + "SomeField", + FieldAttributes.Static, + new TypeDefOrRefSignature(new TypeReference( + module, + "SomeNamepace", + "SomeName") + ).ImportWith(module.DefaultImporter) + )); + + var image = module.ToPEImage(); + + var newModule = ModuleDefinition.FromImage(image); + var typeRef = newModule.GetOrCreateModuleType().Fields.First().Signature!.FieldType.GetUnderlyingTypeDefOrRef()!; + + var scope = Assert.IsAssignableFrom(typeRef.Scope); + Assert.Equal(module.Name, scope.Name); + } + + [Fact] + public void WriteNullScope() + { + var module = new ModuleDefinition("SomeModule"); + module.GetOrCreateModuleType().Fields.Add(new FieldDefinition( + "SomeField", + FieldAttributes.Static, + new TypeDefOrRefSignature(new TypeReference( + null, + "SomeNamespace", + "SomeName") + ).ImportWith(module.DefaultImporter) + )); + + var image = module.ToPEImage(); + + var newModule = ModuleDefinition.FromImage(image); + var typeRef = newModule.GetOrCreateModuleType().Fields.First().Signature!.FieldType.GetUnderlyingTypeDefOrRef()!; + + Assert.Null(typeRef.Scope); + } + + [Fact] + public void ReadNullScopeCurrentModule() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_CurrentModule); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 2)); + + Assert.Null(typeRef.Scope); + } + + [Fact] + public void ReadNullScopeExportedType() + { + var module = ModuleDefinition.FromBytes(Properties.Resources.TypeRefNullScope_ExportedType); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 1)); + + Assert.Null(typeRef.Scope); } [Fact] public void ReadName() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); - var typeRef = (TypeReference) module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + Assert.Equal("Console", typeRef.Name); } @@ -31,7 +167,8 @@ public void ReadName() public void ReadNamespace() { var module = ModuleDefinition.FromBytes(Properties.Resources.HelloWorld); - var typeRef = (TypeReference) module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + var typeRef = module.LookupMember(new MetadataToken(TableIndex.TypeRef, 13)); + Assert.Equal("System", typeRef.Namespace); } @@ -41,6 +178,7 @@ public void CorLibTypeToTypeSignatureShouldReturnCorLibTypeSignature() var module = new ModuleDefinition("SomeModule"); var reference = new TypeReference(module, module.CorLibTypeFactory.CorLibScope, "System", "Object"); var signature = Assert.IsAssignableFrom(reference.ToTypeSignature()); + Assert.Equal(ElementType.Object, signature.ElementType); } @@ -50,6 +188,7 @@ public void NonCorLibTypeToTypeSignatureShouldReturnTypeDefOrRef() var module = new ModuleDefinition("SomeModule"); var reference = new TypeReference(module, module.CorLibTypeFactory.CorLibScope, "System", "Array"); var signature = Assert.IsAssignableFrom(reference.ToTypeSignature()); + Assert.Equal(signature.Type, reference, Comparer); } } diff --git a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj index a540f1edc..5da60fb15 100644 --- a/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj +++ b/test/AsmResolver.PE.File.Tests/AsmResolver.PE.File.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false @@ -9,9 +9,10 @@ - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.File.Tests/PEFileTest.cs b/test/AsmResolver.PE.File.Tests/PEFileTest.cs index 7a1c47463..b1410df05 100644 --- a/test/AsmResolver.PE.File.Tests/PEFileTest.cs +++ b/test/AsmResolver.PE.File.Tests/PEFileTest.cs @@ -185,6 +185,16 @@ public void ReadEofData() Assert.Equal(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz"), data); } + [Fact] + public void ReadEofDataFromFileOffset() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_EOF); + Assert.NotNull(file.EofData); + Assert.True(file.TryCreateReaderAtFileOffset((uint) file.EofData.Offset, out var reader)); + byte[] data = reader.ReadToEnd(); + Assert.Equal(Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwxyz"), data); + } + [Fact] public void AddNewEofData() { @@ -240,5 +250,60 @@ public void RemoveExistingEofData() var newFile = PEFile.FromBytes(newFileBytes); Assert.Null(newFile.EofData); } + + [Fact] + public void ReadSections() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld); + Assert.Equal(new[] {".text", ".rsrc", ".reloc"}, file.Sections.Select(x => x.Name)); + } + + [Fact] + public void ReadInvalidSectionName() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_InvalidSectionName); + Assert.Equal(new[] {".text", ".rsrc", ".reloc"}, file.Sections.Select(x => x.Name)); + } + + [Fact] + public void ReadExtraSectionData() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_ExtraSectionData); + var reader = Assert.IsAssignableFrom(file.ExtraSectionData).CreateReader(); + Assert.Equal("Hello, world", reader.ReadAsciiString()); + } + + [Fact] + public void PersistExtraSectionData() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld); + file.ExtraSectionData = new DataSegment(Encoding.ASCII.GetBytes("Hello, mars")); + + using var stream = new MemoryStream(); + file.Write(stream); + + var newFile = PEFile.FromBytes(stream.ToArray()); + var reader = Assert.IsAssignableFrom(newFile.ExtraSectionData).CreateReader(); + Assert.Equal("Hello, mars", reader.ReadAsciiString()); + } + + [Fact] + public void PersistLargeExtraSectionData() + { + byte[] data = Enumerable.Range(0, 255).Select(x => (byte) x).ToArray(); + + var file = PEFile.FromBytes(Properties.Resources.HelloWorld); + file.ExtraSectionData = new DataSegment(data); + + using var stream = new MemoryStream(); + file.Write(stream); + + var newFile = PEFile.FromBytes(stream.ToArray()); + var reader = Assert.IsAssignableFrom(newFile.ExtraSectionData).CreateReader(); + + byte[] actualBytes = new byte[data.Length]; + Assert.Equal(data.Length, reader.ReadBytes(actualBytes, 0, actualBytes.Length)); + Assert.Equal(data, actualBytes); + } } } diff --git a/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs index f2f817fd5..7923bfd05 100644 --- a/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.File.Tests/Properties/Resources.Designer.cs @@ -90,6 +90,26 @@ public static byte[] HelloWorld_EOF { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] HelloWorld_InvalidSectionName { + get { + object obj = ResourceManager.GetObject("HelloWorld_InvalidSectionName", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] HelloWorld_ExtraSectionData { + get { + object obj = ResourceManager.GetObject("HelloWorld_ExtraSectionData", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/test/AsmResolver.PE.File.Tests/Properties/Resources.resx b/test/AsmResolver.PE.File.Tests/Properties/Resources.resx index c33ee0174..aaeaece36 100644 --- a/test/AsmResolver.PE.File.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.File.Tests/Properties/Resources.resx @@ -127,7 +127,13 @@ ..\Resources\HelloWorld.EOF.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\NativeMemoryDemos.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + ..\Resources\HelloWorld.InvalidSectionName.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\HelloWorld.ExtraSectionData.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\NativeMemoryDemos.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.ExtraSectionData.exe b/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.ExtraSectionData.exe new file mode 100644 index 000000000..1bf9c8360 Binary files /dev/null and b/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.ExtraSectionData.exe differ diff --git a/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.InvalidSectionName.exe b/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.InvalidSectionName.exe new file mode 100644 index 000000000..8947b3b63 Binary files /dev/null and b/test/AsmResolver.PE.File.Tests/Resources/HelloWorld.InvalidSectionName.exe differ diff --git a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj index d53d560b6..ba76c1c56 100644 --- a/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj +++ b/test/AsmResolver.PE.Tests/AsmResolver.PE.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false true enable @@ -16,10 +16,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs b/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs new file mode 100644 index 000000000..a60efde68 --- /dev/null +++ b/test/AsmResolver.PE.Tests/Certificates/CertificateCollectionTest.cs @@ -0,0 +1,40 @@ +using System.IO; +using AsmResolver.PE.Certificates; +using AsmResolver.PE.File; +using Xunit; + +namespace AsmResolver.PE.Tests.Certificates +{ + public class CertificateCollectionTest + { + [Fact] + public void ReadHeader() + { + var image = PEImage.FromBytes(Properties.Resources.HelloWorld_Signed); + var certificate = Assert.Single(image.Certificates); + Assert.Equal(CertificateRevision.Revision_v2_0, certificate.Revision); + Assert.Equal(CertificateType.PkcsSignedData, certificate.Type); + Assert.Equal(0x580u, certificate.CreateContentReader().Length); + } + + [Fact] + public void RebuildExistingSignature() + { + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_Signed); + + var image = PEImage.FromFile(file); + var certificate = Assert.Single(image.Certificates); + file.EofData = image.Certificates; + + using var stream = new MemoryStream(); + file.Write(stream); + + var newImage = PEImage.FromBytes(stream.ToArray()); + var newCertificate = Assert.Single(newImage.Certificates); + Assert.Equal(certificate.Revision, newCertificate.Revision); + Assert.Equal(certificate.Type, newCertificate.Type); + Assert.Equal(certificate.CreateContentReader().ReadToEnd(), newCertificate.CreateContentReader().ReadToEnd()); + Assert.Equal(certificate.WriteIntoArray(), newCertificate.WriteIntoArray()); + } + } +} diff --git a/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs b/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs index d9678ea24..1d2ccda32 100644 --- a/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs +++ b/test/AsmResolver.PE.Tests/DotNet/Builder/ManagedPEFileBuilderTest.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.IO; using AsmResolver.PE.Code; using AsmResolver.PE.DotNet; using AsmResolver.PE.DotNet.Builder; @@ -21,33 +22,33 @@ public ManagedPEFileBuilderTest(TemporaryDirectoryFixture fixture) { _fixture = fixture; } - + [Fact] public void HelloWorldRebuild32BitNoChange() { // Read image var image = PEImage.FromBytes(Properties.Resources.HelloWorld); - + // Rebuild var builder = new ManagedPEFileBuilder(); var peFile = builder.CreateFile(image); - + // Verify _fixture .GetRunner() .RebuildAndRun(peFile, "HelloWorld", "Hello World!" + Environment.NewLine); } - + [Fact] public void HelloWorldRebuild64BitNoChange() { // Read image var image = PEImage.FromBytes(Properties.Resources.HelloWorld_X64); - + // Rebuild var builder = new ManagedPEFileBuilder(); var peFile = builder.CreateFile(image); - + // Verify _fixture .GetRunner() @@ -59,15 +60,15 @@ public void HelloWorld32BitTo64Bit() { // Read image var image = PEImage.FromBytes(Properties.Resources.HelloWorld); - + // Change machine type and pe kind to 64-bit image.MachineType = MachineType.Amd64; image.PEKind = OptionalHeaderMagic.PE32Plus; - + // Rebuild var builder = new ManagedPEFileBuilder(); var peFile = builder.CreateFile(image); - + // Verify _fixture .GetRunner() @@ -79,20 +80,40 @@ public void HelloWorld64BitTo32Bit() { // Read image var image = PEImage.FromBytes(Properties.Resources.HelloWorld_X64); - + // Change machine type and pe kind to 32-bit image.MachineType = MachineType.I386; image.PEKind = OptionalHeaderMagic.PE32; - + // Rebuild var builder = new ManagedPEFileBuilder(); var peFile = builder.CreateFile(image); - + // Verify _fixture .GetRunner() .RebuildAndRun(peFile, "HelloWorld", "Hello World!" + Environment.NewLine); } - + + [Fact] + public void UpdateFieldRvaRowsUnchanged() + { + var image = PEImage.FromBytes(Properties.Resources.FieldRvaTest); + + using var stream = new MemoryStream(); + var file = new ManagedPEFileBuilder(EmptyErrorListener.Instance).CreateFile(image); + file.Write(stream); + + var newImage = PEImage.FromBytes(stream.ToArray()); + var table = newImage.DotNetDirectory!.Metadata! + .GetStream() + .GetTable(); + + byte[] data = new byte[16]; + table[0].Data.CreateReader().ReadBytes(data, 0, data.Length); + Assert.Equal(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, data); + Assert.Equal(0x12345678u, table[1].Data.Rva); + } + } -} \ No newline at end of file +} diff --git a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs index 0b63631d2..3622d8b5f 100644 --- a/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs +++ b/test/AsmResolver.PE.Tests/Exports/ExportDirectoryTest.cs @@ -4,12 +4,23 @@ using AsmResolver.IO; using AsmResolver.PE.DotNet.Builder; using AsmResolver.PE.Exports; +using AsmResolver.PE.Exports.Builder; +using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; +using AsmResolver.Tests.Runners; using Xunit; namespace AsmResolver.PE.Tests.Exports { - public class ExportDirectoryTest + public class ExportDirectoryTest : IClassFixture { + private readonly TemporaryDirectoryFixture _fixture; + + public ExportDirectoryTest(TemporaryDirectoryFixture fixture) + { + _fixture = fixture; + } + [Fact] public void ReadName() { @@ -127,7 +138,7 @@ public void PersistentExportedSymbol() var newImage = RebuildAndReloadManagedPE(image); // Verify. - Assert.Equal(1, newImage.Exports!.Entries.Count); + Assert.Single(newImage.Exports!.Entries); var newExportedSymbol = newImage.Exports.Entries[0]; Assert.Equal(exportedSymbol.Name, newExportedSymbol.Name); Assert.Equal(exportedSymbol.Ordinal, newExportedSymbol.Ordinal); @@ -149,7 +160,7 @@ public void PersistentExportedSymbolByOrdinal() var newImage = RebuildAndReloadManagedPE(image); // Verify. - Assert.Equal(1, newImage.Exports!.Entries.Count); + Assert.Single(newImage.Exports!.Entries); var newExportedSymbol = newImage.Exports.Entries[0]; Assert.True(exportedSymbol.IsByOrdinal); Assert.Equal(exportedSymbol.Ordinal, newExportedSymbol.Ordinal); @@ -207,5 +218,71 @@ public void PersistentExportedSymbolMany() Assert.Equal(unnamedSymbol3.Ordinal, newSymbol.Ordinal); Assert.Equal(unnamedSymbol3.Address.Rva, newSymbol.Address.Rva); } + + [Fact] + public void ReadForwarderSymbol() + { + var exports = PEImage.FromBytes(Properties.Resources.ForwarderDlls_ProxyDll).Exports!; + + var bar = exports.Entries.First(x => x.Name == "Bar"); + var baz = exports.Entries.First(x => x.Name == "Baz"); + + Assert.True(bar.IsForwarder); + Assert.Equal("ActualDll.Foo", bar.ForwarderName); + Assert.False(baz.IsForwarder); + } + + [Fact] + public void RebuildForwarderSymbol() + { + var file = PEFile.FromBytes(Properties.Resources.ForwarderDlls_ProxyDll); + + // Update a forwarder name. + var exports = PEImage.FromFile(file).Exports!; + var bar = exports.Entries.First(x => x.Name == "Bar"); + bar.ForwarderName = "ActualDll.Bar"; + + // Rebuild export directory. + var buffer = new ExportDirectoryBuffer(); + buffer.AddDirectory(exports); + + var section = new PESection( + ".export", + SectionFlags.MemoryRead | SectionFlags.ContentInitializedData, + buffer); + + // Rebuild. + file.Sections.Add(section); + file.UpdateHeaders(); + file.OptionalHeader.SetDataDirectory( + DataDirectoryIndex.ExportDirectory, + new DataDirectory(section.Rva, section.GetPhysicalSize())); + + using var stream = new MemoryStream(); + file.Write(stream); + + // Verify new forwarder symbol is present. + var newExports = PEImage.FromBytes(stream.ToArray()).Exports!; + var newBar = newExports.Entries.First(x => x.Name == "Bar"); + Assert.Equal("ActualDll.Bar", newBar.ForwarderName); + + // Try running it. + var runner = _fixture.GetRunner(); + string basePath = runner.GetTestDirectory(nameof(ExportDirectoryTest), nameof(RebuildForwarderSymbol)); + string exePath = Path.Combine(basePath, "ForwarderTest.exe"); + + System.IO.File.WriteAllBytes( + Path.Combine(basePath, "ActualDll.dll"), + Properties.Resources.ForwarderDlls_ActualDll); + System.IO.File.WriteAllBytes( + exePath, + Properties.Resources.ForwarderDlls_ForwarderTest); + System.IO.File.WriteAllBytes( + Path.Combine(basePath, "ProxyDll.dll"), + stream.ToArray()); + + string output = runner.RunAndCaptureOutput(exePath); + Assert.Equal("ActualDLL::Bar\r\nProxyDll::Baz\r\nHello World!\r\n", output); + } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs index 61ac7d57a..1ab8eda78 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Tests/Properties/Resources.Designer.cs @@ -157,6 +157,13 @@ public static byte[] HelloWorld_UPX { } } + public static byte[] HelloWorld_Signed { + get { + object obj = ResourceManager.GetObject("HelloWorld_Signed", resourceCulture); + return ((byte[])(obj)); + } + } + public static byte[] HelloWorldPortablePdb { get { object obj = ResourceManager.GetObject("HelloWorldPortablePdb", resourceCulture); @@ -240,5 +247,33 @@ public static byte[] LargeIndicesPdb { return ((byte[])(obj)); } } + + public static byte[] ForwarderDlls_ActualDll { + get { + object obj = ResourceManager.GetObject("ForwarderDlls_ActualDll", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] ForwarderDlls_ProxyDll { + get { + object obj = ResourceManager.GetObject("ForwarderDlls_ProxyDll", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] ForwarderDlls_ForwarderTest { + get { + object obj = ResourceManager.GetObject("ForwarderDlls_ForwarderTest", resourceCulture); + return ((byte[])(obj)); + } + } + + public static byte[] FieldRvaTest { + get { + object obj = ResourceManager.GetObject("FieldRvaTest", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.PE.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Tests/Properties/Resources.resx index 5ffcc282d..987d82cad 100644 --- a/test/AsmResolver.PE.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Tests/Properties/Resources.resx @@ -66,6 +66,9 @@ ..\Resources\HelloWorld.upx.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\HelloWorld.signed.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\HelloWorld.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -102,4 +105,16 @@ ..\Resources\LargeIndicesPdb.pdb;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\ActualDll.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\ProxyDll.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\ForwarderTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\FieldRvaTest.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + diff --git a/test/AsmResolver.PE.Tests/Resources/ActualDll.dll b/test/AsmResolver.PE.Tests/Resources/ActualDll.dll new file mode 100644 index 000000000..a56d0a83d Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/ActualDll.dll differ diff --git a/test/AsmResolver.PE.Tests/Resources/FieldRvaTest.exe b/test/AsmResolver.PE.Tests/Resources/FieldRvaTest.exe new file mode 100644 index 000000000..d3b4ff03e Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/FieldRvaTest.exe differ diff --git a/test/AsmResolver.PE.Tests/Resources/ForwarderTest.exe b/test/AsmResolver.PE.Tests/Resources/ForwarderTest.exe new file mode 100644 index 000000000..253655436 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/ForwarderTest.exe differ diff --git a/test/AsmResolver.PE.Tests/Resources/HelloWorld.signed.exe b/test/AsmResolver.PE.Tests/Resources/HelloWorld.signed.exe new file mode 100644 index 000000000..48123c2e6 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/HelloWorld.signed.exe differ diff --git a/test/AsmResolver.PE.Tests/Resources/ProxyDll.dll b/test/AsmResolver.PE.Tests/Resources/ProxyDll.dll new file mode 100644 index 000000000..7afa3ba44 Binary files /dev/null and b/test/AsmResolver.PE.Tests/Resources/ProxyDll.dll differ diff --git a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj index 9b7323361..76a8733e6 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj +++ b/test/AsmResolver.PE.Win32Resources.Tests/AsmResolver.PE.Win32Resources.Tests.csproj @@ -1,20 +1,21 @@ - netcoreapp3.1 + net6.0 false AsmResolver.PE.Win32Resources.Tests warnings - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -23,6 +24,7 @@ + @@ -40,8 +42,4 @@ - - - - diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.Designer.cs b/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.Designer.cs index 866341677..3c3cd2021 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.Designer.cs +++ b/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.Designer.cs @@ -51,5 +51,12 @@ internal static byte[] HelloWorld { return ((byte[])(obj)); } } + + internal static byte[] HelloWorld_PaddedVersionInfo { + get { + object obj = ResourceManager.GetObject("HelloWorld_PaddedVersionInfo", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.resx b/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.resx index 4a49ff593..e6b342ebd 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.resx +++ b/test/AsmResolver.PE.Win32Resources.Tests/Properties/Resources.resx @@ -3,7 +3,7 @@ - + @@ -21,4 +21,7 @@ ..\Resources\HelloWorld.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - \ No newline at end of file + + ..\Resources\HelloWorld.PaddedVersionInfo.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Resources/HelloWorld.PaddedVersionInfo.exe b/test/AsmResolver.PE.Win32Resources.Tests/Resources/HelloWorld.PaddedVersionInfo.exe new file mode 100644 index 000000000..4391dfd46 Binary files /dev/null and b/test/AsmResolver.PE.Win32Resources.Tests/Resources/HelloWorld.PaddedVersionInfo.exe differ diff --git a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs index a9699d22b..3d52a5ad2 100644 --- a/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs +++ b/test/AsmResolver.PE.Win32Resources.Tests/Version/VersionInfoResourceTest.cs @@ -2,13 +2,24 @@ using System.IO; using AsmResolver.IO; using AsmResolver.PE.DotNet.Builder; +using AsmResolver.PE.File; +using AsmResolver.PE.File.Headers; +using AsmResolver.PE.Win32Resources.Builder; using AsmResolver.PE.Win32Resources.Version; +using AsmResolver.Tests.Runners; using Xunit; namespace AsmResolver.PE.Win32Resources.Tests.Version { - public class VersionInfoSegmentTest + public class VersionInfoResourceTest : IClassFixture { + private readonly TemporaryDirectoryFixture _fixture; + + public VersionInfoResourceTest(TemporaryDirectoryFixture fixture) + { + _fixture = fixture; + } + [Fact] public void ReadFixedVersion() { @@ -192,5 +203,42 @@ public void PersistentVersionResource() Assert.Equal(versionInfo.FixedVersionInfo.ProductVersion, newVersionInfo.FixedVersionInfo.ProductVersion); } + [Fact] + public void VersionInfoAlignment() + { + // https://github.com/Washi1337/AsmResolver/issues/202 + + // Open dummy + var file = PEFile.FromBytes(Properties.Resources.HelloWorld_PaddedVersionInfo); + var image = PEImage.FromFile(file); + + // Update version info. + var versionInfo = VersionInfoResource.FromDirectory(image.Resources!)!; + var info = versionInfo.GetChild(StringFileInfo.StringFileInfoKey); + info.Tables[0][StringTable.FileDescriptionKey] = "This is a test application"; + versionInfo.WriteToDirectory(image.Resources!); + + // Replace section. + var resourceBuffer = new ResourceDirectoryBuffer(); + resourceBuffer.AddDirectory(image.Resources!); + + var section = file.GetSectionContainingRva(file.OptionalHeader.GetDataDirectory(DataDirectoryIndex.ResourceDirectory).VirtualAddress); + section.Contents = resourceBuffer; + + file.UpdateHeaders(); + file.OptionalHeader.SetDataDirectory(DataDirectoryIndex.ResourceDirectory, + new DataDirectory(resourceBuffer.Rva, resourceBuffer.GetPhysicalSize())); + + // Rebuild + using var stream = new MemoryStream(); + file.Write(stream); + + // Reopen and verify. + var newImage = PEImage.FromBytes(stream.ToArray()); + var newVersionInfo = VersionInfoResource.FromDirectory(newImage.Resources!)!; + var newInfo = newVersionInfo.GetChild(StringFileInfo.StringFileInfoKey); + Assert.Equal("This is a test application", newInfo.Tables[0][StringTable.FileDescriptionKey]); + Assert.Equal(versionInfo.Lcid, newVersionInfo.Lcid); + } } } diff --git a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj index 74743b9c8..2d4002a19 100644 --- a/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj +++ b/test/AsmResolver.Symbols.Pdb.Tests/AsmResolver.Symbols.Pdb.Tests.csproj @@ -8,13 +8,14 @@ - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/AsmResolver.Tests/AsmResolver.Tests.csproj b/test/AsmResolver.Tests/AsmResolver.Tests.csproj index b31d8874c..5d726da8c 100644 --- a/test/AsmResolver.Tests/AsmResolver.Tests.csproj +++ b/test/AsmResolver.Tests/AsmResolver.Tests.csproj @@ -1,16 +1,17 @@ - netcoreapp3.1 + net6.0 false true enable - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/AsmResolver.Tests/Runners/NativePERunner.cs b/test/AsmResolver.Tests/Runners/NativePERunner.cs index 0edea6cd4..e9e4e8873 100644 --- a/test/AsmResolver.Tests/Runners/NativePERunner.cs +++ b/test/AsmResolver.Tests/Runners/NativePERunner.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics; namespace AsmResolver.Tests.Runners @@ -20,6 +21,16 @@ protected override ProcessStartInfo GetStartInfo(string filePath, string[]? argu UseShellExecute = false }; + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + info.FileName = filePath; + } + else + { + info.FileName = "wine"; + info.ArgumentList.Add(filePath); + } + if (arguments is not null) { foreach (string argument in arguments) diff --git a/test/AsmResolver.Tests/Runners/PERunner.cs b/test/AsmResolver.Tests/Runners/PERunner.cs index c2e4ff405..ee8bc60fa 100644 --- a/test/AsmResolver.Tests/Runners/PERunner.cs +++ b/test/AsmResolver.Tests/Runners/PERunner.cs @@ -34,7 +34,7 @@ public void RebuildAndRun(PEFile peFile, string fileName, string expectedOutput, Assert.Equal(expectedOutput, actualOutput); } - protected string GetTestDirectory(string testClass, string testName) + public string GetTestDirectory(string testClass, string testName) { string path = Path.Combine(BasePath, testClass, testName); if (!Directory.Exists(path)) diff --git a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj index da037df75..b4ceea1e3 100644 --- a/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj +++ b/test/TestBinaries/DotNet/AsmResolver.DotNet.TestCases.Resources/AsmResolver.DotNet.TestCases.Resources.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj index 329c82d3e..53d2dbb55 100644 --- a/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj +++ b/test/TestBinaries/DotNet/HelloWorld/HelloWorld.csproj @@ -9,6 +9,10 @@ AnyCPU + + + none + diff --git a/test/TestBinaries/DotNet/HelloWorld/Program.cs b/test/TestBinaries/DotNet/HelloWorld/Program.cs index 575ec4cc4..6159d9786 100644 --- a/test/TestBinaries/DotNet/HelloWorld/Program.cs +++ b/test/TestBinaries/DotNet/HelloWorld/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace HelloWorld {