XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Buffers.Binary;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Text;

namespace XFESystem.ManagedRuntime.Tests;

/// <summary>Mutates copies of real compiled CLI PE images without loading any assembly into the CLR.</summary>
internal sealed class ManagedImage : IDisposable
{
    private readonly MemoryStream _stream;
    private readonly PEReader _pe;
    public byte[] Bytes { get; }
    public MetadataReader Metadata { get; }
    public MethodDefinition Entry { get; }
    public int CodeOffset { get; }
    public int CodeLength { get; }
    public int MaximumStack { get; }

    public ManagedImage(string name = "ManagedFixture.dll")
    {
        Bytes = File.ReadAllBytes(Path.Combine(AppContext.BaseDirectory, "Fixtures", name));
        _stream = new MemoryStream(Bytes, false);
        _pe = new PEReader(_stream);
        Metadata = _pe.GetMetadataReader();
        var token = _pe.PEHeaders.CorHeader!.EntryPointTokenOrRelativeVirtualAddress;
        Entry = Metadata.GetMethodDefinition((MethodDefinitionHandle)MetadataTokens.EntityHandle(token));
        var body = _pe.GetMethodBody(Entry.RelativeVirtualAddress);
        CodeLength = body.GetILBytes()!.Length;
        MaximumStack = body.MaxStack;
        var header = FileOffset(Entry.RelativeVirtualAddress);
        CodeOffset = header + ((Bytes[header] & 3) == 2 ? 1 : (BinaryPrimitives.ReadUInt16LittleEndian(Bytes.AsSpan(header, 2)) >> 12) * 4);
    }

    public byte[] WithCode(params byte[] instructions)
    {
        if (instructions.Length > CodeLength) throw new ArgumentException("The mutation exceeds the original method body.");
        var copy = Bytes.ToArray();
        copy.AsSpan(CodeOffset, CodeLength).Clear();
        instructions.CopyTo(copy, CodeOffset);
        return copy;
    }

    public byte[] WithTokenInstruction(byte opcode, int token, params byte[] tail)
    {
        var code = new byte[5 + tail.Length];
        code[0] = opcode;
        BinaryPrimitives.WriteInt32LittleEndian(code.AsSpan(1), token);
        tail.CopyTo(code, 5);
        return WithCode(code);
    }

    public int Member(string owner, string name)
    {
        foreach (var handle in Metadata.MemberReferences)
        {
            var member = Metadata.GetMemberReference(handle);
            if (Metadata.GetString(member.Name) != name || member.Parent.Kind != HandleKind.TypeReference) continue;
            var type = Metadata.GetTypeReference((TypeReferenceHandle)member.Parent);
            if (Metadata.GetString(type.Namespace) + "." + Metadata.GetString(type.Name) == owner)
                return MetadataTokens.GetToken(handle);
        }
        throw new InvalidOperationException($"No member token: {owner}.{name}");
    }

    public int UserString(string value)
    {
        // User-string heap entries carry their own compressed length. Read handles from actual ldstr instructions.
        foreach (var methodHandle in Metadata.MethodDefinitions)
        {
            var method = Metadata.GetMethodDefinition(methodHandle);
            if (method.RelativeVirtualAddress == 0) continue;
            var code = _pe.GetMethodBody(method.RelativeVirtualAddress).GetILBytes()!;
            for (int index = 0; index + 4 < code.Length; index++)
                if (code[index] == 0x72)
                {
                    var token = BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(index + 1, 4));
                    if ((token & unchecked((int)0xFF000000)) == 0x70000000 &&
                        Metadata.GetUserString(MetadataTokens.UserStringHandle(token & 0xFFFFFF)) == value) return token;
                }
        }
        throw new InvalidOperationException("The compiled fixture has no requested user string.");
    }

    public int SignaturePayloadOffset(BlobHandle signature)
    {
        var metadataStart = FileOffset(_pe.PEHeaders.CorHeader!.MetadataDirectory.RelativeVirtualAddress);
        int versionLength = BinaryPrimitives.ReadInt32LittleEndian(Bytes.AsSpan(metadataStart + 12, 4));
        int at = metadataStart + 16 + ((versionLength + 3) & ~3);
        int streams = BinaryPrimitives.ReadUInt16LittleEndian(Bytes.AsSpan(at + 2, 2));
        at += 4;
        for (int index = 0; index < streams; index++)
        {
            int offset = BinaryPrimitives.ReadInt32LittleEndian(Bytes.AsSpan(at, 4));
            int nameAt = at + 8, end = nameAt;
            while (Bytes[end] != 0) end++;
            string name = Encoding.ASCII.GetString(Bytes.AsSpan(nameAt, end - nameAt));
            at = nameAt + ((end - nameAt + 4) & ~3);
            if (name != "#Blob") continue;
            int blob = metadataStart + offset + MetadataTokens.GetHeapOffset(signature);
            return blob + (Bytes[blob] < 0x80 ? 1 : Bytes[blob] < 0xC0 ? 2 : 4);
        }
        throw new InvalidOperationException("Missing metadata blob heap.");
    }

    public int CorHeaderOffset => FileOffset(_pe.PEHeaders.PEHeader!.CorHeaderTableDirectory.RelativeVirtualAddress);

    private int FileOffset(int rva)
    {
        foreach (var section in _pe.PEHeaders.SectionHeaders)
            if (rva >= section.VirtualAddress && rva - section.VirtualAddress < Math.Max(section.VirtualSize, section.SizeOfRawData))
                return section.PointerToRawData + rva - section.VirtualAddress;
        throw new BadImageFormatException("Fixture RVA is not in a PE section.");
    }

    public void Dispose() { _pe.Dispose(); _stream.Dispose(); }
}