9.8.2018.0630
大石头 编写于 2018-06-30 11:15:32
X
using System;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using NewLife.Log;
using RVA = System.UInt32;

namespace NewLife.Reflection
{
    /// <summary>PE镜像</summary>
    /// <remarks>
    /// 
    /// </remarks>
    public class PEImage
    {
        #region 属性
        private PEFileKinds _Kind;
        /// <summary>可执行文件类型</summary>
        public PEFileKinds Kind { get { return _Kind; } set { _Kind = value; } }

        private PortableExecutableKinds _ExecutableKind;
        /// <summary>可执行文件代码特性</summary>
        public PortableExecutableKinds ExecutableKind { get { return _ExecutableKind; } set { _ExecutableKind = value; } }

        private ImageFileMachine _Machine;
        /// <summary>可执行文件的目标平台</summary>
        public ImageFileMachine Machine { get { return _Machine; } set { _Machine = value; } }

        private UInt16 _Characteristics;
        /// <summary>标识特性</summary>
        public UInt16 Characteristics { get { return _Characteristics; } set { _Characteristics = value; } }

        private Version _Version;
        /// <summary>版本</summary>
        public Version Version { get { return _Version; } set { _Version = value; } }

        /// <summary>是否.Net程序</summary>
        public Boolean IsNet { get { return Version != null; } }
        // 不能只判断ILOnly,System.Data.SQLite.dll得到的ExecutableKind是24
        //public Boolean IsNet { get { return ExecutableKind.Has(PortableExecutableKinds.ILOnly) && Version != null; } }

        Section[] Sections;
        DataDirectory cli;
        DataDirectory metadata;
        #endregion

        #region 读取镜像
        /// <summary>读取镜像信息</summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static PEImage Read(String file)
        {
            if (String.IsNullOrEmpty(file)) return null;
            if (!File.Exists(file)) return null;

            try
            {
                using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                {
                    return Read(fs);
                }
            }
            catch (Exception ex)
            {
                if (XTrace.Debug) XTrace.WriteLine("PEImage.Read({0})出错 {1}", file, ex);

                return null;
            }
        }

        /// <summary>从数据流中读取PE文件头部</summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static PEImage Read(Stream stream)
        {
            if (stream.Length < 128) return null;

            var reader = new BinaryReader(stream);

            // - DOSHeader DOS头部 固定64字节

            // PE					2
            // Start				58
            // Lfanew				4
            // End					64

            // 幻数
            if (reader.ReadUInt16() != 0x5a4d) return null;

            stream.Seek(58, SeekOrigin.Current);

            // 最后4个字节是PEHeader位置
            stream.Position = reader.ReadUInt32();

            // 4字节PE文件头, PE\0\0
            if (reader.ReadUInt32() != 0x00004550) return null;

            // - PEFileHeader

            var image = new PEImage
            {

                // 执行环境平台
                // Machine				2
                Machine = (ImageFileMachine)reader.ReadUInt16()
            };

            // NumberOfSections		2
            var sections = reader.ReadUInt16();

            // TimeDateStamp		4
            // PointerToSymbolTable	4
            // NumberOfSymbols		4
            // OptionalHeaderSize	2
            stream.Seek(14, SeekOrigin.Current);

            // 一个标志的集合,其大部分位用于OBJ或LIB文件中
            // Characteristics		2
            image.Characteristics = reader.ReadUInt16();

            image.ReadOptionalHeaders(reader);
            image.ReadSections(reader, sections);
            image.ReadCLIHeader(reader);
            image.ReadMetadata(reader);

            return image;
        }

        void ReadOptionalHeaders(BinaryReader reader)
        {
            var stream = reader.BaseStream;
            // - PEOptionalHeader
            //   - StandardFieldsHeader

            // pe32 = 0x10b     pe64 = 0x20b
            // Magic				2
            var pe64 = reader.ReadUInt16() == 0x20b;

            //						pe32 || pe64

            // LMajor				1
            // LMinor				1
            // CodeSize				4
            // InitializedDataSize	4
            // UninitializedDataSize4
            // EntryPointRVA		4
            // BaseOfCode			4
            // BaseOfData			4 || 0

            //   - NTSpecificFieldsHeader

            // ImageBase			4 || 8
            // SectionAlignment		4
            // FileAlignement		4
            // OSMajor				2
            // OSMinor				2
            // UserMajor			2
            // UserMinor			2
            // SubSysMajor			2
            // SubSysMinor			2
            // Reserved				4
            // ImageSize			4
            // HeaderSize			4
            // FileChecksum			4
            stream.Seek(66, SeekOrigin.Current);

            // SubSystem			2
            var subsystem = reader.ReadUInt16();
            if ((Characteristics & 0x2000) != 0)
                Kind = PEFileKinds.Dll;
            else
            {
                switch (subsystem)
                {
                    case 1:
                        Kind = PEFileKinds.Dll;
                        break;
                    case 2:
                    case 9: // WinCE
                        Kind = PEFileKinds.WindowApplication;
                        break;
                    case 3:
                        Kind = PEFileKinds.ConsoleApplication;
                        break;
                    default:
                        Kind = (PEFileKinds)subsystem;
                        break;
                }
            }

            // DLLFlags				2
            // StackReserveSize		4 || 8
            // StackCommitSize		4 || 8
            // HeapReserveSize		4 || 8
            // HeapCommitSize		4 || 8
            // LoaderFlags			4
            // NumberOfDataDir		4

            //   - DataDirectoriesHeader

            // ExportTable			8
            // ImportTable			8
            // ResourceTable		8
            // ExceptionTable		8
            // CertificateTable		8
            // BaseRelocationTable	8

            stream.Seek(pe64 ? 90 : 74, SeekOrigin.Current);

            // Debug				8
            //image.Debug = new DataDirectory(reader.ReadUInt32(), reader.ReadUInt32());
            stream.Seek(8, SeekOrigin.Current);

            // Copyright			8
            // GlobalPtr			8
            // TLSTable				8
            // LoadConfigTable		8
            // BoundImport			8
            // IAT					8
            // DelayImportDescriptor8
            stream.Seek(56, SeekOrigin.Current);

            // CLIHeader			8
            cli = new DataDirectory(reader);
            //stream.Seek(8, SeekOrigin.Current);

            //if (cli.IsZero)
            //    throw new BadImageFormatException();

            // Reserved				8
            stream.Seek(8, SeekOrigin.Current);
        }

        void ReadSections(BinaryReader reader, UInt16 count)
        {
            var stream = reader.BaseStream;
            var sections = new Section[count];

            for (var i = 0; i < count; i++)
            {
                var section = new Section
                {

                    // Name
                    Name = Encoding.ASCII.GetString(reader.ReadBytes(8)).TrimEnd('\0')
                };

                // VirtualSize		4
                stream.Seek(4, SeekOrigin.Current);

                // VirtualAddress	4
                section.VirtualAddress = reader.ReadUInt32();
                // SizeOfRawData	4
                section.SizeOfRawData = reader.ReadUInt32();
                // PointerToRawData	4
                section.PointerToRawData = reader.ReadUInt32();

                // PointerToRelocations		4
                // PointerToLineNumbers		4
                // NumberOfRelocations		2
                // NumberOfLineNumbers		2
                // Characteristics			4
                stream.Seek(16, SeekOrigin.Current);

                sections[i] = section;
            }

            Sections = sections;
        }

        Boolean ReadCLIHeader(BinaryReader reader)
        {
            var stream = reader.BaseStream;

            var addr = ResolveVirtualAddress(cli.VirtualAddress);
            if (addr < 0) return false;
            stream.Position = addr;

            // - CLIHeader

            // Cb						4
            // MajorRuntimeVersion		2
            // MinorRuntimeVersion		2
            stream.Seek(8, SeekOrigin.Current);

            // Metadata					8
            metadata = new DataDirectory(reader);
            // Flags					4
            ExecutableKind = (PortableExecutableKinds)reader.ReadUInt32();
            // EntryPointToken			4
            //image.EntryPointToken = ReadUInt32();
            // Resources				8
            //image.Resources = ReadDataDirectory();
            // StrongNameSignature		8
            // CodeManagerTable			8
            // VTableFixups				8
            // ExportAddressTableJumps	8
            // ManagedNativeHeader		8

            return true;
        }

        Boolean ReadMetadata(BinaryReader reader)
        {
            var stream = reader.BaseStream;

            var addr = ResolveVirtualAddress(metadata.VirtualAddress);
            if (addr < 0) return false;
            stream.Position = addr;

            if (reader.ReadUInt32() != 0x424a5342) return false;

            // MajorVersion			2
            // MinorVersion			2
            // Reserved				4
            stream.Seek(8, SeekOrigin.Current);

            var buf = reader.ReadBytes(reader.ReadInt32());
            if (buf != null && buf.Length > 0)
            {
                var version = Encoding.ASCII.GetString(buf).TrimEnd('\0').TrimStart('v');
                try
                {
                    Version = new Version(version);
                }
                catch { }
            }

            return true;
        }
        #endregion

        #region 辅助
        /// <summary>能否在指定版本下加载程序集</summary>
        /// <remarks>
        /// 必须加强过滤,下面一旦只读加载,就再也不能删除文件
        /// </remarks>
        /// <param name="file">程序集文件</param>
        /// <param name="ver">指定模版,默认空表示当前版本</param>
        /// <param name="debug">是否输出调试日志</param>
        /// <returns></returns>
        public static Boolean CanLoad(String file, Version ver = null, Boolean debug = false)
        {
            if (file.IsNullOrEmpty()) return false;
            if (!File.Exists(file)) return false;

            // 仅加载.Net文件,并且小于等于当前版本
            var pe = Read(file);
            if (pe == null || !pe.IsNet) return false;

            if (ver == null) ver = new Version(Assembly.GetExecutingAssembly().ImageRuntimeVersion.TrimStart('v'));

            // 只判断主次版本,只要这两个相同,后面可以兼容
            var pv = pe.Version;
            if (pv.Major > ver.Major || pv.Major == ver.Major && pv.Minor > ver.Minor)
            {
                if (debug) XTrace.WriteLine("程序集 {0} 的版本 {1} 大于当前运行时 {2}", file, pv, ver);
                return false;
            }
            // 必须加强过滤,下面一旦只读加载,就再也不能删除文件
            if (!pe.ExecutableKind.Has(PortableExecutableKinds.ILOnly))
            {
                // 判断x86/x64兼容。无法区分x86/x64的SQLite驱动
                //XTrace.WriteLine("{0,12} {1} {2}", item, pe.Machine, pe.ExecutableKind);
                //var x64 = pe.ExecutableKind.Has(PortableExecutableKinds.Required32Bit);
                //var x64 = pe.Machine == ImageFileMachine.AMD64;
                var x64 = pe.Machine == ImageFileMachine.AMD64;
                if (Runtime.Is64BitProcess ^ x64)
                {
                    if (debug) XTrace.WriteLine("程序集 {0} 的代码特性是 {1},而当前进程是 {2} 进程", file, pe.Machine, Runtime.Is64BitProcess ? "64位" : "32位");

                    return false;
                }
            }

            return true;
        }

        Int64 ResolveVirtualAddress(RVA rva)
        {
            var section = GetSectionAtVirtualAddress(rva);
            if (section == null) return -1;

            return rva + section.PointerToRawData - section.VirtualAddress;
        }

        Section GetSectionAtVirtualAddress(RVA rva)
        {
            foreach (var section in Sections)
            {
                if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.SizeOfRawData)
                    return section;
            }

            return null;
        }
        #endregion

        #region 内部类
        struct DataDirectory
        {
            public readonly RVA VirtualAddress;
            public readonly RVA Size;

            public Boolean IsZero { get { return VirtualAddress == 0 && Size == 0; } }

            public DataDirectory(RVA rva, RVA size)
            {
                VirtualAddress = rva;
                Size = size;
            }

            public DataDirectory(BinaryReader reader) : this(reader.ReadUInt32(), reader.ReadUInt32()) { }
        }

        sealed class Section
        {
            public String Name;
            public RVA VirtualAddress;
            //public uint VirtualSize;
            public RVA SizeOfRawData;
            public RVA PointerToRawData;
            //public byte[] Data;
        }
        #endregion
    }
}