// // Author: // Jb Evain (jbevain@gmail.com) // // Copyright (c) 2008 - 2015 Jb Evain // Copyright (c) 2008 - 2011 Novell, Inc. // // Licensed under the MIT/X11 license. // using MonoFN.Cecil.Cil; using MonoFN.Cecil.Metadata; using System; using System.IO; namespace MonoFN.Cecil.PE { internal sealed class ImageReader : BinaryStreamReader { private readonly Image image; private DataDirectory cli; private DataDirectory metadata; private uint table_heap_offset; public ImageReader(Disposable stream, string file_name) : base(stream.value) { image = new(); image.Stream = stream; image.FileName = file_name; } private void MoveTo(DataDirectory directory) { BaseStream.Position = image.ResolveVirtualAddress(directory.VirtualAddress); } private void ReadImage() { if (BaseStream.Length < 128) throw new BadImageFormatException(); // - DOSHeader // PE 2 // Start 58 // Lfanew 4 // End 64 if (ReadUInt16() != 0x5a4d) throw new BadImageFormatException(); Advance(58); MoveTo(ReadUInt32()); if (ReadUInt32() != 0x00004550) throw new BadImageFormatException(); // - PEFileHeader // Machine 2 image.Architecture = ReadArchitecture(); // NumberOfSections 2 ushort sections = ReadUInt16(); // TimeDateStamp 4 image.Timestamp = ReadUInt32(); // PointerToSymbolTable 4 // NumberOfSymbols 4 // OptionalHeaderSize 2 Advance(10); // Characteristics 2 ushort characteristics = ReadUInt16(); ushort subsystem, dll_characteristics; ReadOptionalHeaders(out subsystem, out dll_characteristics); ReadSections(sections); ReadCLIHeader(); ReadMetadata(); ReadDebugHeader(); image.Characteristics = characteristics; image.Kind = GetModuleKind(characteristics, subsystem); image.DllCharacteristics = (ModuleCharacteristics)dll_characteristics; } private TargetArchitecture ReadArchitecture() { return (TargetArchitecture)ReadUInt16(); } private static ModuleKind GetModuleKind(ushort characteristics, ushort subsystem) { if ((characteristics & 0x2000) != 0) // ImageCharacteristics.Dll return ModuleKind.Dll; if (subsystem == 0x2 || subsystem == 0x9) // SubSystem.WindowsGui || SubSystem.WindowsCeGui return ModuleKind.Windows; return ModuleKind.Console; } private void ReadOptionalHeaders(out ushort subsystem, out ushort dll_characteristics) { // - PEOptionalHeader // - StandardFieldsHeader // Magic 2 bool pe64 = ReadUInt16() == 0x20b; // pe32 || pe64 image.LinkerVersion = ReadUInt16(); // 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 Advance(44); image.SubSystemMajor = ReadUInt16(); image.SubSystemMinor = ReadUInt16(); // Reserved 4 // ImageSize 4 // HeaderSize 4 // FileChecksum 4 Advance(16); // SubSystem 2 subsystem = ReadUInt16(); // DLLFlags 2 dll_characteristics = ReadUInt16(); // StackReserveSize 4 || 8 // StackCommitSize 4 || 8 // HeapReserveSize 4 || 8 // HeapCommitSize 4 || 8 // LoaderFlags 4 // NumberOfDataDir 4 // - DataDirectoriesHeader // ExportTable 8 // ImportTable 8 Advance(pe64 ? 56 : 40); // ResourceTable 8 image.Win32Resources = ReadDataDirectory(); // ExceptionTable 8 // CertificateTable 8 // BaseRelocationTable 8 Advance(24); // Debug 8 image.Debug = ReadDataDirectory(); // Copyright 8 // GlobalPtr 8 // TLSTable 8 // LoadConfigTable 8 // BoundImport 8 // IAT 8 // DelayImportDescriptor8 Advance(56); // CLIHeader 8 cli = ReadDataDirectory(); if (cli.IsZero) throw new BadImageFormatException(); // Reserved 8 Advance(8); } private string ReadAlignedString(int length) { int read = 0; char[] buffer = new char [length]; while (read < length) { byte current = ReadByte(); if (current == 0) break; buffer[read++] = (char)current; } Advance(-1 + ((read + 4) & ~3) - read); return new(buffer, 0, read); } private string ReadZeroTerminatedString(int length) { int read = 0; char[] buffer = new char [length]; byte[] bytes = ReadBytes(length); while (read < length) { byte current = bytes[read]; if (current == 0) break; buffer[read++] = (char)current; } return new(buffer, 0, read); } private void ReadSections(ushort count) { Section[] sections = new Section [count]; for (int i = 0; i < count; i++) { Section section = new(); // Name section.Name = ReadZeroTerminatedString(8); // VirtualSize 4 Advance(4); // VirtualAddress 4 section.VirtualAddress = ReadUInt32(); // SizeOfRawData 4 section.SizeOfRawData = ReadUInt32(); // PointerToRawData 4 section.PointerToRawData = ReadUInt32(); // PointerToRelocations 4 // PointerToLineNumbers 4 // NumberOfRelocations 2 // NumberOfLineNumbers 2 // Characteristics 4 Advance(16); sections[i] = section; } image.Sections = sections; } private void ReadCLIHeader() { MoveTo(cli); // - CLIHeader // Cb 4 // MajorRuntimeVersion 2 // MinorRuntimeVersion 2 Advance(8); // Metadata 8 metadata = ReadDataDirectory(); // Flags 4 image.Attributes = (ModuleAttributes)ReadUInt32(); // EntryPointToken 4 image.EntryPointToken = ReadUInt32(); // Resources 8 image.Resources = ReadDataDirectory(); // StrongNameSignature 8 image.StrongName = ReadDataDirectory(); // CodeManagerTable 8 // VTableFixups 8 // ExportAddressTableJumps 8 // ManagedNativeHeader 8 } private void ReadMetadata() { MoveTo(metadata); if (ReadUInt32() != 0x424a5342) throw new BadImageFormatException(); // MajorVersion 2 // MinorVersion 2 // Reserved 4 Advance(8); image.RuntimeVersion = ReadZeroTerminatedString(ReadInt32()); // Flags 2 Advance(2); ushort streams = ReadUInt16(); Section section = image.GetSectionAtVirtualAddress(metadata.VirtualAddress); if (section == null) throw new BadImageFormatException(); image.MetadataSection = section; for (int i = 0; i < streams; i++) ReadMetadataStream(section); if (image.PdbHeap != null) ReadPdbHeap(); if (image.TableHeap != null) ReadTableHeap(); } private void ReadDebugHeader() { if (image.Debug.IsZero) { image.DebugHeader = new(Empty.Array); return; } MoveTo(image.Debug); ImageDebugHeaderEntry[] entries = new ImageDebugHeaderEntry [(int)image.Debug.Size / ImageDebugDirectory.Size]; for (int i = 0; i < entries.Length; i++) { ImageDebugDirectory directory = new() { Characteristics = ReadInt32(), TimeDateStamp = ReadInt32(), MajorVersion = ReadInt16(), MinorVersion = ReadInt16(), Type = (ImageDebugType)ReadInt32(), SizeOfData = ReadInt32(), AddressOfRawData = ReadInt32(), PointerToRawData = ReadInt32() }; if (directory.PointerToRawData == 0 || directory.SizeOfData < 0) { entries[i] = new(directory, Empty.Array); continue; } int position = Position; try { MoveTo((uint)directory.PointerToRawData); byte[] data = ReadBytes(directory.SizeOfData); entries[i] = new(directory, data); } finally { Position = position; } } image.DebugHeader = new(entries); } private void ReadMetadataStream(Section section) { // Offset 4 uint offset = metadata.VirtualAddress - section.VirtualAddress + ReadUInt32(); // relative to the section start // Size 4 uint size = ReadUInt32(); byte[] data = ReadHeapData(offset, size); string name = ReadAlignedString(16); switch (name) { case "#~": case "#-": image.TableHeap = new(data); table_heap_offset = offset; break; case "#Strings": image.StringHeap = new(data); break; case "#Blob": image.BlobHeap = new(data); break; case "#GUID": image.GuidHeap = new(data); break; case "#US": image.UserStringHeap = new(data); break; case "#Pdb": image.PdbHeap = new(data); break; } } private byte[] ReadHeapData(uint offset, uint size) { long position = BaseStream.Position; MoveTo(offset + image.MetadataSection.PointerToRawData); byte[] data = ReadBytes((int)size); BaseStream.Position = position; return data; } private void ReadTableHeap() { TableHeap heap = image.TableHeap; MoveTo(table_heap_offset + image.MetadataSection.PointerToRawData); // Reserved 4 // MajorVersion 1 // MinorVersion 1 Advance(6); // HeapSizes 1 byte sizes = ReadByte(); // Reserved2 1 Advance(1); // Valid 8 heap.Valid = ReadInt64(); // Sorted 8 heap.Sorted = ReadInt64(); if (image.PdbHeap != null) { for (int i = 0; i < Mixin.TableCount; i++) { if (!image.PdbHeap.HasTable((Table)i)) continue; heap.Tables[i].Length = image.PdbHeap.TypeSystemTableRows[i]; } } for (int i = 0; i < Mixin.TableCount; i++) { if (!heap.HasTable((Table)i)) continue; heap.Tables[i].Length = ReadUInt32(); } SetIndexSize(image.StringHeap, sizes, 0x1); SetIndexSize(image.GuidHeap, sizes, 0x2); SetIndexSize(image.BlobHeap, sizes, 0x4); ComputeTableInformations(); } private static void SetIndexSize(Heap heap, uint sizes, byte flag) { if (heap == null) return; heap.IndexSize = (sizes & flag) > 0 ? 4 : 2; } private int GetTableIndexSize(Table table) { return image.GetTableIndexSize(table); } private int GetCodedIndexSize(CodedIndex index) { return image.GetCodedIndexSize(index); } private void ComputeTableInformations() { uint offset = (uint)BaseStream.Position - table_heap_offset - image.MetadataSection.PointerToRawData; // header int stridx_size = image.StringHeap != null ? image.StringHeap.IndexSize : 2; int guididx_size = image.GuidHeap != null ? image.GuidHeap.IndexSize : 2; int blobidx_size = image.BlobHeap != null ? image.BlobHeap.IndexSize : 2; TableHeap heap = image.TableHeap; TableInformation[] tables = heap.Tables; for (int i = 0; i < Mixin.TableCount; i++) { Table table = (Table)i; if (!heap.HasTable(table)) continue; int size; switch (table) { case Table.Module: size = 2 // Generation + stridx_size // Name + guididx_size * 3; // Mvid, EncId, EncBaseId break; case Table.TypeRef: size = GetCodedIndexSize(CodedIndex.ResolutionScope) // ResolutionScope + stridx_size * 2; // Name, Namespace break; case Table.TypeDef: size = 4 // Flags + stridx_size * 2 // Name, Namespace + GetCodedIndexSize(CodedIndex.TypeDefOrRef) // BaseType + GetTableIndexSize(Table.Field) // FieldList + GetTableIndexSize(Table.Method); // MethodList break; case Table.FieldPtr: size = GetTableIndexSize(Table.Field); // Field break; case Table.Field: size = 2 // Flags + stridx_size // Name + blobidx_size; // Signature break; case Table.MethodPtr: size = GetTableIndexSize(Table.Method); // Method break; case Table.Method: size = 8 // Rva 4, ImplFlags 2, Flags 2 + stridx_size // Name + blobidx_size // Signature + GetTableIndexSize(Table.Param); // ParamList break; case Table.ParamPtr: size = GetTableIndexSize(Table.Param); // Param break; case Table.Param: size = 4 // Flags 2, Sequence 2 + stridx_size; // Name break; case Table.InterfaceImpl: size = GetTableIndexSize(Table.TypeDef) // Class + GetCodedIndexSize(CodedIndex.TypeDefOrRef); // Interface break; case Table.MemberRef: size = GetCodedIndexSize(CodedIndex.MemberRefParent) // Class + stridx_size // Name + blobidx_size; // Signature break; case Table.Constant: size = 2 // Type + GetCodedIndexSize(CodedIndex.HasConstant) // Parent + blobidx_size; // Value break; case Table.CustomAttribute: size = GetCodedIndexSize(CodedIndex.HasCustomAttribute) // Parent + GetCodedIndexSize(CodedIndex.CustomAttributeType) // Type + blobidx_size; // Value break; case Table.FieldMarshal: size = GetCodedIndexSize(CodedIndex.HasFieldMarshal) // Parent + blobidx_size; // NativeType break; case Table.DeclSecurity: size = 2 // Action + GetCodedIndexSize(CodedIndex.HasDeclSecurity) // Parent + blobidx_size; // PermissionSet break; case Table.ClassLayout: size = 6 // PackingSize 2, ClassSize 4 + GetTableIndexSize(Table.TypeDef); // Parent break; case Table.FieldLayout: size = 4 // Offset + GetTableIndexSize(Table.Field); // Field break; case Table.StandAloneSig: size = blobidx_size; // Signature break; case Table.EventMap: size = GetTableIndexSize(Table.TypeDef) // Parent + GetTableIndexSize(Table.Event); // EventList break; case Table.EventPtr: size = GetTableIndexSize(Table.Event); // Event break; case Table.Event: size = 2 // Flags + stridx_size // Name + GetCodedIndexSize(CodedIndex.TypeDefOrRef); // EventType break; case Table.PropertyMap: size = GetTableIndexSize(Table.TypeDef) // Parent + GetTableIndexSize(Table.Property); // PropertyList break; case Table.PropertyPtr: size = GetTableIndexSize(Table.Property); // Property break; case Table.Property: size = 2 // Flags + stridx_size // Name + blobidx_size; // Type break; case Table.MethodSemantics: size = 2 // Semantics + GetTableIndexSize(Table.Method) // Method + GetCodedIndexSize(CodedIndex.HasSemantics); // Association break; case Table.MethodImpl: size = GetTableIndexSize(Table.TypeDef) // Class + GetCodedIndexSize(CodedIndex.MethodDefOrRef) // MethodBody + GetCodedIndexSize(CodedIndex.MethodDefOrRef); // MethodDeclaration break; case Table.ModuleRef: size = stridx_size; // Name break; case Table.TypeSpec: size = blobidx_size; // Signature break; case Table.ImplMap: size = 2 // MappingFlags + GetCodedIndexSize(CodedIndex.MemberForwarded) // MemberForwarded + stridx_size // ImportName + GetTableIndexSize(Table.ModuleRef); // ImportScope break; case Table.FieldRVA: size = 4 // RVA + GetTableIndexSize(Table.Field); // Field break; case Table.EncLog: size = 8; break; case Table.EncMap: size = 4; break; case Table.Assembly: size = 16 // HashAlgId 4, Version 4 * 2, Flags 4 + blobidx_size // PublicKey + stridx_size * 2; // Name, Culture break; case Table.AssemblyProcessor: size = 4; // Processor break; case Table.AssemblyOS: size = 12; // Platform 4, Version 2 * 4 break; case Table.AssemblyRef: size = 12 // Version 2 * 4 + Flags 4 + blobidx_size * 2 // PublicKeyOrToken, HashValue + stridx_size * 2; // Name, Culture break; case Table.AssemblyRefProcessor: size = 4 // Processor + GetTableIndexSize(Table.AssemblyRef); // AssemblyRef break; case Table.AssemblyRefOS: size = 12 // Platform 4, Version 2 * 4 + GetTableIndexSize(Table.AssemblyRef); // AssemblyRef break; case Table.File: size = 4 // Flags + stridx_size // Name + blobidx_size; // HashValue break; case Table.ExportedType: size = 8 // Flags 4, TypeDefId 4 + stridx_size * 2 // Name, Namespace + GetCodedIndexSize(CodedIndex.Implementation); // Implementation break; case Table.ManifestResource: size = 8 // Offset, Flags + stridx_size // Name + GetCodedIndexSize(CodedIndex.Implementation); // Implementation break; case Table.NestedClass: size = GetTableIndexSize(Table.TypeDef) // NestedClass + GetTableIndexSize(Table.TypeDef); // EnclosingClass break; case Table.GenericParam: size = 4 // Number, Flags + GetCodedIndexSize(CodedIndex.TypeOrMethodDef) // Owner + stridx_size; // Name break; case Table.MethodSpec: size = GetCodedIndexSize(CodedIndex.MethodDefOrRef) // Method + blobidx_size; // Instantiation break; case Table.GenericParamConstraint: size = GetTableIndexSize(Table.GenericParam) // Owner + GetCodedIndexSize(CodedIndex.TypeDefOrRef); // Constraint break; case Table.Document: size = blobidx_size // Name + guididx_size // HashAlgorithm + blobidx_size // Hash + guididx_size; // Language break; case Table.MethodDebugInformation: size = GetTableIndexSize(Table.Document) // Document + blobidx_size; // SequencePoints break; case Table.LocalScope: size = GetTableIndexSize(Table.Method) // Method + GetTableIndexSize(Table.ImportScope) // ImportScope + GetTableIndexSize(Table.LocalVariable) // VariableList + GetTableIndexSize(Table.LocalConstant) // ConstantList + 4 * 2; // StartOffset, Length break; case Table.LocalVariable: size = 2 // Attributes + 2 // Index + stridx_size; // Name break; case Table.LocalConstant: size = stridx_size // Name + blobidx_size; // Signature break; case Table.ImportScope: size = GetTableIndexSize(Table.ImportScope) // Parent + blobidx_size; break; case Table.StateMachineMethod: size = GetTableIndexSize(Table.Method) // MoveNextMethod + GetTableIndexSize(Table.Method); // KickOffMethod break; case Table.CustomDebugInformation: size = GetCodedIndexSize(CodedIndex.HasCustomDebugInformation) // Parent + guididx_size // Kind + blobidx_size; // Value break; default: throw new NotSupportedException(); } tables[i].RowSize = (uint)size; tables[i].Offset = offset; offset += (uint)size * tables[i].Length; } } private void ReadPdbHeap() { PdbHeap heap = image.PdbHeap; ByteBuffer buffer = new(heap.data); heap.Id = buffer.ReadBytes(20); heap.EntryPoint = buffer.ReadUInt32(); heap.TypeSystemTables = buffer.ReadInt64(); heap.TypeSystemTableRows = new uint [Mixin.TableCount]; for (int i = 0; i < Mixin.TableCount; i++) { Table table = (Table)i; if (!heap.HasTable(table)) continue; heap.TypeSystemTableRows[i] = buffer.ReadUInt32(); } } public static Image ReadImage(Disposable stream, string file_name) { try { ImageReader reader = new(stream, file_name); reader.ReadImage(); return reader.image; } catch (EndOfStreamException e) { throw new BadImageFormatException(stream.value.GetFileName(), e); } } public static Image ReadPortablePdb(Disposable stream, string file_name) { try { ImageReader reader = new(stream, file_name); uint length = (uint)stream.value.Length; reader.image.Sections = new[] { new Section { PointerToRawData = 0, SizeOfRawData = length, VirtualAddress = 0, VirtualSize = length } }; reader.metadata = new(0, length); reader.ReadMetadata(); return reader.image; } catch (EndOfStreamException e) { throw new BadImageFormatException(stream.value.GetFileName(), e); } } } }