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();