diff --git a/BurnOutSharp.Builder/PortableExecutable.cs b/BurnOutSharp.Builder/PortableExecutable.cs index 6f97ca5c..3fe7f7f7 100644 --- a/BurnOutSharp.Builder/PortableExecutable.cs +++ b/BurnOutSharp.Builder/PortableExecutable.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using BurnOutSharp.Models.PortableExecutable; namespace BurnOutSharp.Builder @@ -91,6 +92,29 @@ namespace BurnOutSharp.Builder #endregion + #region COFF Symbol Table + + // TODO: Validate that this is correct + if (coffFileHeader.PointerToSymbolTable != 0) + { + // If the offset for the COFF symbol table doesn't exist + int tableAddress = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + (int)coffFileHeader.PointerToSymbolTable; + if (tableAddress >= data.Length) + return executable; + + // Try to parse the COFF symbol table + var coffSymbolTable = ParseCOFFSymbolTable(data, tableAddress, coffFileHeader.NumberOfSymbols); + if (coffSymbolTable == null) + return null; + + // Set the COFF symbol table + executable.COFFSymbolTable = coffSymbolTable; + } + + #endregion + // TODO: Finish implementing PE parsing return executable; } @@ -329,6 +353,161 @@ namespace BurnOutSharp.Builder return sectionTable; } + /// + /// Parse a byte array into a COFF symbol table + /// + /// Byte array to parse + /// Offset into the byte array + /// Number of COFF symbol table entries to read + /// Filled COFF symbol table on success, null on error + private static COFFSymbolTableEntry[] ParseCOFFSymbolTable(byte[] data, int offset, uint count) + { + // TODO: Use marshalling here instead of building + var coffSymbolTable = new COFFSymbolTableEntry[count]; + + int auxSymbolsRemaining = 0; + int currentSymbolType = 0; + + for (int i = 0; i < count; i++) + { + // Standard COFF Symbol Table Entry + if (currentSymbolType == 0) + { + var entry = new COFFSymbolTableEntry(); + entry.ShortName = data.ReadBytes(ref offset, 8); + entry.Zeroes = BitConverter.ToUInt32(entry.ShortName, 0); + if (entry.Zeroes == 0) + { + entry.Offset = BitConverter.ToUInt32(entry.ShortName, 4); + entry.ShortName = null; + } + entry.Value = data.ReadUInt32(ref offset); + entry.SectionNumber = data.ReadUInt16(ref offset); + entry.SymbolType = (SymbolType)data.ReadUInt16(ref offset); + entry.StorageClass = (StorageClass)data.ReadByte(ref offset); + entry.NumberOfAuxSymbols = data.ReadByte(ref offset); + coffSymbolTable[i] = entry; + + auxSymbolsRemaining = entry.NumberOfAuxSymbols; + if (auxSymbolsRemaining == 0) + continue; + + if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL + && entry.SymbolType == SymbolType.IMAGE_SYM_TYPE_FUNC + && entry.SectionNumber > 0) + { + currentSymbolType = 1; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FUNCTION + && entry.ShortName != null + && ((entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x62 && entry.ShortName[2] == 0x66) // .bf + || (entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x65 && entry.ShortName[2] == 0x66))) // .ef + { + currentSymbolType = 2; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL + && entry.SectionNumber == (ushort)SectionNumber.IMAGE_SYM_UNDEFINED + && entry.Value == 0) + { + currentSymbolType = 3; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FILE) + { + // TODO: Symbol name should be ".file" + currentSymbolType = 4; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_STATIC) + { + // TODO: Should have the name of a section (like ".text") + currentSymbolType = 5; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_CLR_TOKEN) + { + currentSymbolType = 6; + } + } + + // Auxiliary Format 1: Function Definitions + else if (currentSymbolType == 1) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat1TagIndex = data.ReadUInt32(ref offset); + entry.AuxFormat1TotalSize = data.ReadUInt32(ref offset); + entry.AuxFormat1PointerToLinenumber = data.ReadUInt32(ref offset); + entry.AuxFormat1PointerToNextFunction = data.ReadUInt32(ref offset); + entry.AuxFormat1Unused = data.ReadUInt16(ref offset); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 2: .bf and .ef Symbols + else if (currentSymbolType == 2) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat2Unused1 = data.ReadUInt32(ref offset); + entry.AuxFormat2Linenumber = data.ReadUInt16(ref offset); + entry.AuxFormat2Unused2 = data.ReadBytes(ref offset, 6); + entry.AuxFormat2PointerToNextFunction = data.ReadUInt32(ref offset); + entry.AuxFormat2Unused3 = data.ReadUInt16(ref offset); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 3: Weak Externals + else if (currentSymbolType == 3) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat3TagIndex = data.ReadUInt32(ref offset); + entry.AuxFormat3Characteristics = data.ReadUInt32(ref offset); + entry.AuxFormat3Unused = data.ReadBytes(ref offset, 10); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 4: Files + else if (currentSymbolType == 4) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat4FileName = data.ReadBytes(ref offset, 18); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 5: Section Definitions + else if (currentSymbolType == 5) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat5Length = data.ReadUInt32(ref offset); + entry.AuxFormat5NumberOfRelocations = data.ReadUInt16(ref offset); + entry.AuxFormat5NumberOfLinenumbers = data.ReadUInt16(ref offset); + entry.AuxFormat5CheckSum = data.ReadUInt32(ref offset); + entry.AuxFormat5Number = data.ReadUInt16(ref offset); + entry.AuxFormat5Selection = data.ReadByte(ref offset); + entry.AuxFormat5Unused = data.ReadBytes(ref offset, 3); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 6: CLR Token Definition + else if (currentSymbolType == 6) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat6AuxType = data.ReadByte(ref offset); + entry.AuxFormat6Reserved1 = data.ReadByte(ref offset); + entry.AuxFormat6SymbolTableIndex = data.ReadUInt32(ref offset); + entry.AuxFormat6Reserved2 = data.ReadBytes(ref offset, 12); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // If we hit the last aux symbol, go back to normal format + if (auxSymbolsRemaining == 0) + currentSymbolType = 0; + } + + return coffSymbolTable; + } + #endregion #region Stream Data @@ -415,6 +594,30 @@ namespace BurnOutSharp.Builder #endregion + #region COFF Symbol Table + + // TODO: Validate that this is correct + if (coffFileHeader.PointerToSymbolTable != 0) + { + // If the offset for the COFF symbol table doesn't exist + int tableAddress = initialOffset + + (int)stub.Header.NewExeHeaderAddr + + (int)coffFileHeader.PointerToSymbolTable; + if (tableAddress >= data.Length) + return executable; + + // Try to parse the COFF symbol table + data.Seek(tableAddress, SeekOrigin.Begin); + var coffSymbolTable = ParseCOFFSymbolTable(data, coffFileHeader.NumberOfSymbols); + if (coffSymbolTable == null) + return null; + + // Set the COFF symbol table + executable.COFFSymbolTable = coffSymbolTable; + } + + #endregion + // TODO: Finish implementing PE parsing return executable; } @@ -650,6 +853,160 @@ namespace BurnOutSharp.Builder return sectionTable; } + /// + /// Parse a Stream into a COFF symbol table + /// + /// Stream to parse + /// Number of COFF symbol table entries to read + /// Filled COFF symbol table on success, null on error + private static COFFSymbolTableEntry[] ParseCOFFSymbolTable(Stream data, uint count) + { + // TODO: Use marshalling here instead of building + var coffSymbolTable = new COFFSymbolTableEntry[count]; + + int auxSymbolsRemaining = 0; + int currentSymbolType = 0; + + for (int i = 0; i < count; i++) + { + // Standard COFF Symbol Table Entry + if (currentSymbolType == 0) + { + var entry = new COFFSymbolTableEntry(); + entry.ShortName = data.ReadBytes(8); + entry.Zeroes = BitConverter.ToUInt32(entry.ShortName, 0); + if (entry.Zeroes == 0) + { + entry.Offset = BitConverter.ToUInt32(entry.ShortName, 4); + entry.ShortName = null; + } + entry.Value = data.ReadUInt32(); + entry.SectionNumber = data.ReadUInt16(); + entry.SymbolType = (SymbolType)data.ReadUInt16(); + entry.StorageClass = (StorageClass)data.ReadByte(); + entry.NumberOfAuxSymbols = data.ReadByteValue(); + coffSymbolTable[i] = entry; + + auxSymbolsRemaining = entry.NumberOfAuxSymbols; + if (auxSymbolsRemaining == 0) + continue; + + if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL + && entry.SymbolType == SymbolType.IMAGE_SYM_TYPE_FUNC + && entry.SectionNumber > 0) + { + currentSymbolType = 1; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FUNCTION + && entry.ShortName != null + && ((entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x62 && entry.ShortName[2] == 0x66) // .bf + || (entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x65 && entry.ShortName[2] == 0x66))) // .ef + { + currentSymbolType = 2; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL + && entry.SectionNumber == (ushort)SectionNumber.IMAGE_SYM_UNDEFINED + && entry.Value == 0) + { + currentSymbolType = 3; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FILE) + { + // TODO: Symbol name should be ".file" + currentSymbolType = 4; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_STATIC) + { + // TODO: Should have the name of a section (like ".text") + currentSymbolType = 5; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_CLR_TOKEN) + { + currentSymbolType = 6; + } + } + + // Auxiliary Format 1: Function Definitions + else if (currentSymbolType == 1) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat1TagIndex = data.ReadUInt32(); + entry.AuxFormat1TotalSize = data.ReadUInt32(); + entry.AuxFormat1PointerToLinenumber = data.ReadUInt32(); + entry.AuxFormat1PointerToNextFunction = data.ReadUInt32(); + entry.AuxFormat1Unused = data.ReadUInt16(); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 2: .bf and .ef Symbols + else if (currentSymbolType == 2) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat2Unused1 = data.ReadUInt32(); + entry.AuxFormat2Linenumber = data.ReadUInt16(); + entry.AuxFormat2Unused2 = data.ReadBytes(6); + entry.AuxFormat2PointerToNextFunction = data.ReadUInt32(); + entry.AuxFormat2Unused3 = data.ReadUInt16(); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 3: Weak Externals + else if (currentSymbolType == 3) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat3TagIndex = data.ReadUInt32(); + entry.AuxFormat3Characteristics = data.ReadUInt32(); + entry.AuxFormat3Unused = data.ReadBytes(10); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 4: Files + else if (currentSymbolType == 4) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat4FileName = data.ReadBytes(18); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 5: Section Definitions + else if (currentSymbolType == 5) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat5Length = data.ReadUInt32(); + entry.AuxFormat5NumberOfRelocations = data.ReadUInt16(); + entry.AuxFormat5NumberOfLinenumbers = data.ReadUInt16(); + entry.AuxFormat5CheckSum = data.ReadUInt32(); + entry.AuxFormat5Number = data.ReadUInt16(); + entry.AuxFormat5Selection = data.ReadByteValue(); + entry.AuxFormat5Unused = data.ReadBytes(3); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // Auxiliary Format 6: CLR Token Definition + else if (currentSymbolType == 6) + { + var entry = new COFFSymbolTableEntry(); + entry.AuxFormat6AuxType = data.ReadByteValue(); + entry.AuxFormat6Reserved1 = data.ReadByteValue(); + entry.AuxFormat6SymbolTableIndex = data.ReadUInt32(); + entry.AuxFormat6Reserved2 = data.ReadBytes(12); + coffSymbolTable[i] = entry; + auxSymbolsRemaining--; + } + + // If we hit the last aux symbol, go back to normal format + if (auxSymbolsRemaining == 0) + currentSymbolType = 0; + } + + return coffSymbolTable; + } + #endregion } } \ No newline at end of file diff --git a/BurnOutSharp.Models/PortableExecutable/COFFSymbolTableEntry.cs b/BurnOutSharp.Models/PortableExecutable/COFFSymbolTableEntry.cs index 48d19a94..b6ef65e6 100644 --- a/BurnOutSharp.Models/PortableExecutable/COFFSymbolTableEntry.cs +++ b/BurnOutSharp.Models/PortableExecutable/COFFSymbolTableEntry.cs @@ -102,7 +102,7 @@ namespace BurnOutSharp.Models.PortableExecutable /// The symbol-table index of the corresponding .bf (begin function) /// symbol record. /// - [FieldOffset(0)] public uint AuxFormat1TagIndex; + [FieldOffset(0)] public uint AuxFormat1TagIndex; /// /// The size of the executable code for the function itself. If the function @@ -136,15 +136,15 @@ namespace BurnOutSharp.Models.PortableExecutable // the beginning, ending, and number of lines. Each of these symbols has // storage class FUNCTION (101): // - // A symbol record named.bf(begin function). The Value field is unused. + // A symbol record named .bf (begin function). The Value field is unused. // - // A symbol record named.lf(lines in function). The Value field gives the + // A symbol record named .lf (lines in function). The Value field gives the // number of lines in the function. // - // A symbol record named.ef (end of function). The Value field has the same + // A symbol record named .ef (end of function). The Value field has the same // number as the Total Size field in the function-definition symbol record. // - // The.bf and.ef symbol records (but not .lf records) are followed by an + // The .bf and .ef symbol records (but not .lf records) are followed by an // auxiliary record with the following format: /// diff --git a/BurnOutSharp.Models/PortableExecutable/Enums.cs b/BurnOutSharp.Models/PortableExecutable/Enums.cs index 9c09cbdd..cfa81fd6 100644 --- a/BurnOutSharp.Models/PortableExecutable/Enums.cs +++ b/BurnOutSharp.Models/PortableExecutable/Enums.cs @@ -2079,7 +2079,7 @@ namespace BurnOutSharp.Models.PortableExecutable IMAGE_SYM_CLASS_CLR_TOKEN = 0x6A, } - public enum SymbolType : byte + public enum SymbolType : ushort { /// /// No type information or unknown base type. Microsoft tools use this setting @@ -2160,6 +2160,11 @@ namespace BurnOutSharp.Models.PortableExecutable /// An unsigned 4-byte integer /// IMAGE_SYM_TYPE_DWORD = 0x0F, + + /// + /// A function pointer + /// + IMAGE_SYM_TYPE_FUNC = 0x20, } public enum SymbolDerivedType : byte diff --git a/ExecutableTest/Program.cs b/ExecutableTest/Program.cs index bff03479..f132a700 100644 --- a/ExecutableTest/Program.cs +++ b/ExecutableTest/Program.cs @@ -1,5 +1,6 @@ using System.Text; using BurnOutSharp.Builder; +using BurnOutSharp.Models.PortableExecutable; namespace ExecutableTest { @@ -609,6 +610,135 @@ namespace ExecutableTest } } Console.WriteLine(); + + Console.WriteLine(" COFF Symbol Table Information:"); + Console.WriteLine(" -------------------------"); + if (executable.COFFFileHeader.PointerToSymbolTable == 0 + || executable.COFFFileHeader.NumberOfSymbols == 0 + || executable.COFFSymbolTable.Length == 0) + { + Console.WriteLine(" No COFF symbol table items"); + } + else + { + int auxSymbolsRemaining = 0; + int currentSymbolType = 0; + + for (int i = 0; i < executable.COFFSymbolTable.Length; i++) + { + var entry = executable.COFFSymbolTable[i]; + Console.WriteLine($" COFF Symbol Table Entry {i} (Subtype {currentSymbolType})"); + if (currentSymbolType == 0) + { + if (entry.ShortName != null) + { + Console.WriteLine($" Short name = {Encoding.UTF8.GetString(entry.ShortName)}"); + } + else + { + Console.WriteLine($" Zeroes = {entry.Zeroes}"); + Console.WriteLine($" Offset = {entry.Offset}"); + } + Console.WriteLine($" Value = {entry.Value}"); + Console.WriteLine($" Section number = {entry.SectionNumber}"); + Console.WriteLine($" Symbol type = {entry.SymbolType}"); + Console.WriteLine($" Storage class = {entry.StorageClass}"); + Console.WriteLine($" Number of aux symbols = {entry.NumberOfAuxSymbols}"); + + auxSymbolsRemaining = entry.NumberOfAuxSymbols; + if (auxSymbolsRemaining == 0) + continue; + + if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL + && entry.SymbolType == SymbolType.IMAGE_SYM_TYPE_FUNC + && entry.SectionNumber > 0) + { + currentSymbolType = 1; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FUNCTION + && entry.ShortName != null + && ((entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x62 && entry.ShortName[2] == 0x66) // .bf + || (entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x65 && entry.ShortName[2] == 0x66))) // .ef + { + currentSymbolType = 2; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL + && entry.SectionNumber == (ushort)SectionNumber.IMAGE_SYM_UNDEFINED + && entry.Value == 0) + { + currentSymbolType = 3; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FILE) + { + // TODO: Symbol name should be ".file" + currentSymbolType = 4; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_STATIC) + { + // TODO: Should have the name of a section (like ".text") + currentSymbolType = 5; + } + else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_CLR_TOKEN) + { + currentSymbolType = 6; + } + } + else if (currentSymbolType == 1) + { + Console.WriteLine($" Tag index = {entry.AuxFormat1TagIndex}"); + Console.WriteLine($" Total size = {entry.AuxFormat1TotalSize}"); + Console.WriteLine($" Pointer to linenumber = {entry.AuxFormat1PointerToLinenumber}"); + Console.WriteLine($" Pointer to next function = {entry.AuxFormat1PointerToNextFunction}"); + Console.WriteLine($" Unused = {entry.AuxFormat1Unused}"); + auxSymbolsRemaining--; + } + else if (currentSymbolType == 2) + { + Console.WriteLine($" Unused = {entry.AuxFormat2Unused1}"); + Console.WriteLine($" Linenumber = {entry.AuxFormat2Linenumber}"); + Console.WriteLine($" Unused = {entry.AuxFormat2Unused2}"); + Console.WriteLine($" Pointer to next function = {entry.AuxFormat2PointerToNextFunction}"); + Console.WriteLine($" Unused = {entry.AuxFormat2Unused3}"); + auxSymbolsRemaining--; + } + else if (currentSymbolType == 3) + { + Console.WriteLine($" Tag index = {entry.AuxFormat3TagIndex}"); + Console.WriteLine($" Characteristics = {entry.AuxFormat3Characteristics}"); + Console.WriteLine($" Unused = {BitConverter.ToString(entry.AuxFormat3Unused).Replace("-", string.Empty)}"); + auxSymbolsRemaining--; + } + else if (currentSymbolType == 4) + { + Console.WriteLine($" File name = {Encoding.ASCII.GetString(entry.AuxFormat4FileName)}"); + auxSymbolsRemaining--; + } + else if (currentSymbolType == 5) + { + Console.WriteLine($" Length = {entry.AuxFormat5Length}"); + Console.WriteLine($" Number of relocations = {entry.AuxFormat5NumberOfRelocations}"); + Console.WriteLine($" Number of linenumbers = {entry.AuxFormat5NumberOfLinenumbers}"); + Console.WriteLine($" Checksum = {entry.AuxFormat5CheckSum}"); + Console.WriteLine($" Number = {entry.AuxFormat5Number}"); + Console.WriteLine($" Selection = {entry.AuxFormat5Selection}"); + Console.WriteLine($" Unused = {BitConverter.ToString(entry.AuxFormat5Unused).Replace("-", string.Empty)}"); + auxSymbolsRemaining--; + } + else if (currentSymbolType == 6) + { + Console.WriteLine($" Aux type = {entry.AuxFormat6AuxType}"); + Console.WriteLine($" Reserved = {entry.AuxFormat6Reserved1}"); + Console.WriteLine($" Symbol table index = {entry.AuxFormat6SymbolTableIndex}"); + Console.WriteLine($" Reserved = {BitConverter.ToString(entry.AuxFormat6Reserved2).Replace("-", string.Empty)}"); + auxSymbolsRemaining--; + } + + // If we hit the last aux symbol, go back to normal format + if (auxSymbolsRemaining == 0) + currentSymbolType = 0; + } + } + Console.WriteLine(); } } } \ No newline at end of file