From f91efd15681dfbbd4f0125a10c8f0587a102044e Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Fri, 9 Mar 2018 12:42:56 +0000 Subject: [PATCH] Add decoding of section list, .edata, .idata and .debug from PE. Also recognize BeOS and Singularity executables. --- libexeinfo/PE/Info.cs | 226 ++++++++++++++++++++++--------- libexeinfo/PE/PE.cs | 278 +++++++++++++++++++++++++++++++++++---- libexeinfo/PE/Structs.cs | 116 +++++++++++++--- 3 files changed, 511 insertions(+), 109 deletions(-) diff --git a/libexeinfo/PE/Info.cs b/libexeinfo/PE/Info.cs index 21acf38..ae75f3c 100644 --- a/libexeinfo/PE/Info.cs +++ b/libexeinfo/PE/Info.cs @@ -24,81 +24,179 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using System.Linq; using System.Text; namespace libexeinfo { public partial class PE { - public string Information => GetInfo(Header, WinHeader, BaseExecutable); - - static string GetInfo(PEHeader header, WindowsHeader64 winheader, IExecutable baseExecutable) + public string Information { - StringBuilder sb = new StringBuilder(); - sb.Append(baseExecutable.Information); - sb.Append(COFF.GetInfo(header.coff)); - sb.AppendLine("Portable Executable (PE):"); - - if(header.coff.optionalHeader.magic == PE32Plus) - sb.AppendFormat("\tExecutable base address: 0x{0:X16}", winheader.imageBase).AppendLine(); - else sb.AppendFormat("\tExecutable base address: 0x{0:X8}", winheader.imageBase).AppendLine(); - sb.AppendFormat("\tSections are aligned to {0} bytes", winheader.sectionAlignment).AppendLine(); - sb.AppendFormat("\tFile is aligned to {0} bytes", winheader.fileAlignment).AppendLine(); - if(winheader.majorOperatingSystemVersion > 0 || winheader.minorOperatingSystemVersion > 0) - sb.AppendFormat("\tExecutable requires at least operating system version {0}.{1} to run", - winheader.majorOperatingSystemVersion, winheader.minorOperatingSystemVersion) + get + { + StringBuilder sb = new StringBuilder(); + sb.Append(baseExecutable.Information); + sb.Append(COFF.GetInfo(header.coff)); + sb.AppendLine("Portable Executable (PE):"); + if(!string.IsNullOrEmpty(moduleName)) sb.AppendFormat("\tModule name: {0}", moduleName).AppendLine(); + if(header.coff.optionalHeader.magic == PE32Plus) + sb.AppendFormat("\tExecutable base address: 0x{0:X16}", winHeader.imageBase).AppendLine(); + else sb.AppendFormat("\tExecutable base address: 0x{0:X8}", winHeader.imageBase).AppendLine(); + sb.AppendFormat("\tSections are aligned to {0} bytes", winHeader.sectionAlignment).AppendLine(); + sb.AppendFormat("\tFile is aligned to {0} bytes", winHeader.fileAlignment).AppendLine(); + if(winHeader.majorOperatingSystemVersion > 0 || winHeader.minorOperatingSystemVersion > 0) + sb.AppendFormat("\tExecutable requires at least operating system version {0}.{1} to run", + winHeader.majorOperatingSystemVersion, winHeader.minorOperatingSystemVersion) + .AppendLine(); + if(winHeader.majorImageVersion > 0 || winHeader.minorImageVersion > 0) + sb.AppendFormat("\tExecutable version: {0}.{1}", winHeader.majorImageVersion, + winHeader.minorImageVersion).AppendLine(); + sb.AppendFormat("\tAccording to subsystem, executable is {0}", SubsystemToString(winHeader.subsystem)) .AppendLine(); - if(winheader.majorImageVersion > 0 || winheader.minorImageVersion > 0) - sb.AppendFormat("\tExecutable version: {0}.{1}", winheader.majorImageVersion, - winheader.minorImageVersion).AppendLine(); - sb.AppendFormat("\tAccording to subsystem, executable is {0}", SubsystemToString(winheader.subsystem)) - .AppendLine(); - if(winheader.majorSubsystemVersion > 0 || winheader.minorSubsystemVersion > 0) - sb.AppendFormat("\tExecutable requires at least subsystem version {0}.{1} to run", - winheader.majorSubsystemVersion, winheader.minorSubsystemVersion).AppendLine(); + if(importedNames != null && importedNames.Contains("libbe.so")) + sb.AppendLine("\tExecutable is a BeOS R3 executable."); + if(importedNames != null && importedNames.Contains("Singularity.V1.dll")) + sb.AppendLine("\tExecutable is a Singularity executable."); + if(winHeader.majorSubsystemVersion > 0 || winHeader.minorSubsystemVersion > 0) + sb.AppendFormat("\tExecutable requires at least subsystem version {0}.{1} to run", + winHeader.majorSubsystemVersion, winHeader.minorSubsystemVersion).AppendLine(); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA)) - sb.AppendLine("\tExecutable can handle a high entropy 64-bit virtual address space"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)) - sb.AppendLine("\tExecutable can be relocated at load time"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY)) - sb.AppendLine("\tCode Integrity checks are enforced"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) - sb.AppendLine("\tExecutable is NX compatible"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_NO_ISOLATION)) - sb.AppendLine("\tExecutable is isolation aware, but should not be isolated"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_NO_SEH)) - sb.AppendLine("\tExecutable does not use structured exception handling"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_NO_BIND)) - sb.AppendLine("\tExecutable should not be binded"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_APPCONTAINER)) - sb.AppendLine("\tExecutable must be run inside an AppContainer"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_WDM_DRIVER)) - sb.AppendLine("\tExecutable contains a WDM driver"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_GUARD_CF)) - sb.AppendLine("\tExecutable supports Control Flow Guard"); - if(winheader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE)) - sb.AppendLine("\tExecutable is Terminal Server aware"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA)) + sb.AppendLine("\tExecutable can handle a high entropy 64-bit virtual address space"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)) + sb.AppendLine("\tExecutable can be relocated at load time"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY)) + sb.AppendLine("\tCode Integrity checks are enforced"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) + sb.AppendLine("\tExecutable is NX compatible"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_NO_ISOLATION)) + sb.AppendLine("\tExecutable is isolation aware, but should not be isolated"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_NO_SEH)) + sb.AppendLine("\tExecutable does not use structured exception handling"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_NO_BIND)) + sb.AppendLine("\tExecutable should not be binded"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_APPCONTAINER)) + sb.AppendLine("\tExecutable must be run inside an AppContainer"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_WDM_DRIVER)) + sb.AppendLine("\tExecutable contains a WDM driver"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics.IMAGE_DLLCHARACTERISTICS_GUARD_CF)) + sb.AppendLine("\tExecutable supports Control Flow Guard"); + if(winHeader.dllCharacteristics.HasFlag(DllCharacteristics + .IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE)) + sb.AppendLine("\tExecutable is Terminal Server aware"); - if(winheader.win32VersionValue > 0) - sb.AppendFormat("\tWin32 version value: {0}", winheader.win32VersionValue) - .AppendLine(); - sb.AppendFormat("\tExecutable is {0} bytes", winheader.sizeOfImage).AppendLine(); - sb.AppendFormat("\tHeaders are {0} bytes", winheader.sizeOfHeaders).AppendLine(); - sb.AppendFormat("\tChecksum: 0x{0:X8}", winheader.checksum).AppendLine(); - sb.AppendFormat("\t{0} bytes of stack should be reserved", winheader.sizeOfStackReserve) - .AppendLine(); - sb.AppendFormat("\t{0} bytes of stack should be committed", winheader.sizeOfStackCommit) - .AppendLine(); - sb.AppendFormat("\t{0} bytes of heap should be reserved", winheader.sizeOfHeapReserve) - .AppendLine(); - sb.AppendFormat("\t{0} bytes of heap should be committed", winheader.sizeOfHeapCommit) - .AppendLine(); - if(winheader.loaderFlags > 0) sb.AppendFormat("\tLoader flags: {0}", winheader.loaderFlags).AppendLine(); - sb.AppendFormat("\t{0} RVA entries follow the header", winheader.numberOfRvaAndSizes) - .AppendLine(); + if(winHeader.win32VersionValue > 0) + sb.AppendFormat("\tWin32 version value: {0}", winHeader.win32VersionValue).AppendLine(); + sb.AppendFormat("\tExecutable is {0} bytes", winHeader.sizeOfImage).AppendLine(); + sb.AppendFormat("\tHeaders are {0} bytes", winHeader.sizeOfHeaders).AppendLine(); + sb.AppendFormat("\tChecksum: 0x{0:X8}", winHeader.checksum).AppendLine(); + sb.AppendFormat("\t{0} bytes of stack should be reserved", winHeader.sizeOfStackReserve).AppendLine(); + sb.AppendFormat("\t{0} bytes of stack should be committed", winHeader.sizeOfStackCommit).AppendLine(); + sb.AppendFormat("\t{0} bytes of heap should be reserved", winHeader.sizeOfHeapReserve).AppendLine(); + sb.AppendFormat("\t{0} bytes of heap should be committed", winHeader.sizeOfHeapCommit).AppendLine(); + if(winHeader.loaderFlags > 0) + sb.AppendFormat("\tLoader flags: {0}", winHeader.loaderFlags).AppendLine(); + sb.AppendFormat("\t{0} RVA entries follow the header", winHeader.numberOfRvaAndSizes).AppendLine(); - return sb.ToString(); + for(int i = 0; i < directoryEntries.Length; i++) + { + string tableName; + switch(i) + { + case 0: + tableName = "Export table"; + break; + case 1: + tableName = "Import table"; + break; + case 2: + tableName = "Resource table"; + break; + case 3: + tableName = "Exception table"; + break; + case 4: + tableName = "Certificate table"; + break; + case 5: + tableName = "Base relocation table"; + break; + case 6: + tableName = "Debug data"; + break; + case 7: + tableName = "Architecture-specific data"; + break; + case 8: + tableName = "Global pointer register"; + break; + case 9: + tableName = "Thread local storage table"; + break; + case 10: + tableName = "Load configuration table"; + break; + case 11: + tableName = "Bound import table"; + break; + case 12: + tableName = "Import address table"; + break; + case 13: + tableName = "Delay import descriptor"; + break; + case 14: + tableName = "CLR runtime header"; + break; + default: + tableName = "Unknown table"; + break; + } + + if(directoryEntries[i].rva == 0) + sb.AppendFormat("\tImage does not contain {0}", tableName).AppendLine(); + else + sb.AppendFormat("\t{0} starts at virtual address {1} ({2} physical offset) and has {3} bytes", + tableName, directoryEntries[i].rva, + RvaToReal(directoryEntries[i].rva, sectionHeaders), directoryEntries[i].size) + .AppendLine(); + } + + sb.AppendFormat("\t{0} sections:", sectionHeaders.Length).AppendLine(); + for(int i = 0; i < sectionHeaders.Length; i++) + { + sb.AppendFormat("\t\tSection {0}:", i).AppendLine(); + sb.AppendFormat("\t\t\tName: {0}", sectionHeaders[i].name).AppendLine(); + sb.AppendFormat("\t\t\tCharacteristics: {0}", sectionHeaders[i].characteristics).AppendLine(); + sb.AppendFormat("\t\t\t{0} relocations start at {1}", sectionHeaders[i].numberOfRelocations, + sectionHeaders[i].pointerToRelocations).AppendLine(); + sb.AppendFormat("\t\t\t{0} line numbers start at {1}", sectionHeaders[i].numberOfLineNumbers, + sectionHeaders[i].pointerToRelocations).AppendLine(); + sb.AppendFormat("\t\t\tRaw data starts at {0} and has {1} bytes", + sectionHeaders[i].pointerToRawData, sectionHeaders[i].sizeOfRawData).AppendLine(); + sb.AppendFormat("\t\t\tVirtual address: {0}", sectionHeaders[i].virtualAddress).AppendLine(); + sb.AppendFormat("\t\t\tVirtual size: {0} bytes", sectionHeaders[i].virtualSize).AppendLine(); + } + + if(exportedNames != null) + { + sb.AppendLine("\tExported functions:"); + foreach(string name in exportedNames) sb.AppendFormat("\t\t{0}", name).AppendLine(); + } + + if(importedNames != null) + { + sb.AppendLine("\tImported libraries:"); + foreach(string name in importedNames) sb.AppendFormat("\t\t{0}", name).AppendLine(); + } + + if(debugDirectory.pointerToRawData > 0) + sb.AppendFormat("\tExecutable contains debug information of type {0}", debugDirectory.type) + .AppendLine(); + + return sb.ToString(); + } } } } \ No newline at end of file diff --git a/libexeinfo/PE/PE.cs b/libexeinfo/PE/PE.cs index ebc8812..51587c7 100644 --- a/libexeinfo/PE/PE.cs +++ b/libexeinfo/PE/PE.cs @@ -27,21 +27,31 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.InteropServices; +using System.Text; namespace libexeinfo { /// /// Represents a Microsoft Portable Executable /// + // TODO: Process BeOS resources + // TODO: Process Windows resources public partial class PE : IExecutable { - MZ BaseExecutable; + MZ baseExecutable; + DebugDirectory debugDirectory; + ImageDataDirectory[] directoryEntries; + string[] exportedNames; /// /// Header for this executable /// - PEHeader Header; - WindowsHeader64 WinHeader; + PEHeader header; + string[] importedNames; + string moduleName; + COFF.SectionHeader[] sectionHeaders; + WindowsHeader64 winHeader; /// /// Initializes a new instance of the class. @@ -73,47 +83,47 @@ namespace libexeinfo Initialize(); } - public Stream BaseStream { get; } - public bool IsBigEndian => false; - public bool Recognized { get; private set; } - public string Type { get; private set; } + public Stream BaseStream { get; } + public bool IsBigEndian => false; + public bool Recognized { get; private set; } + public string Type { get; private set; } public IEnumerable Architectures => - new[] {COFF.MachineTypeToArchitecture(Header.coff.machine)}; - public OperatingSystem RequiredOperatingSystem { get; private set; } - public IEnumerable Strings { get; } - public IEnumerable Segments { get; } + new[] {COFF.MachineTypeToArchitecture(header.coff.machine)}; + public OperatingSystem RequiredOperatingSystem { get; private set; } + public IEnumerable Strings { get; private set; } + public IEnumerable Segments { get; private set; } void Initialize() { Recognized = false; if(BaseStream == null) return; - BaseExecutable = new MZ(BaseStream); - if(!BaseExecutable.Recognized) return; + baseExecutable = new MZ(BaseStream); + if(!baseExecutable.Recognized) return; - if(BaseExecutable.Header.new_offset >= BaseStream.Length) return; + if(baseExecutable.Header.new_offset >= BaseStream.Length) return; - BaseStream.Seek(BaseExecutable.Header.new_offset, SeekOrigin.Begin); + BaseStream.Seek(baseExecutable.Header.new_offset, SeekOrigin.Begin); byte[] buffer = new byte[Marshal.SizeOf(typeof(PEHeader))]; BaseStream.Read(buffer, 0, buffer.Length); IntPtr hdrPtr = Marshal.AllocHGlobal(buffer.Length); Marshal.Copy(buffer, 0, hdrPtr, buffer.Length); - Header = (PEHeader)Marshal.PtrToStructure(hdrPtr, typeof(PEHeader)); + header = (PEHeader)Marshal.PtrToStructure(hdrPtr, typeof(PEHeader)); Marshal.FreeHGlobal(hdrPtr); - Recognized = Header.signature == SIGNATURE; + Recognized = header.signature == SIGNATURE; if(!Recognized) return; Type = "Portable Executable (PE)"; - if(Header.coff.optionalHeader.magic == PE32Plus) + if(header.coff.optionalHeader.magic == PE32Plus) { BaseStream.Position -= 4; buffer = new byte[Marshal.SizeOf(typeof(WindowsHeader64))]; BaseStream.Read(buffer, 0, buffer.Length); hdrPtr = Marshal.AllocHGlobal(buffer.Length); Marshal.Copy(buffer, 0, hdrPtr, buffer.Length); - WinHeader = (WindowsHeader64)Marshal.PtrToStructure(hdrPtr, typeof(WindowsHeader64)); + winHeader = (WindowsHeader64)Marshal.PtrToStructure(hdrPtr, typeof(WindowsHeader64)); Marshal.FreeHGlobal(hdrPtr); } else @@ -124,12 +134,12 @@ namespace libexeinfo Marshal.Copy(buffer, 0, hdrPtr, buffer.Length); WindowsHeader hdr32 = (WindowsHeader)Marshal.PtrToStructure(hdrPtr, typeof(WindowsHeader)); Marshal.FreeHGlobal(hdrPtr); - WinHeader = ToPlus(hdr32); + winHeader = ToPlus(hdr32); } OperatingSystem reqOs = new OperatingSystem(); - switch(WinHeader.subsystem) + switch(winHeader.subsystem) { case Subsystems.IMAGE_SUBSYSTEM_UNKNOWN: reqOs.Name = "Unknown"; @@ -139,11 +149,11 @@ namespace libexeinfo reqOs.Subsystem = "Native"; break; case Subsystems.IMAGE_SUBSYSTEM_WINDOWS_GUI: - reqOs.Name = WinHeader.majorOperatingSystemVersion < 3 ? "Windows NT" : "Windows"; + reqOs.Name = winHeader.majorSubsystemVersion <= 3 ? "Windows NT" : "Windows"; reqOs.Subsystem = "GUI"; break; case Subsystems.IMAGE_SUBSYSTEM_WINDOWS_CUI: - reqOs.Name = WinHeader.majorOperatingSystemVersion < 3 ? "Windows NT" : "Windows"; + reqOs.Name = winHeader.majorSubsystemVersion <= 3 ? "Windows NT" : "Windows"; reqOs.Subsystem = "Console"; break; case Subsystems.IMAGE_SUBSYSTEM_OS2_CUI: @@ -175,13 +185,220 @@ namespace libexeinfo reqOs.Subsystem = "Boot environment"; break; default: - reqOs.Name = $"Unknown code ${(ushort)WinHeader.subsystem}"; + reqOs.Name = $"Unknown code ${(ushort)winHeader.subsystem}"; break; } - reqOs.MajorVersion = WinHeader.majorOperatingSystemVersion; - reqOs.MinorVersion = WinHeader.minorOperatingSystemVersion; + reqOs.MajorVersion = winHeader.majorSubsystemVersion; + reqOs.MinorVersion = winHeader.minorSubsystemVersion; RequiredOperatingSystem = reqOs; + + buffer = new byte[Marshal.SizeOf(typeof(ImageDataDirectory))]; + directoryEntries = new ImageDataDirectory[winHeader.numberOfRvaAndSizes]; + for(int i = 0; i < directoryEntries.Length; i++) + { + BaseStream.Read(buffer, 0, buffer.Length); + directoryEntries[i] = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + } + + buffer = new byte[Marshal.SizeOf(typeof(COFF.SectionHeader))]; + sectionHeaders = new COFF.SectionHeader[header.coff.numberOfSections]; + for(int i = 0; i < sectionHeaders.Length; i++) + { + BaseStream.Read(buffer, 0, buffer.Length); + sectionHeaders[i] = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + } + + Dictionary newSectionHeaders = + sectionHeaders.ToDictionary(section => section.name); + + for(int i = 0; i < directoryEntries.Length; i++) + { + string tableName; + switch(i) + { + case 0: + tableName = ".edata"; + break; + case 1: + tableName = ".idata"; + break; + case 2: + tableName = ".rsrc"; + break; + case 3: + tableName = ".pdata"; + break; + case 5: + tableName = ".reloc"; + break; + case 6: + tableName = ".debug"; + break; + case 9: + tableName = ".tls"; + break; + case 14: + tableName = ".cormeta"; + break; + default: continue; + } + + if(newSectionHeaders.ContainsKey(tableName)) continue; + if(directoryEntries[i].rva == 0) continue; + + newSectionHeaders.Add(tableName, + new COFF.SectionHeader + { + characteristics = + COFF.SectionFlags.IMAGE_SCN_CNT_INITIALIZED_DATA | + COFF.SectionFlags.IMAGE_SCN_MEM_READ, + name = tableName, + pointerToRawData = RvaToReal(directoryEntries[i].rva, sectionHeaders), + virtualAddress = directoryEntries[i].rva, + sizeOfRawData = directoryEntries[i].size, + virtualSize = directoryEntries[i].size + }); + } + + List chars; + List strings = new List(); + + if(newSectionHeaders.TryGetValue(".edata", out COFF.SectionHeader edata)) + { + buffer = new byte[Marshal.SizeOf(typeof(ExportDirectoryTable))]; + BaseStream.Position = edata.pointerToRawData; + BaseStream.Read(buffer, 0, buffer.Length); + ExportDirectoryTable edataTable = + BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + + BaseStream.Position = RvaToReal(edataTable.nameRva, sectionHeaders); + chars = new List(); + while(true) + { + int ch = BaseStream.ReadByte(); + if(ch <= 0) break; + + chars.Add((byte)ch); + } + + moduleName = Encoding.ASCII.GetString(chars.ToArray()); + + uint[] namePointers = new uint[edataTable.numberOfNamePointers]; + exportedNames = new string[edataTable.numberOfNamePointers]; + buffer = new byte[Marshal.SizeOf(typeof(uint)) * edataTable.numberOfNamePointers]; + BaseStream.Position = RvaToReal(edataTable.namePointerRva, sectionHeaders); + BaseStream.Read(buffer, 0, buffer.Length); + for(int i = 0; i < edataTable.numberOfNamePointers; i++) + { + namePointers[i] = BitConverter.ToUInt32(buffer, i * 4); + BaseStream.Position = RvaToReal(namePointers[i], sectionHeaders); + + chars = new List(); + while(true) + { + int ch = BaseStream.ReadByte(); + if(ch <= 0) break; + + chars.Add((byte)ch); + } + + exportedNames[i] = Encoding.ASCII.GetString(chars.ToArray()); + } + } + + if(newSectionHeaders.TryGetValue(".idata", out COFF.SectionHeader idata)) + { + buffer = new byte[Marshal.SizeOf(typeof(ImportDirectoryTable))]; + BaseStream.Position = idata.pointerToRawData; + List importDirectoryEntries = new List(); + + while(true) + { + BaseStream.Read(buffer, 0, buffer.Length); + if(buffer.All(b => b == 0)) break; + + importDirectoryEntries.Add(BigEndianMarshal + .ByteArrayToStructureLittleEndian(buffer)); + } + + importedNames = new string[importDirectoryEntries.Count]; + for(int i = 0; i < importDirectoryEntries.Count; i++) + { + BaseStream.Position = RvaToReal(importDirectoryEntries[i].nameRva, sectionHeaders); + + chars = new List(); + while(true) + { + int ch = BaseStream.ReadByte(); + if(ch <= 0) break; + + chars.Add((byte)ch); + } + + importedNames[i] = Encoding.ASCII.GetString(chars.ToArray()); + + // BeOS R3 uses PE with no subsystem + if(importedNames[i].ToLower() == "libbe.so") + { + reqOs.MajorVersion = 3; + reqOs.MinorVersion = 0; + reqOs.Subsystem = null; + reqOs.Name = "BeOS"; + RequiredOperatingSystem = reqOs; + } + // Singularity appears as a native NT executable + else if(importedNames[i].ToLower() == "singularity.v1.dll") + { + reqOs.MajorVersion = 1; + reqOs.MinorVersion = 0; + reqOs.Subsystem = null; + reqOs.Name = "Singularity"; + RequiredOperatingSystem = reqOs; + } + } + } + + if(newSectionHeaders.TryGetValue(".debug", out COFF.SectionHeader debug) && debug.virtualAddress > 0) + { + buffer = new byte[Marshal.SizeOf(typeof(DebugDirectory))]; + BaseStream.Position = debug.pointerToRawData; + BaseStream.Read(buffer, 0, buffer.Length); + debugDirectory = BigEndianMarshal.ByteArrayToStructureLittleEndian(buffer); + } + + // BeOS .rsrc is not virtual addressing, and has no size, solve it + if(reqOs.Name == "BeOS" && newSectionHeaders.ContainsKey(".rsrc")) + { + newSectionHeaders.TryGetValue(".rsrc", out COFF.SectionHeader beRsrc); + newSectionHeaders.Remove(".rsrc"); + beRsrc.pointerToRawData = beRsrc.virtualAddress; + + long maxPosition = BaseStream.Length; + foreach(KeyValuePair kvp in newSectionHeaders) + if(kvp.Value.pointerToRawData <= maxPosition && + kvp.Value.pointerToRawData > beRsrc.pointerToRawData) + maxPosition = kvp.Value.pointerToRawData; + + beRsrc.sizeOfRawData = (uint)(maxPosition - beRsrc.pointerToRawData); + beRsrc.virtualSize = beRsrc.sizeOfRawData; + newSectionHeaders.Add(".rsrc", beRsrc); + } + + sectionHeaders = newSectionHeaders.Values.OrderBy(s => s.pointerToRawData).ToArray(); + Segment[] segments = new Segment[sectionHeaders.Length]; + for(int i = 0; i < segments.Length; i++) + segments[i] = new Segment + { + Flags = $"{sectionHeaders[i].characteristics}", + Name = sectionHeaders[i].name, + Offset = sectionHeaders[i].pointerToRawData, + Size = sectionHeaders[i].sizeOfRawData + }; + + Segments = segments; + strings.Sort(); + Strings = strings; } /// @@ -257,5 +474,14 @@ namespace libexeinfo numberOfRvaAndSizes = header.numberOfRvaAndSizes }; } + + static uint RvaToReal(uint rva, COFF.SectionHeader[] sections) + { + for(int i = 0; i < sections.Length; i++) + if(rva >= sections[i].virtualAddress && rva <= sections[i].virtualAddress + sections[i].sizeOfRawData) + return sections[i].pointerToRawData + (rva - sections[i].virtualAddress); + + return 0; + } } } \ No newline at end of file diff --git a/libexeinfo/PE/Structs.cs b/libexeinfo/PE/Structs.cs index 89d2ec6..4fdaf6d 100644 --- a/libexeinfo/PE/Structs.cs +++ b/libexeinfo/PE/Structs.cs @@ -34,14 +34,14 @@ namespace libexeinfo /// /// Header for a Microsoft New Executable /// - [StructLayout(LayoutKind.Sequential /*, Pack = 2*/)] - public struct PEHeader + [StructLayout(LayoutKind.Sequential)] + struct PEHeader { /// /// After the MS-DOS stub, at the file offset specified at offset 0x3c, is a 4-byte signature that identifies the file /// as a PE format image file. This signature is "PE\0\0" (the letters "P" and "E" followed by two null bytes). /// - public uint signature; + public uint signature; public COFFHeader coff; } @@ -49,8 +49,8 @@ namespace libexeinfo /// The next 21 fields are an extension to the COFF optional header format. They contain additional information that is /// required by the linker and loader in Windows. /// - [StructLayout(LayoutKind.Sequential /*, Pack = 2*/)] - public struct WindowsHeader + [StructLayout(LayoutKind.Sequential)] + struct WindowsHeader { /// /// The preferred address of the first byte of image when loaded into memory; must be a multiple of 64 K. The default @@ -152,8 +152,8 @@ namespace libexeinfo /// The next 21 fields are an extension to the COFF optional header format. They contain additional information that is /// required by the linker and loader in Windows. /// - [StructLayout(LayoutKind.Sequential /*, Pack = 2*/)] - public struct WindowsHeader64 + [StructLayout(LayoutKind.Sequential)] + struct WindowsHeader64 { /// /// The preferred address of the first byte of image when loaded into memory; must be a multiple of 64 K. The default @@ -251,21 +251,21 @@ namespace libexeinfo public uint numberOfRvaAndSizes; } - [StructLayout(LayoutKind.Sequential /*, Pack = 2*/)] - public struct ImageDataDirectory + [StructLayout(LayoutKind.Sequential)] + struct ImageDataDirectory { /// /// Relative virtual address of the table /// - uint rva; + public uint rva; /// /// The size in bytes /// - uint size; + public uint size; } - [StructLayout(LayoutKind.Sequential /*, Pack = 2*/)] - public struct DebugDirectory + [StructLayout(LayoutKind.Sequential)] + struct DebugDirectory { /// /// A reserved field intended to be used for flags, set to zero for now. @@ -302,8 +302,8 @@ namespace libexeinfo public uint pointerToRawData; } - [StructLayout(LayoutKind.Sequential /*, Pack = 2*/)] - public struct ResourceDirectoryTable + [StructLayout(LayoutKind.Sequential)] + struct ResourceDirectoryTable { /// /// Resource flags, reserved for future use; currently set to zero. @@ -334,8 +334,8 @@ namespace libexeinfo public ushort idEntries; } - [StructLayout(LayoutKind.Sequential /*, Pack = 2*/)] - public struct ResourceDirectoryEntries + [StructLayout(LayoutKind.Sequential)] + struct ResourceDirectoryEntries { /// /// Address of string that gives the Type, Name, or Language identifier, depending on level of table. @@ -350,8 +350,8 @@ namespace libexeinfo public uint rva; } - [StructLayout(LayoutKind.Sequential /*, Pack = 2*/)] - public struct ResourceDataEntry + [StructLayout(LayoutKind.Sequential)] + struct ResourceDataEntry { /// /// Address of a unit of resource data in the Resource Data area. @@ -371,5 +371,83 @@ namespace libexeinfo /// public uint reserved; } + + [StructLayout(LayoutKind.Sequential)] + struct ExportDirectoryTable + { + /// + /// Reserved, must be 0. + /// + public uint exportFlags; + /// + /// The time and date that the export data was created. + /// + public uint timestamp; + /// + /// The major version number. The major and minor version numbers can be set by the user. + /// + public ushort majorVersion; + /// + /// The minor version number. + /// + public ushort minorVersion; + /// + /// The address of the ASCII string that contains the name of the DLL. This address is relative to the image base. + /// + public uint nameRva; + /// + /// The starting ordinal number for exports in this image. This field specifies the starting ordinal number for the + /// export address table. It is usually set to 1. + /// + public uint ordinalBase; + /// + /// The number of entries in the export address table. + /// + public uint addressTableEntries; + /// + /// The number of entries in the name pointer table. This is also the number of entries in the ordinal table. + /// + public uint numberOfNamePointers; + /// + /// The address of the export address table, relative to the image base. + /// + public uint exportAddressTableRva; + /// + /// The address of the export name pointer table, relative to the image base. The table size is given by the Number of + /// Name Pointers field. + /// + public uint namePointerRva; + /// + /// The address of the ordinal table, relative to the image base. + /// + public uint ordinalTableRva; + } + + [StructLayout(LayoutKind.Sequential)] + struct ImportDirectoryTable + { + /// + /// The RVA of the import lookup table. This table contains a name or ordinal for each import. + /// + public uint importLookupTableRva; + /// + /// The stamp that is set to zero until the image is bound. After the image is bound, this field is set to the + /// time/data stamp of the DLL. + /// + public uint timestamp; + /// + /// The index of the first forwarder reference. + /// + public uint forwardedChain; + /// + /// The address of an ASCII string that contains the name of the DLL. This address is relative to the image base. + /// + public uint nameRva; + /// + /// The RVA of the import address table. The contents of this table are identical to the contents of the import lookup + /// table until the image is bound. + /// + public uint importAddressTableRva; + } } } \ No newline at end of file