diff --git a/BurnOutSharp.Wrappers/PortableExecutable.cs b/BurnOutSharp.Wrappers/PortableExecutable.cs index 5235b1ef..57f3e635 100644 --- a/BurnOutSharp.Wrappers/PortableExecutable.cs +++ b/BurnOutSharp.Wrappers/PortableExecutable.cs @@ -395,7 +395,7 @@ namespace BurnOutSharp.Wrappers } /// - /// Array of sanitized section names + /// Sanitized section names /// public string[] SectionNames { @@ -424,6 +424,30 @@ namespace BurnOutSharp.Wrappers } } + /// + /// Stub executable data, if it exists + /// + public byte[] StubExecutableData + { + get + { + lock (_stubExecutableDataLock) + { + // If we already have cached data, just use that immediately + if (_stubExecutableData != null) + return _stubExecutableData; + + // Populate the raw stub executable data based on the source + int endOfStubHeader = 0x40; + int lengthOfStubExecutableData = (int)_executable.Stub.Header.NewExeHeaderAddr - endOfStubHeader; + _stubExecutableData = ReadFromDataSource(endOfStubHeader, lengthOfStubExecutableData); + + // Cache and return the stub executable data, even if null + return _stubExecutableData; + } + } + } + /// /// Dictionary of resource data /// @@ -585,15 +609,20 @@ namespace BurnOutSharp.Wrappers private byte[] _overlayData = null; /// - /// Array of sanitized section names + /// Stub executable data, if it exists + /// + private byte[] _stubExecutableData = null; + + /// + /// Sanitized section names /// private string[] _sectionNames = null; /// /// Cached raw section data /// - private readonly Dictionary _rawSectionData = new Dictionary(); - + private byte[][] _sectionData = null; + /// /// Cached resource data /// @@ -618,15 +647,20 @@ namespace BurnOutSharp.Wrappers /// private readonly object _overlayDataLock = new object(); + /// + /// Lock object for concurrent modifications on + /// + private readonly object _stubExecutableDataLock = new object(); + /// /// Lock object for concurrent modifications on /// private readonly object _sectionNamesLock = new object(); /// - /// Lock object for concurrent modifications on + /// Lock object for concurrent modifications on /// - private readonly object _rawSectionsLock = new object(); + private readonly object _sectionDataLock = new object(); /// /// Lock object for concurrent modifications on @@ -635,15 +669,6 @@ namespace BurnOutSharp.Wrappers #endregion - #region Constants - - /// - /// Special string representing the MS-DOS executable data "section" - /// - public const string MSDOSExecutableData = "[MS-DOS Executable Data]"; - - #endregion - #region Constructors /// @@ -699,235 +724,6 @@ namespace BurnOutSharp.Wrappers // TODO: Cache all certificate objects - /// - /// Determine if a section is contained within the section table - /// - /// Name of the section to check for - /// True to enable exact matching of names, false for starts-with - /// True if the section is in the executable, false otherwise - public bool ContainsSection(string sectionName, bool exact = false) - { - // Get all section names first - if (SectionNames == null) - return false; - - // If we're checking exactly, return only exact matches - if (exact) - return SectionNames.Any(n => n.Equals(sectionName)); - - // Otherwise, check if section name starts with the value - else - return SectionNames.Any(n => n.StartsWith(sectionName)); - } - - /// - /// Get the first section based on name, if possible - /// - /// Name of the section to check for - /// True to enable exact matching of names, false for starts-with - /// Section data on success, null on error - public Models.PortableExecutable.SectionHeader GetFirstSection(string sectionName, bool exact = false) - { - // If we have no sections, we can't do anything - if (_executable.SectionTable == null || !SectionTable.Any()) - return null; - - // TODO: Handle long section names with leading `/` - - // If we're checking exactly, return only exact matches - if (exact) - return SectionTable.FirstOrDefault(s => Encoding.UTF8.GetString(s.Name).Equals(sectionName)); - - // Otherwise, check if section name starts with the value - else - return SectionTable.FirstOrDefault(s => Encoding.UTF8.GetString(s.Name).StartsWith(sectionName)); - } - - /// - /// Get the last section based on name, if possible - /// - /// Name of the section to check for - /// True to enable exact matching of names, false for starts-with - /// Section data on success, null on error - public Models.PortableExecutable.SectionHeader GetLastSection(string sectionName, bool exact = false) - { - // If we have no sections, we can't do anything - if (SectionTable == null || !SectionTable.Any()) - return null; - - // TODO: Handle long section names with leading `/` - - // If we're checking exactly, return only exact matches (with nulls trimmed) - if (exact) - return SectionTable.LastOrDefault(s => Encoding.UTF8.GetString(s.Name).Equals(sectionName)); - - // Otherwise, check if section name starts with the value - else - return SectionTable.LastOrDefault(s => Encoding.UTF8.GetString(s.Name).StartsWith(sectionName)); - } - - /// - /// Get raw section data from the source file - /// - /// Name of the section to get raw data for - /// Byte array representing the data, null on error - public byte[] GetRawSection(string sectionName) - { - // If we have an invalid section name - if (string.IsNullOrEmpty(sectionName)) - return null; - - // Validate the data source - if (!DataSourceIsValid()) - return null; - - // We have a special case for the MS-DOS stub executable data - if (sectionName == MSDOSExecutableData) - { - lock (_rawSectionsLock) - { - // If we already have cached data, just use that immediately - if (_rawSectionData.ContainsKey(sectionName)) - return _rawSectionData[sectionName]; - - // Populate the raw stub executable data based on the source - int endOfStubHeader = 0x40; - int lengthOfStubExecutableData = (int)_executable.Stub.Header.NewExeHeaderAddr - endOfStubHeader; - byte[] sectionData = ReadFromDataSource(endOfStubHeader, lengthOfStubExecutableData); - - // Cache and return the stub executable data, even if null - _rawSectionData[sectionName] = sectionData; - return sectionData; - } - } - - // Get the section index from the array - int sectionIndex = Array.IndexOf(SectionNames, sectionName); - if (sectionIndex < 0) - return null; - - // Get the section data from the table - var section = _executable.SectionTable[sectionIndex]; - uint sectionAddress = section.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable); - if (sectionAddress == 0) - return null; - - uint sectionSize = section.SizeOfRawData; - - // TODO: Use section name and index to combat duplicates - lock (_rawSectionsLock) - { - // If we already have cached data, just use that immediately - if (_rawSectionData.ContainsKey(sectionName)) - return _rawSectionData[sectionName]; - - // Populate the raw section data based on the source - byte[] sectionData = ReadFromDataSource((int)sectionAddress, (int)sectionSize); - - // Cache and return the section data, even if null - _rawSectionData[sectionName] = sectionData; - return sectionData; - } - } - - /// - /// Find dialog box resources by title - /// - /// Dialog box title to check for - /// Enumerable of matching resources - public IEnumerable FindDialogByTitle(string title) - { - return ResourceData.Select(r => r.Value) - .Select(r => r as Models.PortableExecutable.DialogBoxResource) - .Where(d => d != null) - .Where(d => - { - return (d.DialogTemplate?.TitleResource?.Contains(title) ?? false) - || (d.ExtendedDialogTemplate?.TitleResource?.Contains(title) ?? false); - }); - } - - /// - /// Find dialog box resources by contained item title - /// - /// Dialog box item title to check for - /// Enumerable of matching resources - public IEnumerable FindDialogBoxByItemTitle(string title) - { - return ResourceData.Select(r => r.Value) - .Select(r => r as Models.PortableExecutable.DialogBoxResource) - .Where(d => d != null) - .Where(d => - { - if (d.DialogItemTemplates != null) - { - return d.DialogItemTemplates - .Where(dit => dit?.TitleResource != null) - .Any(dit => dit.TitleResource.Contains(title)); - } - else if (d.ExtendedDialogItemTemplates != null) - { - return d.ExtendedDialogItemTemplates - .Where(edit => edit?.TitleResource != null) - .Any(edit => edit.TitleResource.Contains(title)); - } - - return false; - }); - } - - /// - /// Find unparsed resources by type name - /// - /// Type name to check for - /// Enumerable of matching resources - public IEnumerable FindResourceByNamedType(string typeName) - { - return ResourceData.Where(kvp => kvp.Key.Contains(typeName)) - .Select(kvp => kvp.Value as byte[]) - .Where(b => b != null); - } - - /// - /// Find unparsed resources by string value - /// - /// String value to check for - /// Enumerable of matching resources - public IEnumerable FindGenericResource(string value) - { - return ResourceData.Select(r => r.Value) - .Select(r => r as byte[]) - .Where(b => b != null) - .Where(b => - { - try - { - string arrayAsASCII = Encoding.ASCII.GetString(b); - if (arrayAsASCII.Contains(value)) - return true; - } - catch { } - - try - { - string arrayAsUTF8 = Encoding.UTF8.GetString(b); - if (arrayAsUTF8.Contains(value)) - return true; - } - catch { } - - try - { - string arrayAsUnicode = Encoding.Unicode.GetString(b); - if (arrayAsUnicode.Contains(value)) - return true; - } - catch { } - - return true; - }); - } - /// /// Get the version info string associated with a key, if possible /// @@ -2511,6 +2307,108 @@ namespace BurnOutSharp.Wrappers #endregion + #region Resource Data + + /// + /// Find dialog box resources by title + /// + /// Dialog box title to check for + /// Enumerable of matching resources + public IEnumerable FindDialogByTitle(string title) + { + return ResourceData.Select(r => r.Value) + .Select(r => r as Models.PortableExecutable.DialogBoxResource) + .Where(d => d != null) + .Where(d => + { + return (d.DialogTemplate?.TitleResource?.Contains(title) ?? false) + || (d.ExtendedDialogTemplate?.TitleResource?.Contains(title) ?? false); + }); + } + + /// + /// Find dialog box resources by contained item title + /// + /// Dialog box item title to check for + /// Enumerable of matching resources + public IEnumerable FindDialogBoxByItemTitle(string title) + { + return ResourceData.Select(r => r.Value) + .Select(r => r as Models.PortableExecutable.DialogBoxResource) + .Where(d => d != null) + .Where(d => + { + if (d.DialogItemTemplates != null) + { + return d.DialogItemTemplates + .Where(dit => dit?.TitleResource != null) + .Any(dit => dit.TitleResource.Contains(title)); + } + else if (d.ExtendedDialogItemTemplates != null) + { + return d.ExtendedDialogItemTemplates + .Where(edit => edit?.TitleResource != null) + .Any(edit => edit.TitleResource.Contains(title)); + } + + return false; + }); + } + + /// + /// Find unparsed resources by type name + /// + /// Type name to check for + /// Enumerable of matching resources + public IEnumerable FindResourceByNamedType(string typeName) + { + return ResourceData.Where(kvp => kvp.Key.Contains(typeName)) + .Select(kvp => kvp.Value as byte[]) + .Where(b => b != null); + } + + /// + /// Find unparsed resources by string value + /// + /// String value to check for + /// Enumerable of matching resources + public IEnumerable FindGenericResource(string value) + { + return ResourceData.Select(r => r.Value) + .Select(r => r as byte[]) + .Where(b => b != null) + .Where(b => + { + try + { + string arrayAsASCII = Encoding.ASCII.GetString(b); + if (arrayAsASCII.Contains(value)) + return true; + } + catch { } + + try + { + string arrayAsUTF8 = Encoding.UTF8.GetString(b); + if (arrayAsUTF8.Contains(value)) + return true; + } + catch { } + + try + { + string arrayAsUnicode = Encoding.Unicode.GetString(b); + if (arrayAsUnicode.Contains(value)) + return true; + } + catch { } + + return true; + }); + } + + #endregion + #region Resource Parsing /// @@ -2645,5 +2543,225 @@ namespace BurnOutSharp.Wrappers } #endregion + + #region Sections + + /// + /// Determine if a section is contained within the section table + /// + /// Name of the section to check for + /// True to enable exact matching of names, false for starts-with + /// True if the section is in the executable, false otherwise + public bool ContainsSection(string sectionName, bool exact = false) + { + // Get all section names first + if (SectionNames == null) + return false; + + // If we're checking exactly, return only exact matches + if (exact) + return SectionNames.Any(n => n.Equals(sectionName)); + + // Otherwise, check if section name starts with the value + else + return SectionNames.Any(n => n.StartsWith(sectionName)); + } + + /// + /// Get the first section based on name, if possible + /// + /// Name of the section to check for + /// True to enable exact matching of names, false for starts-with + /// Section data on success, null on error + public Models.PortableExecutable.SectionHeader GetFirstSection(string name, bool exact = false) + { + // If we have no sections + if (_executable.SectionTable == null || !SectionTable.Any()) + return null; + + // If the section doesn't exist + if (!ContainsSection(name, exact)) + return null; + + // Get the first index of the section + int index = Array.IndexOf(SectionNames, name); + if (index == -1) + return null; + + // Return the section + return SectionTable[index]; + } + + /// + /// Get the last section based on name, if possible + /// + /// Name of the section to check for + /// True to enable exact matching of names, false for starts-with + /// Section data on success, null on error + public Models.PortableExecutable.SectionHeader GetLastSection(string name, bool exact = false) + { + // If we have no sections + if (_executable.SectionTable == null || !SectionTable.Any()) + return null; + + // If the section doesn't exist + if (!ContainsSection(name, exact)) + return null; + + // Get the last index of the section + int index = Array.LastIndexOf(SectionNames, name); + if (index == -1) + return null; + + // Return the section + return SectionTable[index]; + } + + /// + /// Get the section based on index, if possible + /// + /// Index of the section to check for + /// Section data on success, null on error + public Models.PortableExecutable.SectionHeader GetSection(int index) + { + // If we have no sections + if (_executable.SectionTable == null || !SectionTable.Any()) + return null; + + // If the section doesn't exist + if (index < 0 || index >= SectionTable.Length) + return null; + + // Return the section + return SectionTable[index]; + } + + /// + /// Get the first section data based on name, if possible + /// + /// Name of the section to check for + /// True to enable exact matching of names, false for starts-with + /// Section data on success, null on error + public byte[] GetFirstSectionData(string name, bool exact = false) + { + // If we have no sections + if (_executable.SectionTable == null || !SectionTable.Any()) + return null; + + // If the section doesn't exist + if (!ContainsSection(name, exact)) + return null; + + // Get the first index of the section + int index = Array.IndexOf(SectionNames, name); + if (index == -1) + return null; + + // Get the section data from the table + var section = _executable.SectionTable[index]; + uint address = section.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable); + if (address == 0) + return null; + + // Set the section size + uint size = section.SizeOfRawData; + lock (_sectionDataLock) + { + // If we already have cached data, just use that immediately + if (_sectionData[index] != null) + return _sectionData[index]; + + // Populate the raw section data based on the source + byte[] sectionData = ReadFromDataSource((int)address, (int)size); + + // Cache and return the section data, even if null + _sectionData[index] = sectionData; + return sectionData; + } + } + + /// + /// Get the last section data based on name, if possible + /// + /// Name of the section to check for + /// True to enable exact matching of names, false for starts-with + /// Section data on success, null on error + public byte[] GetLastSectionData(string name, bool exact = false) + { + // If we have no sections + if (_executable.SectionTable == null || !SectionTable.Any()) + return null; + + // If the section doesn't exist + if (!ContainsSection(name, exact)) + return null; + + // Get the last index of the section + int index = Array.LastIndexOf(SectionNames, name); + if (index == -1) + return null; + + // Get the section data from the table + var section = _executable.SectionTable[index]; + uint address = section.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable); + if (address == 0) + return null; + + // Set the section size + uint size = section.SizeOfRawData; + lock (_sectionDataLock) + { + // If we already have cached data, just use that immediately + if (_sectionData[index] != null) + return _sectionData[index]; + + // Populate the raw section data based on the source + byte[] sectionData = ReadFromDataSource((int)address, (int)size); + + // Cache and return the section data, even if null + _sectionData[index] = sectionData; + return sectionData; + } + } + + /// + /// Get the section data based on index, if possible + /// + /// Index of the section to check for + /// Section data on success, null on error + public byte[] GetSectionData(int index) + { + // If we have no sections + if (_executable.SectionTable == null || !SectionTable.Any()) + return null; + + // If the section doesn't exist + if (index < 0 || index >= SectionTable.Length) + return null; + + // Get the section data from the table + var section = _executable.SectionTable[index]; + uint address = section.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable); + if (address == 0) + return null; + + // Set the section size + uint size = section.SizeOfRawData; + lock (_sectionDataLock) + { + // If we already have cached data, just use that immediately + if (_sectionData.ContainsKey(index)) + return _sectionData[index]; + + // Populate the raw section data based on the source + byte[] sectionData = ReadFromDataSource((int)address, (int)size); + + // Cache and return the section data, even if null + _sectionData[index] = sectionData; + return sectionData; + } + } + + #endregion } } \ No newline at end of file