potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# DOS MZファイルヘッダー(IMAGE_DOS_HEADER)のe_lfanewの幅広さ

Windows 10の実行ファイルはイメージファイルの一種であり、普通はPEファイルフォーマットを持ちます。PEファイルフォーマットではファイルの先頭にMS-DOSと共通のMZファイルヘッダー(IMAGE_DOS_HEADER構造体)、続けてMS-DOS Real Modeスタブプログラムを持ち、さらにIMAGE_DOS_HEADET.e_lfanewの指す位置にPEヘッダーを持ちます。

Windowsディレクトリのイメージファイル(実行ファイルやDLL等)のe_lfanewを確認したところ、その範囲は思ったよりも広範でした。以下にサンプルコードを示します。

using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        var paths = Directory.GetFiles(Environment.SystemDirectory);
        var infos = paths
            .Select(path => (FileName: Path.GetFileName(path), Header: MZFileInfo.TryGet(path, out var info) ? info : null))
            .Where(filenameInfoPair => filenameInfoPair.Header?.IsValid ?? false)
            .ToArray();

        // e_lfanewsの値の確認用
        var lfanews = infos.Select(info => info.Header.Raw.e_lfanew).Distinct().OrderBy(x => x);
        var lfanewsStr = string.Join(", ", lfanews);
        // "128, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 256, 264, 272, 280, 288, 296, 304, 10872"

        var lookupByLfanew = infos.ToLookup(
            info => info.Header.Raw.e_lfanew,
            info => info);
    }
}

internal sealed class MZFileInfo
{
    [StructLayout(LayoutKind.Sequential)]
    public struct IMAGE_DOS_HEADER
    {
        public ushort e_magic;
        public ushort e_cblp;
        public ushort e_cp;
        public ushort e_crlc;
        public ushort e_cparhdr;
        public ushort e_minalloc;
        public ushort e_maxalloc;
        public ushort e_ss;
        public ushort e_sp;
        public ushort e_csum;
        public ushort e_ip;
        public ushort e_cs;
        public ushort e_lfarlc;
        public ushort e_ovno;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
        public ushort[] e_res;
        public ushort e_oemid;
        public ushort e_oeminfo;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
        public ushort[] e_res2;
        public int e_lfanew;
    }

    private const ushort IMAGE_DOS_SIGNATURE = 0x5A4D; // "MZ"

    public ushort ImageDOSSignature = IMAGE_DOS_SIGNATURE;

    private IMAGE_DOS_HEADER dosHeader;

    public static bool TryGet(string filename, out MZFileInfo info)
    {
        try
        {
            info = new MZFileInfo(filename);
            return true;
        }
        catch
        {
            info = default(MZFileInfo);
            return false;
        }
    }

    public static bool TryGet(Stream stream, out MZFileInfo info)
    {
        try
        {
            info = new MZFileInfo(stream);
            return true;
        }
        catch
        {
            info = default(MZFileInfo);
            return false;
        }
    }

    public MZFileInfo()
    {
    }

    public MZFileInfo(string filename)
    {
        using var file = File.OpenRead(filename);
        Init(file);
    }

    public MZFileInfo(Stream stream)
    {
        Init(stream);
    }

    private void Init(Stream stream)
    {
        var buffer = new byte[Marshal.SizeOf<IMAGE_DOS_HEADER>()];
        stream.Read(buffer);
        using (var pointer = new SafeManagedObjectPointer(buffer))
        {
            dosHeader = Marshal.PtrToStructure<IMAGE_DOS_HEADER>(pointer);
        }
    }

    public ref readonly IMAGE_DOS_HEADER Raw => ref dosHeader;
    public bool IsValid => dosHeader.e_magic == ImageDOSSignature;
}

internal sealed class SafeManagedObjectPointer : IDisposable
{
    private GCHandle handle;
    public IntPtr Value { get; private set; }

    public SafeManagedObjectPointer() { }
    public SafeManagedObjectPointer(object obj)
    {
        handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
        Value = handle.AddrOfPinnedObject();
    }

    ~SafeManagedObjectPointer()
    {
        Dispose();
    }

    public void Dispose()
    {
        if (handle.IsAllocated)
        {
            handle.Free();
            GC.SuppressFinalize(this);
            Value = IntPtr.Zero;
        }
    }

    public bool IsAllocated => handle.IsAllocated;

    public static implicit operator IntPtr(SafeManagedObjectPointer source)
    {
        return source.Value;
    }
}