From 7d2edd315c05aa68e253ce2b3c2c05d6f8f4278d Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sat, 3 Dec 2022 13:17:29 -0800 Subject: [PATCH] Add stub data to cache, resource/section finding --- BurnOutSharp.Wrappers/PortableExecutable.cs | 218 +++++++++++++++++++- 1 file changed, 209 insertions(+), 9 deletions(-) diff --git a/BurnOutSharp.Wrappers/PortableExecutable.cs b/BurnOutSharp.Wrappers/PortableExecutable.cs index 8fe1a9ea..17a7caa2 100644 --- a/BurnOutSharp.Wrappers/PortableExecutable.cs +++ b/BurnOutSharp.Wrappers/PortableExecutable.cs @@ -635,6 +635,15 @@ 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 /// @@ -690,6 +699,73 @@ 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 /// @@ -705,9 +781,25 @@ namespace BurnOutSharp.Wrappers if (!DataSourceIsValid()) return null; - // Make sure the section exists - if (SectionNames == null) - 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); @@ -738,7 +830,91 @@ namespace BurnOutSharp.Wrappers } } - // TODO: Make a method that allows you to find resources + /// + /// 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 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 @@ -925,7 +1101,7 @@ namespace BurnOutSharp.Wrappers } // If we have a custom resource type - else if (types != null && types.Count > 0 && types[0] is string resourceString) + else if (types != null && types.Count > 0 && types[0] is string) { value = entry.Data; } @@ -938,6 +1114,21 @@ namespace BurnOutSharp.Wrappers #region Printing + /// + /// Print all sections, including their start and end addresses + /// + public void PrintAllSections() + { + foreach (var section in _executable.SectionTable) + { + // TODO: Handle long section names with leading `/` + string sectionName = Encoding.UTF8.GetString(section.Name); + int sectionAddr = (int)section.PointerToRawData; + int sectionEnd = sectionAddr + (int)section.VirtualSize; + Console.WriteLine($"{sectionName}: {sectionAddr} -> {sectionEnd}"); + } + } + /// public override void Print() { @@ -1417,7 +1608,14 @@ namespace BurnOutSharp.Wrappers { Console.WriteLine($" Certificate Data [Binary]"); Console.WriteLine(" -------------------------"); - Console.WriteLine($" {BitConverter.ToString(entry.Certificate).Replace("-", string.Empty)}"); + try + { + Console.WriteLine($" {BitConverter.ToString(entry.Certificate).Replace("-", string.Empty)}"); + } + catch + { + Console.WriteLine($" [DATA TOO LARGE TO FORMAT]"); + } } Console.WriteLine(); @@ -2432,15 +2630,17 @@ namespace BurnOutSharp.Wrappers default: Console.WriteLine($"{padding}Type {(Models.PortableExecutable.ResourceType)resourceType} found, not parsed yet"); //Console.WriteLine($"{padding}Data: {BitConverter.ToString(entry.Data).Replace("-", string.Empty)}"); - //Console.WriteLine($"{padding}Data: {Encoding.Unicode.GetString(entry.Data)}"); + Console.WriteLine($"{padding}Data: {Encoding.ASCII.GetString(entry.Data)}"); break; } } else if (types != null && types.Count > 0 && types[0] is string resourceString) { Console.WriteLine($"{padding}Custom data type: {resourceString}"); - //Console.WriteLine($"{padding}Data: {BitConverter.ToString(entry.Data).Replace("-", string.Empty)}"); - //Console.WriteLine($"{padding}Data: {Encoding.Unicode.GetString(entry.Data)}"); + if (entry.Data[0] == 0x4D && entry.Data[1] == 0x5A) + Console.WriteLine($"{padding}Data: [Embedded Executable File]"); // TODO: Parse this out and print separately + else + Console.WriteLine($"{padding}Data: {Encoding.ASCII.GetString(entry.Data)}"); } Console.WriteLine();