Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Field RVA reader Improvements #430

Merged
merged 5 commits into from
May 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions docs/dotnet/advanced-module-reading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ These parameters can then be passed on to any of the ``ModuleDefinition.FromXXX`
PE image reading parameters
---------------------------

.NET modules are stored in a normal PE file. To customize the way AsmResolver reads the underlying PE image before it is being interpreted as a .NET image, ``ModuleReaderParameters`` provides a ``PEReaderParameters`` property that can be modified or replaced completely.
.NET modules are stored in a normal PE file. To customize the way AsmResolver reads the underlying PE image before it is being interpreted as a .NET image, ``ModuleReaderParameters`` provides a ``PEReaderParameters`` property that can be modified or replaced completely.

.. code-block:: csharp

parameters.PEReaderParameters = new PEReaderParameters
parameters.PEReaderParameters = new PEReaderParameters
{
...
};
Expand All @@ -34,7 +34,7 @@ For example, this can be in particular useful if you want to let AsmResolver ign

parameters.PEReaderParameters.ErrorListener = EmptyErrorListener.Instance;


Alternatively, this property can also be set through the constructor of the ``ModuleReaderParameters`` class directly:

.. code-block:: csharp
Expand All @@ -53,7 +53,7 @@ Modules often depend on other assemblies. These assemblies often are placed in t

parameters.WorkingDirectory = @"C:\Path\To\Different\Folder";


Alternatively, this property can also be set through the constructor of the ``ModuleReaderParameters`` class directly:

.. code-block:: csharp
Expand Down Expand Up @@ -83,10 +83,10 @@ To let the reader use this implementation of the ``INetModuleResolver``, set the
parameters.NetModuleResolver = new CustomNetModuleResolver();


Custom method body readers
Custom method body readers
--------------------------

Some .NET obfuscators store the implementation of method definitions in an encrypted form, use native method bodies, or use a custom format that is interpreted at runtime by the means of JIT hooking. To change the way of how method bodies are being read, it is possible to provide a custom implementation of the ``IMethodBodyReader`` interface, or extend the default implementation.
Some .NET obfuscators store the implementation of method definitions in an encrypted form, use native method bodies, or use a custom format that is interpreted at runtime by the means of JIT hooking. To change the way of how method bodies are being read, it is possible to provide a custom implementation of the ``IMethodBodyReader`` interface, or extend the default implementation.

Below an example of how to add support for reading simple x86 method bodies:

Expand All @@ -95,11 +95,11 @@ Below an example of how to add support for reading simple x86 method bodies:
public class CustomMethodBodyReader : DefaultMethodBodyReader
{
public override MethodBody ReadMethodBody(
ModuleReaderContext context,
MethodDefinition owner,
ModuleReaderContext context,
MethodDefinition owner,
in MethodDefinitionRow row)
{
if (owner.IsNative && row.Body.CanRead)
if (owner.IsNative && row.Body.CanRead)
{
// Create raw binary reader if method is native.
var reader = row.Body.CreateReader();
Expand All @@ -109,7 +109,7 @@ Below an example of how to add support for reading simple x86 method bodies:
// a very accurate heuristic for finding the boundaries of native
// method bodies.

var code = reader.ReadBytesUntil(0xC3);
var code = reader.ReadBytesUntil(0xC3);

// Create native method body.
return new NativeMethodBody(owner, code);
Expand All @@ -128,7 +128,7 @@ To let the reader use this implementation of the ``IMethodBodyReader``, set the
parameters.MethodBodyReader = new CustomMethodBodyReader();


Custom Field RVA reading
Custom Field RVA reading
------------------------

By default, the field RVA data storing the initial binary value of a field is interpreted as raw byte blobs, and are turned into instances of the ``DataSegment`` class. To adjust this behaviour, it is possible to provide a custom implementation of the ``IFieldRvaDataReader`` interface.
Expand All @@ -139,8 +139,9 @@ By default, the field RVA data storing the initial binary value of a field is in
public class CustomFieldRvaDataReader : FieldRvaDataReader
{
public override ISegment ResolveFieldData(
IErrorListener listener,
IMetadata metadata,
IErrorListener listener,
Platform platform,
IDotNetDirectory directory,
in FieldRvaRow fieldRvaRow)
{
// ...
Expand Down
24 changes: 24 additions & 0 deletions src/AsmResolver.DotNet/ModuleDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using AsmResolver.PE.DotNet.Metadata.Tables;
using AsmResolver.PE.File;
using AsmResolver.PE.File.Headers;
using AsmResolver.PE.Platforms;
using AsmResolver.PE.Win32Resources;

namespace AsmResolver.DotNet
Expand Down Expand Up @@ -789,6 +790,29 @@ public ReferenceImporter DefaultImporter
}
}

/// <summary>
/// Determines whether the module is loaded as a 32-bit process.
/// </summary>
/// <returns>
/// <c>true</c> if the module is loaded as a 32-bit process, <c>false</c> if it is loaded as a 64-bit process.
/// </returns>
public bool IsLoadedAs32Bit() => IsLoadedAs32Bit(false, true);

/// <summary>
/// Determines whether the module is loaded as a 32-bit process.
/// </summary>
/// <param name="assume32BitSystem"><c>true</c> if a 32-bit system should be assumed.</param>
/// <param name="canLoadAs32Bit"><c>true</c> if the application can be loaded as a 32-bit process.</param>
/// <returns>
/// <c>true</c> if the module is loaded as a 32-bit process, <c>false</c> if it is loaded as a 64-bit process.
/// </returns>
public bool IsLoadedAs32Bit(bool assume32BitSystem, bool canLoadAs32Bit)
{
// Assume 32-bit if platform is unknown.
return Platform.TryGet(MachineType, out var platform)
&& Attributes.IsLoadedAs32Bit(platform, assume32BitSystem, canLoadAs32Bit);
}

/// <summary>
/// Looks up a member by its metadata token.
/// </summary>
Expand Down
16 changes: 13 additions & 3 deletions src/AsmResolver.DotNet/Serialized/SerializedFieldDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using AsmResolver.PE.DotNet.Metadata.Strings;
using AsmResolver.PE.DotNet.Metadata.Tables;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
using AsmResolver.PE.Platforms;

namespace AsmResolver.DotNet.Serialized
{
Expand Down Expand Up @@ -84,15 +85,24 @@ public SerializedFieldDefinition(ModuleReaderContext context, MetadataToken toke
protected override ISegment? GetFieldRva()
{
var module = _context.ParentModule;
if (!Platform.TryGet(module.MachineType, out var platform))
return null;

uint rid = module.GetFieldRvaRid(MetadataToken);
bool result = _context.TablesStream
.GetTable<FieldRvaRow>(TableIndex.FieldRva)
.TryGetByRid(rid, out var fieldRvaRow);

return result
? _context.Parameters.FieldRvaDataReader.ResolveFieldData(_context, _context.Metadata, fieldRvaRow)
: null;
if (result)
{
return _context.Parameters.FieldRvaDataReader.ResolveFieldData(
_context,
platform,
_context.ParentModule.DotNetDirectory,
fieldRvaRow);
}

return null;
}

/// <inheritdoc />
Expand Down
4 changes: 3 additions & 1 deletion src/AsmResolver.PE.File/PESection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,9 @@ public ISegment? Contents
/// Gets a value indicating whether the section is readable using a binary stream reader.
/// </summary>
[MemberNotNullWhen(true, nameof(Contents))]
public bool IsReadable => Contents is IReadableSegment;
public bool IsReadable => Contents is VirtualSegment segment
? segment.IsReadable
: Contents is IReadableSegment;

/// <inheritdoc />
public ulong Offset => Contents?.Offset ?? 0;
Expand Down
7 changes: 6 additions & 1 deletion src/AsmResolver.PE.File/PESegmentReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@ public uint Rva
}

/// <inheritdoc />
public bool CanRead => _peFile.TryGetSectionContainingRva(Rva, out _);
public bool CanRead => _peFile.TryGetSectionContainingRva(Rva, out var section) && section.IsReadable;

/// <inheritdoc />
public bool IsBounded => false;

/// <summary>
/// Gets a value indicating whether the reference points to a valid section within the PE file.
/// </summary>
public bool IsValidAddress => _peFile.TryGetSectionContainingRva(Rva, out _);

/// <inheritdoc />
public BinaryStreamReader CreateReader() => _peFile.CreateReaderAtRva(Rva);

Expand Down
17 changes: 10 additions & 7 deletions src/AsmResolver.PE/DotNet/Builder/ManagedPEFileBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using AsmResolver.PE.Builder;
using AsmResolver.PE.Code;
using AsmResolver.PE.Debug;
using AsmResolver.PE.Debug.Builder;
using AsmResolver.PE.DotNet.Cil;
using AsmResolver.PE.DotNet.Metadata;
Expand Down Expand Up @@ -495,10 +493,10 @@ private static void AddMethodBodiesToTable(MethodBodyTableBuffer table, TablesSt

private static void AddFieldRvasToTable(ManagedPEBuilderContext context)
{
var metadata = context.DotNetSegment.DotNetDirectory.Metadata;
var fieldRvaTable = metadata
!.GetStream<TablesStream>()
!.GetTable<FieldRvaRow>(TableIndex.FieldRva);
var directory = context.DotNetSegment.DotNetDirectory;
var fieldRvaTable = directory.Metadata!
.GetStream<TablesStream>()
.GetTable<FieldRvaRow>(TableIndex.FieldRva);

if (fieldRvaTable.Count == 0)
return;
Expand All @@ -508,7 +506,12 @@ private static void AddFieldRvasToTable(ManagedPEBuilderContext context)

for (int i = 0; i < fieldRvaTable.Count; i++)
{
var data = reader.ResolveFieldData(ThrowErrorListener.Instance, metadata, fieldRvaTable[i]);
var data = reader.ResolveFieldData(
ThrowErrorListener.Instance,
context.Platform,
directory,
fieldRvaTable[i]);

if (data is null)
continue;

Expand Down
58 changes: 58 additions & 0 deletions src/AsmResolver.PE/DotNet/DotNetDirectoryFlagsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using AsmResolver.PE.Platforms;

namespace AsmResolver.PE.DotNet
{
/// <summary>
/// Provides extension methods for <see cref="DotNetDirectoryFlags"/>.
/// </summary>
public static class DotNetDirectoryFlagsExtensions
{
/// <summary>
/// Determines whether the module is loaded as a 32-bit process.
/// </summary>
/// <returns>
/// <param name="flags">The flags of the module as specified in its COR20 header.</param>
/// <param name="platform">The platform to assume the module is loaded on.</param>
/// <c>true</c> if the module is loaded as a 32-bit process, <c>false</c> if it is loaded as a 64-bit process.
/// </returns>
public static bool IsLoadedAs32Bit(this DotNetDirectoryFlags flags, Platform platform)
{
return flags.IsLoadedAs32Bit(platform, false, true);
}

/// <summary>
/// Determines whether the module is loaded as a 32-bit process.
/// </summary>
/// <param name="flags">The flags of the module as specified in its COR20 header.</param>
/// <param name="platform">The platform to assume the module is loaded on.</param>
/// <param name="assume32BitSystem"><c>true</c> if a 32-bit system should be assumed.</param>
/// <param name="canLoadAs32Bit"><c>true</c> if the application can be loaded as a 32-bit process.</param>
/// <returns>
/// <c>true</c> if the module is loaded as a 32-bit process, <c>false</c> if it is loaded as a 64-bit process.
/// </returns>
public static bool IsLoadedAs32Bit(this DotNetDirectoryFlags flags, Platform platform, bool assume32BitSystem, bool canLoadAs32Bit)
{
// Short-circuit all 64-bit platforms.
if (!platform.Is32Bit)
return false;

// Check if we are dealing with an AnyCPU binary.
if (platform is not I386Platform)
return true;

// Non-ILOnly 32-bit binaries are always loaded as 32-bit.
if ((flags & DotNetDirectoryFlags.ILOnly) == 0)
return true;

// If we require 32-bit as specified by COR20 headers, load as 32-bit.
if ((flags & DotNetDirectoryFlags.Bit32Required) != 0)
return true;

// Try cater to preference.
if ((flags & DotNetDirectoryFlags.Bit32Preferred) != 0)
return assume32BitSystem | canLoadAs32Bit;

return assume32BitSystem;
}
}
}
62 changes: 43 additions & 19 deletions src/AsmResolver.PE/DotNet/Metadata/FieldRvaDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using AsmResolver.PE.DotNet.Metadata.Blob;
using AsmResolver.PE.DotNet.Metadata.Tables;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
using AsmResolver.PE.File;
using AsmResolver.PE.Platforms;

namespace AsmResolver.PE.DotNet.Metadata
{
Expand All @@ -12,40 +14,59 @@ namespace AsmResolver.PE.DotNet.Metadata
public class FieldRvaDataReader : IFieldRvaDataReader
{
/// <inheritdoc />
public ISegment? ResolveFieldData(IErrorListener listener, IMetadata metadata, in FieldRvaRow fieldRvaRow)
public ISegment? ResolveFieldData(
IErrorListener listener,
Platform platform,
IDotNetDirectory directory,
in FieldRvaRow fieldRvaRow)
{
if (fieldRvaRow.Data.IsBounded)
return fieldRvaRow.Data.GetSegment();

if (fieldRvaRow.Data.CanRead)
var metadata = directory.Metadata;
if (metadata is null)
{
listener.BadImage(".NET directory does not contain a metadata directory.");
return null;
}

if (!metadata.TryGetStream<TablesStream>(out var tablesStream))
{
if (!metadata.TryGetStream<TablesStream>(out var tablesStream))
{
listener.BadImage("Metadata does not contain a tables stream.");
return null;
}
listener.BadImage("Metadata does not contain a tables stream.");
return null;
}

var table = tablesStream.GetTable<FieldDefinitionRow>(TableIndex.Field);
if (fieldRvaRow.Field > table.Count)
{
listener.BadImage("FieldRva row has an invalid Field column value.");
return null;
}
var table = tablesStream.GetTable<FieldDefinitionRow>(TableIndex.Field);
if (fieldRvaRow.Field > table.Count)
{
listener.BadImage("FieldRva row has an invalid Field column value.");
return null;
}

var field = table.GetByRid(fieldRvaRow.Field);
int valueSize = DetermineFieldSize(metadata, field);
var field = table.GetByRid(fieldRvaRow.Field);
int valueSize = DetermineFieldSize(platform, directory, field);

if (fieldRvaRow.Data.CanRead)
{
var reader = fieldRvaRow.Data.CreateReader();
return DataSegment.FromReader(ref reader, valueSize);
}

if (fieldRvaRow.Data is PESegmentReference {IsValidAddress: true})
{
// We are reading from a virtual segment that is resized at runtime, assume zeroes.
var segment = new ZeroesSegment((uint) valueSize);
segment.UpdateOffsets(new RelocationParameters(fieldRvaRow.Data.Offset, fieldRvaRow.Data.Rva));
return segment;
}

listener.NotSupported("FieldRva row has an invalid or unsupported data column.");
return null;
}

private int DetermineFieldSize(IMetadata metadata, in FieldDefinitionRow field)
private int DetermineFieldSize(Platform platform, IDotNetDirectory directory, in FieldDefinitionRow field)
{
if (!metadata.TryGetStream<BlobStream>(out var blobStream)
if (!directory.Metadata!.TryGetStream<BlobStream>(out var blobStream)
|| !blobStream.TryGetBlobReaderByIndex(field.Signature, out var reader))
{
return 0;
Expand All @@ -67,8 +88,11 @@ private int DetermineFieldSize(IMetadata metadata, in FieldDefinitionRow field)
ElementType.U8 => sizeof(ulong),
ElementType.R4 => sizeof(float),
ElementType.R8 => sizeof(double),
ElementType.ValueType => GetCustomTypeSize(metadata, ref reader),
ElementType.Class => GetCustomTypeSize(metadata, ref reader),
ElementType.I or ElementType.U => directory.Flags.IsLoadedAs32Bit(platform)
? sizeof(uint)
: sizeof(ulong),
ElementType.ValueType => GetCustomTypeSize(directory.Metadata, ref reader),
ElementType.Class => GetCustomTypeSize(directory.Metadata, ref reader),
_ => throw new ArgumentOutOfRangeException()
};
}
Expand Down
Loading