From f1a2fca4f3d97e97f48a289c314f6168afb57b92 Mon Sep 17 00:00:00 2001 From: bird_egop Date: Sat, 12 Apr 2025 17:12:18 +0300 Subject: [PATCH] Refactor PEFormat into smaller classes following Single Responsibility Principle --- X86Disassembler/PE/PEFormat.cs | 578 ++---------------- X86Disassembler/PE/PEUtility.cs | 62 ++ X86Disassembler/PE/Parsers/DOSHeaderParser.cs | 63 ++ .../PE/Parsers/ExportDirectoryParser.cs | 176 ++++++ .../PE/Parsers/FileHeaderParser.cs | 30 + X86Disassembler/PE/Parsers/IParser.cs | 18 + .../PE/Parsers/ImportDescriptorParser.cs | 192 ++++++ .../PE/Parsers/OptionalHeaderParser.cs | 114 ++++ .../PE/Parsers/SectionHeaderParser.cs | 52 ++ 9 files changed, 746 insertions(+), 539 deletions(-) create mode 100644 X86Disassembler/PE/PEUtility.cs create mode 100644 X86Disassembler/PE/Parsers/DOSHeaderParser.cs create mode 100644 X86Disassembler/PE/Parsers/ExportDirectoryParser.cs create mode 100644 X86Disassembler/PE/Parsers/FileHeaderParser.cs create mode 100644 X86Disassembler/PE/Parsers/IParser.cs create mode 100644 X86Disassembler/PE/Parsers/ImportDescriptorParser.cs create mode 100644 X86Disassembler/PE/Parsers/OptionalHeaderParser.cs create mode 100644 X86Disassembler/PE/Parsers/SectionHeaderParser.cs diff --git a/X86Disassembler/PE/PEFormat.cs b/X86Disassembler/PE/PEFormat.cs index 00c64c6..257281f 100644 --- a/X86Disassembler/PE/PEFormat.cs +++ b/X86Disassembler/PE/PEFormat.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using X86Disassembler.PE.Parsers; namespace X86Disassembler.PE { @@ -44,6 +45,15 @@ namespace X86Disassembler.PE // PE file data private byte[] _fileData; + // Parser instances + private readonly DOSHeaderParser _dosHeaderParser; + private readonly FileHeaderParser _fileHeaderParser; + private readonly OptionalHeaderParser _optionalHeaderParser; + private readonly SectionHeaderParser _sectionHeaderParser; + private PEUtility _peUtility; + private ExportDirectoryParser _exportDirectoryParser; + private ImportDescriptorParser _importDescriptorParser; + // Parsed headers public DOSHeader DosHeader { get; private set; } public FileHeader FileHeader { get; private set; } @@ -66,6 +76,12 @@ namespace X86Disassembler.PE SectionHeaders = new List(); ExportedFunctions = new List(); ImportDescriptors = new List(); + + // Initialize parsers + _dosHeaderParser = new DOSHeaderParser(); + _fileHeaderParser = new FileHeaderParser(); + _optionalHeaderParser = new OptionalHeaderParser(); + _sectionHeaderParser = new SectionHeaderParser(); } /// @@ -80,7 +96,7 @@ namespace X86Disassembler.PE using (BinaryReader reader = new BinaryReader(stream)) { // Parse DOS header - DosHeader = ParseDOSHeader(reader); + DosHeader = _dosHeaderParser.Parse(reader); // Move to PE header reader.BaseStream.Seek(DosHeader.e_lfanew, SeekOrigin.Begin); @@ -93,30 +109,40 @@ namespace X86Disassembler.PE } // Parse File Header - FileHeader = ParseFileHeader(reader); + FileHeader = _fileHeaderParser.Parse(reader); // Parse Optional Header - OptionalHeader = ParseOptionalHeader(reader); + OptionalHeader = _optionalHeaderParser.Parse(reader); + Is64Bit = _optionalHeaderParser.Is64Bit(OptionalHeader); // Parse Section Headers for (int i = 0; i < FileHeader.NumberOfSections; i++) { - SectionHeaders.Add(ParseSectionHeader(reader)); + SectionHeaders.Add(_sectionHeaderParser.Parse(reader)); } + // Initialize utility after section headers are parsed + _peUtility = new PEUtility(SectionHeaders, OptionalHeader.SizeOfHeaders); + _exportDirectoryParser = new ExportDirectoryParser(_peUtility); + _importDescriptorParser = new ImportDescriptorParser(_peUtility); + // Parse Export Directory if (OptionalHeader.DataDirectories.Length > IMAGE_DIRECTORY_ENTRY_EXPORT && OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress != 0) { - ExportDirectory = ParseExportDirectory(reader, OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); - ParseExportedFunctions(reader); + uint exportDirRva = OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + uint exportDirSize = OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + + ExportDirectory = _exportDirectoryParser.Parse(reader, exportDirRva); + ExportedFunctions = _exportDirectoryParser.ParseExportedFunctions(reader, ExportDirectory, exportDirRva, exportDirSize); } // Parse Import Descriptors if (OptionalHeader.DataDirectories.Length > IMAGE_DIRECTORY_ENTRY_IMPORT && OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress != 0) { - ImportDescriptors = ParseImportDescriptors(reader, OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + uint importDirRva = OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + ImportDescriptors = _importDescriptorParser.Parse(reader, importDirRva); } } @@ -129,494 +155,6 @@ namespace X86Disassembler.PE } } - /// - /// Parses the DOS header - /// - private DOSHeader ParseDOSHeader(BinaryReader reader) - { - DOSHeader header = new DOSHeader(); - - header.e_magic = reader.ReadUInt16(); - if (header.e_magic != DOS_SIGNATURE) - { - throw new InvalidDataException("Invalid DOS signature (MZ)"); - } - - header.e_cblp = reader.ReadUInt16(); - header.e_cp = reader.ReadUInt16(); - header.e_crlc = reader.ReadUInt16(); - header.e_cparhdr = reader.ReadUInt16(); - header.e_minalloc = reader.ReadUInt16(); - header.e_maxalloc = reader.ReadUInt16(); - header.e_ss = reader.ReadUInt16(); - header.e_sp = reader.ReadUInt16(); - header.e_csum = reader.ReadUInt16(); - header.e_ip = reader.ReadUInt16(); - header.e_cs = reader.ReadUInt16(); - header.e_lfarlc = reader.ReadUInt16(); - header.e_ovno = reader.ReadUInt16(); - - header.e_res = new ushort[4]; - for (int i = 0; i < 4; i++) - { - header.e_res[i] = reader.ReadUInt16(); - } - - header.e_oemid = reader.ReadUInt16(); - header.e_oeminfo = reader.ReadUInt16(); - - header.e_res2 = new ushort[10]; - for (int i = 0; i < 10; i++) - { - header.e_res2[i] = reader.ReadUInt16(); - } - - header.e_lfanew = reader.ReadUInt32(); - - return header; - } - - /// - /// Parses the File header - /// - private FileHeader ParseFileHeader(BinaryReader reader) - { - FileHeader header = new FileHeader(); - - header.Machine = reader.ReadUInt16(); - header.NumberOfSections = reader.ReadUInt16(); - header.TimeDateStamp = reader.ReadUInt32(); - header.PointerToSymbolTable = reader.ReadUInt32(); - header.NumberOfSymbols = reader.ReadUInt32(); - header.SizeOfOptionalHeader = reader.ReadUInt16(); - header.Characteristics = reader.ReadUInt16(); - - return header; - } - - /// - /// Parses the Optional header - /// - private OptionalHeader ParseOptionalHeader(BinaryReader reader) - { - OptionalHeader header = new OptionalHeader(); - - // Standard fields - header.Magic = reader.ReadUInt16(); - - // Determine if this is a PE32 or PE32+ file - Is64Bit = header.Magic == PE32PLUS_MAGIC; - - header.MajorLinkerVersion = reader.ReadByte(); - header.MinorLinkerVersion = reader.ReadByte(); - header.SizeOfCode = reader.ReadUInt32(); - header.SizeOfInitializedData = reader.ReadUInt32(); - header.SizeOfUninitializedData = reader.ReadUInt32(); - header.AddressOfEntryPoint = reader.ReadUInt32(); - header.BaseOfCode = reader.ReadUInt32(); - - // PE32 has BaseOfData, PE32+ doesn't - if (!Is64Bit) - { - header.BaseOfData = reader.ReadUInt32(); - } - - // Windows-specific fields - if (Is64Bit) - { - header.ImageBase = reader.ReadUInt64(); - } - else - { - header.ImageBase = reader.ReadUInt32(); - } - - header.SectionAlignment = reader.ReadUInt32(); - header.FileAlignment = reader.ReadUInt32(); - header.MajorOperatingSystemVersion = reader.ReadUInt16(); - header.MinorOperatingSystemVersion = reader.ReadUInt16(); - header.MajorImageVersion = reader.ReadUInt16(); - header.MinorImageVersion = reader.ReadUInt16(); - header.MajorSubsystemVersion = reader.ReadUInt16(); - header.MinorSubsystemVersion = reader.ReadUInt16(); - header.Win32VersionValue = reader.ReadUInt32(); - header.SizeOfImage = reader.ReadUInt32(); - header.SizeOfHeaders = reader.ReadUInt32(); - header.CheckSum = reader.ReadUInt32(); - header.Subsystem = reader.ReadUInt16(); - header.DllCharacteristics = reader.ReadUInt16(); - - // Size fields differ between PE32 and PE32+ - if (Is64Bit) - { - header.SizeOfStackReserve = reader.ReadUInt64(); - header.SizeOfStackCommit = reader.ReadUInt64(); - header.SizeOfHeapReserve = reader.ReadUInt64(); - header.SizeOfHeapCommit = reader.ReadUInt64(); - } - else - { - header.SizeOfStackReserve = reader.ReadUInt32(); - header.SizeOfStackCommit = reader.ReadUInt32(); - header.SizeOfHeapReserve = reader.ReadUInt32(); - header.SizeOfHeapCommit = reader.ReadUInt32(); - } - - header.LoaderFlags = reader.ReadUInt32(); - header.NumberOfRvaAndSizes = reader.ReadUInt32(); - - // Data directories - int numDirectories = (int)Math.Min(header.NumberOfRvaAndSizes, 16); // Maximum of 16 directories - header.DataDirectories = new DataDirectory[numDirectories]; - - for (int i = 0; i < numDirectories; i++) - { - DataDirectory dir = new DataDirectory(); - dir.VirtualAddress = reader.ReadUInt32(); - dir.Size = reader.ReadUInt32(); - header.DataDirectories[i] = dir; - } - - return header; - } - - /// - /// Parses a section header - /// - private SectionHeader ParseSectionHeader(BinaryReader reader) - { - SectionHeader header = new SectionHeader(); - - // Read section name (8 bytes) - byte[] nameBytes = reader.ReadBytes(8); - // Convert to string, removing any null characters - header.Name = Encoding.ASCII.GetString(nameBytes).TrimEnd('\0'); - - header.VirtualSize = reader.ReadUInt32(); - header.VirtualAddress = reader.ReadUInt32(); - header.SizeOfRawData = reader.ReadUInt32(); - header.PointerToRawData = reader.ReadUInt32(); - header.PointerToRelocations = reader.ReadUInt32(); - header.PointerToLinenumbers = reader.ReadUInt32(); - header.NumberOfRelocations = reader.ReadUInt16(); - header.NumberOfLinenumbers = reader.ReadUInt16(); - header.Characteristics = reader.ReadUInt32(); - - return header; - } - - /// - /// Parses the Export Directory - /// - private ExportDirectory ParseExportDirectory(BinaryReader reader, uint rva) - { - ExportDirectory directory = new ExportDirectory(); - - reader.BaseStream.Seek(RvaToOffset(rva), SeekOrigin.Begin); - - directory.Characteristics = reader.ReadUInt32(); - directory.TimeDateStamp = reader.ReadUInt32(); - directory.MajorVersion = reader.ReadUInt16(); - directory.MinorVersion = reader.ReadUInt16(); - directory.Name = reader.ReadUInt32(); - directory.Base = reader.ReadUInt32(); - directory.NumberOfFunctions = reader.ReadUInt32(); - directory.NumberOfNames = reader.ReadUInt32(); - directory.AddressOfFunctions = reader.ReadUInt32(); - directory.AddressOfNames = reader.ReadUInt32(); - directory.AddressOfNameOrdinals = reader.ReadUInt32(); - - // Read the DLL name - try - { - uint dllNameRVA = directory.Name; - uint dllNameOffset = RvaToOffset(dllNameRVA); - reader.BaseStream.Seek(dllNameOffset, SeekOrigin.Begin); - - // Read the null-terminated ASCII string - StringBuilder nameBuilder = new StringBuilder(); - byte b; - - while ((b = reader.ReadByte()) != 0) - { - nameBuilder.Append((char)b); - } - - directory.DllName = nameBuilder.ToString(); - } - catch (Exception) - { - directory.DllName = "Unknown"; - } - - return directory; - } - - /// - /// Parses the Import Descriptors - /// - private List ParseImportDescriptors(BinaryReader reader, uint rva) - { - List descriptors = new List(); - - try - { - uint importTableOffset = RvaToOffset(rva); - reader.BaseStream.Seek(importTableOffset, SeekOrigin.Begin); - - int descriptorCount = 0; - - while (true) - { - descriptorCount++; - - // Read the import descriptor - uint originalFirstThunk = reader.ReadUInt32(); - uint timeDateStamp = reader.ReadUInt32(); - uint forwarderChain = reader.ReadUInt32(); - uint nameRva = reader.ReadUInt32(); - uint firstThunk = reader.ReadUInt32(); - - // Check if we've reached the end of the import descriptors - if (originalFirstThunk == 0 && nameRva == 0 && firstThunk == 0) - { - break; - } - - ImportDescriptor descriptor = new ImportDescriptor - { - OriginalFirstThunk = originalFirstThunk, - TimeDateStamp = timeDateStamp, - ForwarderChain = forwarderChain, - Name = nameRva, - FirstThunk = firstThunk, - DllName = "Unknown" // Default name in case we can't read it - }; - - // Try to read the DLL name - try - { - if (nameRva != 0) - { - uint nameOffset = RvaToOffset(nameRva); - reader.BaseStream.Seek(nameOffset, SeekOrigin.Begin); - - // Read the null-terminated ASCII string - StringBuilder nameBuilder = new StringBuilder(); - byte b; - - while ((b = reader.ReadByte()) != 0) - { - nameBuilder.Append((char)b); - } - - descriptor.DllName = nameBuilder.ToString(); - } - } - catch (Exception) - { - // If we can't read the name, keep the default "Unknown" - } - - // Parse the imported functions - ParseImportedFunctions(reader, descriptor); - - descriptors.Add(descriptor); - - // Return to the import table to read the next descriptor - reader.BaseStream.Seek(importTableOffset + (descriptorCount * 20), SeekOrigin.Begin); - } - } - catch (Exception ex) - { - Console.WriteLine($"Error parsing import descriptors: {ex.Message}"); - // Return whatever descriptors we've managed to parse - } - - return descriptors; - } - - /// - /// Parses the imported functions for a given import descriptor - /// - private void ParseImportedFunctions(BinaryReader reader, ImportDescriptor descriptor) - { - try - { - // Use OriginalFirstThunk if available, otherwise use FirstThunk - uint thunkRva = descriptor.OriginalFirstThunk != 0 ? descriptor.OriginalFirstThunk : descriptor.FirstThunk; - - if (thunkRva == 0) - { - return; // No functions to parse - } - - uint thunkOffset = RvaToOffset(thunkRva); - int functionCount = 0; - - while (true) - { - reader.BaseStream.Seek(thunkOffset + (functionCount * 4), SeekOrigin.Begin); - uint thunkData = reader.ReadUInt32(); - - if (thunkData == 0) - { - break; // End of the function list - } - - ImportedFunction function = new ImportedFunction - { - ThunkRVA = thunkRva + (uint)(functionCount * 4) - }; - - // Check if imported by ordinal (high bit set) - if ((thunkData & 0x80000000) != 0) - { - function.IsOrdinal = true; - function.Ordinal = (ushort)(thunkData & 0xFFFF); - function.Name = $"Ordinal {function.Ordinal}"; - } - else - { - // Imported by name - the thunkData is an RVA to a hint/name structure - try - { - uint hintNameOffset = RvaToOffset(thunkData); - reader.BaseStream.Seek(hintNameOffset, SeekOrigin.Begin); - - // Read the hint (2 bytes) - function.Hint = reader.ReadUInt16(); - - // Read the function name (null-terminated ASCII string) - StringBuilder nameBuilder = new StringBuilder(); - byte b; - - while ((b = reader.ReadByte()) != 0) - { - nameBuilder.Append((char)b); - } - - function.Name = nameBuilder.ToString(); - - if (string.IsNullOrEmpty(function.Name)) - { - function.Name = $"Function_at_{thunkData:X8}"; - } - } - catch (Exception) - { - function.Name = $"Function_at_{thunkData:X8}"; - } - } - - descriptor.Functions.Add(function); - functionCount++; - } - } - catch (Exception ex) - { - Console.WriteLine($"Error parsing imported functions for {descriptor.DllName}: {ex.Message}"); - } - } - - /// - /// Parses the exported functions using the export directory information - /// - private void ParseExportedFunctions(BinaryReader reader) - { - if (ExportDirectory == null) - { - return; - } - - // Read the array of function addresses (RVAs) - uint[] functionRVAs = new uint[ExportDirectory.NumberOfFunctions]; - reader.BaseStream.Seek(RvaToOffset(ExportDirectory.AddressOfFunctions), SeekOrigin.Begin); - for (int i = 0; i < ExportDirectory.NumberOfFunctions; i++) - { - functionRVAs[i] = reader.ReadUInt32(); - } - - // Read the array of name RVAs - uint[] nameRVAs = new uint[ExportDirectory.NumberOfNames]; - reader.BaseStream.Seek(RvaToOffset(ExportDirectory.AddressOfNames), SeekOrigin.Begin); - for (int i = 0; i < ExportDirectory.NumberOfNames; i++) - { - nameRVAs[i] = reader.ReadUInt32(); - } - - // Read the array of name ordinals - ushort[] nameOrdinals = new ushort[ExportDirectory.NumberOfNames]; - reader.BaseStream.Seek(RvaToOffset(ExportDirectory.AddressOfNameOrdinals), SeekOrigin.Begin); - for (int i = 0; i < ExportDirectory.NumberOfNames; i++) - { - nameOrdinals[i] = reader.ReadUInt16(); - } - - // Create a dictionary to map ordinals to names - Dictionary ordinalToName = new Dictionary(); - for (int i = 0; i < ExportDirectory.NumberOfNames; i++) - { - // Read the function name - reader.BaseStream.Seek(RvaToOffset(nameRVAs[i]), SeekOrigin.Begin); - List nameBytes = new List(); - byte b; - while ((b = reader.ReadByte()) != 0) - { - nameBytes.Add(b); - } - string name = Encoding.ASCII.GetString(nameBytes.ToArray()); - - // Map the ordinal to the name - ordinalToName[nameOrdinals[i]] = name; - } - - // Create the exported functions - for (ushort i = 0; i < ExportDirectory.NumberOfFunctions; i++) - { - uint functionRVA = functionRVAs[i]; - if (functionRVA == 0) - { - continue; // Skip empty entries - } - - ExportedFunction function = new ExportedFunction(); - function.Ordinal = (ushort)(i + ExportDirectory.Base); - function.Address = functionRVA; - - // Check if this function has a name - if (ordinalToName.TryGetValue(i, out string name)) - { - function.Name = name; - } - else - { - function.Name = $"Ordinal_{function.Ordinal}"; - } - - // Check if this is a forwarder - uint exportDirStart = OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; - uint exportDirEnd = exportDirStart + OptionalHeader.DataDirectories[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; - - if (functionRVA >= exportDirStart && functionRVA < exportDirEnd) - { - function.IsForwarder = true; - - // Read the forwarder string - reader.BaseStream.Seek(RvaToOffset(functionRVA), SeekOrigin.Begin); - List forwarderBytes = new List(); - byte b; - while ((b = reader.ReadByte()) != 0) - { - forwarderBytes.Add(b); - } - function.ForwarderName = Encoding.ASCII.GetString(forwarderBytes.ToArray()); - } - - ExportedFunctions.Add(function); - } - } - /// /// Gets the raw data for a specific section /// @@ -655,17 +193,6 @@ namespace X86Disassembler.PE throw new ArgumentException($"Section '{sectionName}' not found"); } - /// - /// Checks if a section contains code - /// - /// The section to check - /// True if the section contains code, false otherwise - public bool IsSectionContainsCode(SectionHeader section) - { - return (section.Characteristics & IMAGE_SCN_CNT_CODE) != 0 || - (section.Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; - } - /// /// Gets all code sections /// @@ -676,7 +203,7 @@ namespace X86Disassembler.PE for (int i = 0; i < SectionHeaders.Count; i++) { - if (IsSectionContainsCode(SectionHeaders[i])) + if (_sectionHeaderParser.IsSectionContainsCode(SectionHeaders[i])) { codeSections.Add(i); } @@ -686,40 +213,13 @@ namespace X86Disassembler.PE } /// - /// Converts a Relative Virtual Address (RVA) to a file offset + /// Checks if a section contains code /// - /// The RVA to convert - /// The corresponding file offset - public uint RvaToOffset(uint rva) + /// The section to check + /// True if the section contains code, false otherwise + public bool IsSectionContainsCode(SectionHeader section) { - if (rva == 0) - { - return 0; - } - - foreach (var section in SectionHeaders) - { - // Check if the RVA is within this section - if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize) - { - // Calculate the offset within the section - uint offsetInSection = rva - section.VirtualAddress; - - // Make sure we don't exceed the raw data size - if (offsetInSection < section.SizeOfRawData) - { - return section.PointerToRawData + offsetInSection; - } - } - } - - // If the RVA is not within any section, it might be in the headers - if (rva < OptionalHeader.SizeOfHeaders) - { - return rva; - } - - throw new ArgumentException($"RVA {rva:X8} is not within any section"); + return _sectionHeaderParser.IsSectionContainsCode(section); } } } diff --git a/X86Disassembler/PE/PEUtility.cs b/X86Disassembler/PE/PEUtility.cs new file mode 100644 index 0000000..eea571a --- /dev/null +++ b/X86Disassembler/PE/PEUtility.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace X86Disassembler.PE +{ + /// + /// Utility class for PE format operations + /// + public class PEUtility + { + private readonly List _sectionHeaders; + private readonly uint _sizeOfHeaders; + + /// + /// Initialize a new instance of the PEUtility class + /// + /// The section headers + /// The size of the headers + public PEUtility(List sectionHeaders, uint sizeOfHeaders) + { + _sectionHeaders = sectionHeaders; + _sizeOfHeaders = sizeOfHeaders; + } + + /// + /// Converts a Relative Virtual Address (RVA) to a file offset + /// + /// The RVA to convert + /// The corresponding file offset + public uint RvaToOffset(uint rva) + { + if (rva == 0) + { + return 0; + } + + foreach (var section in _sectionHeaders) + { + // Check if the RVA is within this section + if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize) + { + // Calculate the offset within the section + uint offsetInSection = rva - section.VirtualAddress; + + // Make sure we don't exceed the raw data size + if (offsetInSection < section.SizeOfRawData) + { + return section.PointerToRawData + offsetInSection; + } + } + } + + // If the RVA is not within any section, it might be in the headers + if (rva < _sizeOfHeaders) + { + return rva; + } + + throw new ArgumentException($"RVA {rva:X8} is not within any section"); + } + } +} diff --git a/X86Disassembler/PE/Parsers/DOSHeaderParser.cs b/X86Disassembler/PE/Parsers/DOSHeaderParser.cs new file mode 100644 index 0000000..3cc5427 --- /dev/null +++ b/X86Disassembler/PE/Parsers/DOSHeaderParser.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; + +namespace X86Disassembler.PE.Parsers +{ + /// + /// Parser for the DOS header of a PE file + /// + public class DOSHeaderParser : IParser + { + // DOS Header constants + private const ushort DOS_SIGNATURE = 0x5A4D; // 'MZ' + + /// + /// Parse the DOS header from the binary reader + /// + /// The binary reader positioned at the start of the DOS header + /// The parsed DOS header + public DOSHeader Parse(BinaryReader reader) + { + DOSHeader header = new DOSHeader(); + + header.e_magic = reader.ReadUInt16(); + if (header.e_magic != DOS_SIGNATURE) + { + throw new InvalidDataException("Invalid DOS signature (MZ)"); + } + + header.e_cblp = reader.ReadUInt16(); + header.e_cp = reader.ReadUInt16(); + header.e_crlc = reader.ReadUInt16(); + header.e_cparhdr = reader.ReadUInt16(); + header.e_minalloc = reader.ReadUInt16(); + header.e_maxalloc = reader.ReadUInt16(); + header.e_ss = reader.ReadUInt16(); + header.e_sp = reader.ReadUInt16(); + header.e_csum = reader.ReadUInt16(); + header.e_ip = reader.ReadUInt16(); + header.e_cs = reader.ReadUInt16(); + header.e_lfarlc = reader.ReadUInt16(); + header.e_ovno = reader.ReadUInt16(); + + header.e_res = new ushort[4]; + for (int i = 0; i < 4; i++) + { + header.e_res[i] = reader.ReadUInt16(); + } + + header.e_oemid = reader.ReadUInt16(); + header.e_oeminfo = reader.ReadUInt16(); + + header.e_res2 = new ushort[10]; + for (int i = 0; i < 10; i++) + { + header.e_res2[i] = reader.ReadUInt16(); + } + + header.e_lfanew = reader.ReadUInt32(); + + return header; + } + } +} diff --git a/X86Disassembler/PE/Parsers/ExportDirectoryParser.cs b/X86Disassembler/PE/Parsers/ExportDirectoryParser.cs new file mode 100644 index 0000000..046b7b1 --- /dev/null +++ b/X86Disassembler/PE/Parsers/ExportDirectoryParser.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace X86Disassembler.PE.Parsers +{ + /// + /// Parser for the Export Directory of a PE file + /// + public class ExportDirectoryParser + { + private readonly PEUtility _utility; + + public ExportDirectoryParser(PEUtility utility) + { + _utility = utility; + } + + /// + /// Parse the Export Directory from the binary reader + /// + /// The binary reader + /// The RVA of the Export Directory + /// The parsed Export Directory + public ExportDirectory Parse(BinaryReader reader, uint rva) + { + ExportDirectory directory = new ExportDirectory(); + + reader.BaseStream.Seek(_utility.RvaToOffset(rva), SeekOrigin.Begin); + + directory.Characteristics = reader.ReadUInt32(); + directory.TimeDateStamp = reader.ReadUInt32(); + directory.MajorVersion = reader.ReadUInt16(); + directory.MinorVersion = reader.ReadUInt16(); + directory.Name = reader.ReadUInt32(); + directory.Base = reader.ReadUInt32(); + directory.NumberOfFunctions = reader.ReadUInt32(); + directory.NumberOfNames = reader.ReadUInt32(); + directory.AddressOfFunctions = reader.ReadUInt32(); + directory.AddressOfNames = reader.ReadUInt32(); + directory.AddressOfNameOrdinals = reader.ReadUInt32(); + + // Read the DLL name + try + { + uint dllNameRVA = directory.Name; + uint dllNameOffset = _utility.RvaToOffset(dllNameRVA); + reader.BaseStream.Seek(dllNameOffset, SeekOrigin.Begin); + + // Read the null-terminated ASCII string + StringBuilder nameBuilder = new StringBuilder(); + byte b; + + while ((b = reader.ReadByte()) != 0) + { + nameBuilder.Append((char)b); + } + + directory.DllName = nameBuilder.ToString(); + } + catch (Exception) + { + directory.DllName = "Unknown"; + } + + return directory; + } + + /// + /// Parse the exported functions using the export directory information + /// + /// The binary reader + /// The Export Directory + /// The RVA of the Export Directory + /// The size of the Export Directory + /// List of exported functions + public List ParseExportedFunctions(BinaryReader reader, ExportDirectory directory, uint exportDirRva, uint exportDirSize) + { + List exportedFunctions = new List(); + + if (directory == null) + { + return exportedFunctions; + } + + // Read the array of function addresses (RVAs) + uint[] functionRVAs = new uint[directory.NumberOfFunctions]; + reader.BaseStream.Seek(_utility.RvaToOffset(directory.AddressOfFunctions), SeekOrigin.Begin); + for (int i = 0; i < directory.NumberOfFunctions; i++) + { + functionRVAs[i] = reader.ReadUInt32(); + } + + // Read the array of name RVAs + uint[] nameRVAs = new uint[directory.NumberOfNames]; + reader.BaseStream.Seek(_utility.RvaToOffset(directory.AddressOfNames), SeekOrigin.Begin); + for (int i = 0; i < directory.NumberOfNames; i++) + { + nameRVAs[i] = reader.ReadUInt32(); + } + + // Read the array of name ordinals + ushort[] nameOrdinals = new ushort[directory.NumberOfNames]; + reader.BaseStream.Seek(_utility.RvaToOffset(directory.AddressOfNameOrdinals), SeekOrigin.Begin); + for (int i = 0; i < directory.NumberOfNames; i++) + { + nameOrdinals[i] = reader.ReadUInt16(); + } + + // Create a dictionary to map ordinals to names + Dictionary ordinalToName = new Dictionary(); + for (int i = 0; i < directory.NumberOfNames; i++) + { + // Read the function name + reader.BaseStream.Seek(_utility.RvaToOffset(nameRVAs[i]), SeekOrigin.Begin); + List nameBytes = new List(); + byte b; + while ((b = reader.ReadByte()) != 0) + { + nameBytes.Add(b); + } + string name = Encoding.ASCII.GetString(nameBytes.ToArray()); + + // Map the ordinal to the name + ordinalToName[nameOrdinals[i]] = name; + } + + // Create the exported functions + for (ushort i = 0; i < directory.NumberOfFunctions; i++) + { + uint functionRVA = functionRVAs[i]; + if (functionRVA == 0) + { + continue; // Skip empty entries + } + + ExportedFunction function = new ExportedFunction(); + function.Ordinal = (ushort)(i + directory.Base); + function.Address = functionRVA; + + // Check if this function has a name + if (ordinalToName.TryGetValue(i, out string name)) + { + function.Name = name; + } + else + { + function.Name = $"Ordinal_{function.Ordinal}"; + } + + // Check if this is a forwarder + uint exportDirEnd = exportDirRva + exportDirSize; + + if (functionRVA >= exportDirRva && functionRVA < exportDirEnd) + { + function.IsForwarder = true; + + // Read the forwarder string + reader.BaseStream.Seek(_utility.RvaToOffset(functionRVA), SeekOrigin.Begin); + List forwarderBytes = new List(); + byte b; + while ((b = reader.ReadByte()) != 0) + { + forwarderBytes.Add(b); + } + function.ForwarderName = Encoding.ASCII.GetString(forwarderBytes.ToArray()); + } + + exportedFunctions.Add(function); + } + + return exportedFunctions; + } + } +} diff --git a/X86Disassembler/PE/Parsers/FileHeaderParser.cs b/X86Disassembler/PE/Parsers/FileHeaderParser.cs new file mode 100644 index 0000000..6e4602a --- /dev/null +++ b/X86Disassembler/PE/Parsers/FileHeaderParser.cs @@ -0,0 +1,30 @@ +using System.IO; + +namespace X86Disassembler.PE.Parsers +{ + /// + /// Parser for the File header of a PE file + /// + public class FileHeaderParser : IParser + { + /// + /// Parse the File header from the binary reader + /// + /// The binary reader positioned at the start of the File header + /// The parsed File header + public FileHeader Parse(BinaryReader reader) + { + FileHeader header = new FileHeader(); + + header.Machine = reader.ReadUInt16(); + header.NumberOfSections = reader.ReadUInt16(); + header.TimeDateStamp = reader.ReadUInt32(); + header.PointerToSymbolTable = reader.ReadUInt32(); + header.NumberOfSymbols = reader.ReadUInt32(); + header.SizeOfOptionalHeader = reader.ReadUInt16(); + header.Characteristics = reader.ReadUInt16(); + + return header; + } + } +} diff --git a/X86Disassembler/PE/Parsers/IParser.cs b/X86Disassembler/PE/Parsers/IParser.cs new file mode 100644 index 0000000..72e3b39 --- /dev/null +++ b/X86Disassembler/PE/Parsers/IParser.cs @@ -0,0 +1,18 @@ +using System.IO; + +namespace X86Disassembler.PE.Parsers +{ + /// + /// Interface for PE format component parsers + /// + /// The type of component to parse + public interface IParser + { + /// + /// Parse a component from the binary reader + /// + /// The binary reader positioned at the start of the component + /// The parsed component + T Parse(BinaryReader reader); + } +} diff --git a/X86Disassembler/PE/Parsers/ImportDescriptorParser.cs b/X86Disassembler/PE/Parsers/ImportDescriptorParser.cs new file mode 100644 index 0000000..ed7f741 --- /dev/null +++ b/X86Disassembler/PE/Parsers/ImportDescriptorParser.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace X86Disassembler.PE.Parsers +{ + /// + /// Parser for Import Descriptors in a PE file + /// + public class ImportDescriptorParser + { + private readonly PEUtility _utility; + + public ImportDescriptorParser(PEUtility utility) + { + _utility = utility; + } + + /// + /// Parse the Import Descriptors from the binary reader + /// + /// The binary reader + /// The RVA of the Import Directory + /// List of Import Descriptors + public List Parse(BinaryReader reader, uint rva) + { + List descriptors = new List(); + + try + { + uint importTableOffset = _utility.RvaToOffset(rva); + reader.BaseStream.Seek(importTableOffset, SeekOrigin.Begin); + + int descriptorCount = 0; + + while (true) + { + descriptorCount++; + + // Read the import descriptor + uint originalFirstThunk = reader.ReadUInt32(); + uint timeDateStamp = reader.ReadUInt32(); + uint forwarderChain = reader.ReadUInt32(); + uint nameRva = reader.ReadUInt32(); + uint firstThunk = reader.ReadUInt32(); + + // Check if we've reached the end of the import descriptors + if (originalFirstThunk == 0 && nameRva == 0 && firstThunk == 0) + { + break; + } + + ImportDescriptor descriptor = new ImportDescriptor + { + OriginalFirstThunk = originalFirstThunk, + TimeDateStamp = timeDateStamp, + ForwarderChain = forwarderChain, + Name = nameRva, + FirstThunk = firstThunk, + DllName = "Unknown" // Default name in case we can't read it + }; + + // Try to read the DLL name + try + { + if (nameRva != 0) + { + uint nameOffset = _utility.RvaToOffset(nameRva); + reader.BaseStream.Seek(nameOffset, SeekOrigin.Begin); + + // Read the null-terminated ASCII string + StringBuilder nameBuilder = new StringBuilder(); + byte b; + + while ((b = reader.ReadByte()) != 0) + { + nameBuilder.Append((char)b); + } + + descriptor.DllName = nameBuilder.ToString(); + } + } + catch (Exception) + { + // If we can't read the name, keep the default "Unknown" + } + + // Parse the imported functions + ParseImportedFunctions(reader, descriptor); + + descriptors.Add(descriptor); + + // Return to the import table to read the next descriptor + reader.BaseStream.Seek(importTableOffset + (descriptorCount * 20), SeekOrigin.Begin); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error parsing import descriptors: {ex.Message}"); + // Return whatever descriptors we've managed to parse + } + + return descriptors; + } + + /// + /// Parse the imported functions for a given import descriptor + /// + /// The binary reader + /// The Import Descriptor + private void ParseImportedFunctions(BinaryReader reader, ImportDescriptor descriptor) + { + try + { + // Use OriginalFirstThunk if available, otherwise use FirstThunk + uint thunkRva = descriptor.OriginalFirstThunk != 0 ? descriptor.OriginalFirstThunk : descriptor.FirstThunk; + + if (thunkRva == 0) + { + return; // No functions to parse + } + + uint thunkOffset = _utility.RvaToOffset(thunkRva); + int functionCount = 0; + + while (true) + { + reader.BaseStream.Seek(thunkOffset + (functionCount * 4), SeekOrigin.Begin); + uint thunkData = reader.ReadUInt32(); + + if (thunkData == 0) + { + break; // End of the function list + } + + ImportedFunction function = new ImportedFunction + { + ThunkRVA = thunkRva + (uint)(functionCount * 4) + }; + + // Check if imported by ordinal (high bit set) + if ((thunkData & 0x80000000) != 0) + { + function.IsOrdinal = true; + function.Ordinal = (ushort)(thunkData & 0xFFFF); + function.Name = $"Ordinal {function.Ordinal}"; + } + else + { + // Imported by name - the thunkData is an RVA to a hint/name structure + try + { + uint hintNameOffset = _utility.RvaToOffset(thunkData); + reader.BaseStream.Seek(hintNameOffset, SeekOrigin.Begin); + + // Read the hint (2 bytes) + function.Hint = reader.ReadUInt16(); + + // Read the function name (null-terminated ASCII string) + StringBuilder nameBuilder = new StringBuilder(); + byte b; + + while ((b = reader.ReadByte()) != 0) + { + nameBuilder.Append((char)b); + } + + function.Name = nameBuilder.ToString(); + + if (string.IsNullOrEmpty(function.Name)) + { + function.Name = $"Function_at_{thunkData:X8}"; + } + } + catch (Exception) + { + function.Name = $"Function_at_{thunkData:X8}"; + } + } + + descriptor.Functions.Add(function); + functionCount++; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error parsing imported functions for {descriptor.DllName}: {ex.Message}"); + } + } + } +} diff --git a/X86Disassembler/PE/Parsers/OptionalHeaderParser.cs b/X86Disassembler/PE/Parsers/OptionalHeaderParser.cs new file mode 100644 index 0000000..c1f1a0a --- /dev/null +++ b/X86Disassembler/PE/Parsers/OptionalHeaderParser.cs @@ -0,0 +1,114 @@ +using System; +using System.IO; + +namespace X86Disassembler.PE.Parsers +{ + /// + /// Parser for the Optional header of a PE file + /// + public class OptionalHeaderParser : IParser + { + // Optional Header Magic values + private const ushort PE32_MAGIC = 0x10B; // 32-bit executable + private const ushort PE32PLUS_MAGIC = 0x20B; // 64-bit executable + + /// + /// Parse the Optional header from the binary reader + /// + /// The binary reader positioned at the start of the Optional header + /// The parsed Optional header + public OptionalHeader Parse(BinaryReader reader) + { + OptionalHeader header = new OptionalHeader(); + bool is64Bit; + + // Standard fields + header.Magic = reader.ReadUInt16(); + + // Determine if this is a PE32 or PE32+ file + is64Bit = header.Magic == PE32PLUS_MAGIC; + + header.MajorLinkerVersion = reader.ReadByte(); + header.MinorLinkerVersion = reader.ReadByte(); + header.SizeOfCode = reader.ReadUInt32(); + header.SizeOfInitializedData = reader.ReadUInt32(); + header.SizeOfUninitializedData = reader.ReadUInt32(); + header.AddressOfEntryPoint = reader.ReadUInt32(); + header.BaseOfCode = reader.ReadUInt32(); + + // PE32 has BaseOfData, PE32+ doesn't + if (!is64Bit) + { + header.BaseOfData = reader.ReadUInt32(); + } + + // Windows-specific fields + if (is64Bit) + { + header.ImageBase = reader.ReadUInt64(); + } + else + { + header.ImageBase = reader.ReadUInt32(); + } + + header.SectionAlignment = reader.ReadUInt32(); + header.FileAlignment = reader.ReadUInt32(); + header.MajorOperatingSystemVersion = reader.ReadUInt16(); + header.MinorOperatingSystemVersion = reader.ReadUInt16(); + header.MajorImageVersion = reader.ReadUInt16(); + header.MinorImageVersion = reader.ReadUInt16(); + header.MajorSubsystemVersion = reader.ReadUInt16(); + header.MinorSubsystemVersion = reader.ReadUInt16(); + header.Win32VersionValue = reader.ReadUInt32(); + header.SizeOfImage = reader.ReadUInt32(); + header.SizeOfHeaders = reader.ReadUInt32(); + header.CheckSum = reader.ReadUInt32(); + header.Subsystem = reader.ReadUInt16(); + header.DllCharacteristics = reader.ReadUInt16(); + + // Size fields differ between PE32 and PE32+ + if (is64Bit) + { + header.SizeOfStackReserve = reader.ReadUInt64(); + header.SizeOfStackCommit = reader.ReadUInt64(); + header.SizeOfHeapReserve = reader.ReadUInt64(); + header.SizeOfHeapCommit = reader.ReadUInt64(); + } + else + { + header.SizeOfStackReserve = reader.ReadUInt32(); + header.SizeOfStackCommit = reader.ReadUInt32(); + header.SizeOfHeapReserve = reader.ReadUInt32(); + header.SizeOfHeapCommit = reader.ReadUInt32(); + } + + header.LoaderFlags = reader.ReadUInt32(); + header.NumberOfRvaAndSizes = reader.ReadUInt32(); + + // Data directories + int numDirectories = (int)Math.Min(header.NumberOfRvaAndSizes, 16); // Maximum of 16 directories + header.DataDirectories = new DataDirectory[numDirectories]; + + for (int i = 0; i < numDirectories; i++) + { + DataDirectory dir = new DataDirectory(); + dir.VirtualAddress = reader.ReadUInt32(); + dir.Size = reader.ReadUInt32(); + header.DataDirectories[i] = dir; + } + + return header; + } + + /// + /// Determines if the PE file is 64-bit based on the Optional header + /// + /// The Optional header + /// True if the PE file is 64-bit, false otherwise + public bool Is64Bit(OptionalHeader header) + { + return header.Magic == PE32PLUS_MAGIC; + } + } +} diff --git a/X86Disassembler/PE/Parsers/SectionHeaderParser.cs b/X86Disassembler/PE/Parsers/SectionHeaderParser.cs new file mode 100644 index 0000000..74df093 --- /dev/null +++ b/X86Disassembler/PE/Parsers/SectionHeaderParser.cs @@ -0,0 +1,52 @@ +using System.IO; +using System.Text; + +namespace X86Disassembler.PE.Parsers +{ + /// + /// Parser for section headers in a PE file + /// + public class SectionHeaderParser : IParser + { + /// + /// Parse a section header from the binary reader + /// + /// The binary reader positioned at the start of the section header + /// The parsed section header + public SectionHeader Parse(BinaryReader reader) + { + SectionHeader header = new SectionHeader(); + + // Read section name (8 bytes) + byte[] nameBytes = reader.ReadBytes(8); + // Convert to string, removing any null characters + header.Name = Encoding.ASCII.GetString(nameBytes).TrimEnd('\0'); + + header.VirtualSize = reader.ReadUInt32(); + header.VirtualAddress = reader.ReadUInt32(); + header.SizeOfRawData = reader.ReadUInt32(); + header.PointerToRawData = reader.ReadUInt32(); + header.PointerToRelocations = reader.ReadUInt32(); + header.PointerToLinenumbers = reader.ReadUInt32(); + header.NumberOfRelocations = reader.ReadUInt16(); + header.NumberOfLinenumbers = reader.ReadUInt16(); + header.Characteristics = reader.ReadUInt32(); + + return header; + } + + /// + /// Checks if a section contains code + /// + /// The section to check + /// True if the section contains code, false otherwise + public bool IsSectionContainsCode(SectionHeader section) + { + const uint IMAGE_SCN_CNT_CODE = 0x00000020; // Section contains code + const uint IMAGE_SCN_MEM_EXECUTE = 0x20000000; // Section is executable + + return (section.Characteristics & IMAGE_SCN_CNT_CODE) != 0 || + (section.Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + } + } +}