using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml;
using BurnOutSharp.ExecutableType.Microsoft.PE;
using BurnOutSharp.ExecutableType.Microsoft.PE.Entries;
using BurnOutSharp.ExecutableType.Microsoft.PE.Sections;
using BurnOutSharp.ExecutableType.Microsoft.PE.Tables;
using BurnOutSharp.ExecutableType.Microsoft.Resources;
namespace BurnOutSharp.Tools
{
internal static class Utilities
{
#region Dictionary Manipulation
///
/// Append one result to a results dictionary
///
/// Dictionary to append to
/// Key to add information to
/// String value to add
public static void AppendToDictionary(ConcurrentDictionary> original, string key, string value)
{
// If the value is empty, don't add it
if (string.IsNullOrWhiteSpace(value))
return;
var values = new ConcurrentQueue();
values.Enqueue(value);
AppendToDictionary(original, key, values);
}
///
/// Append one result to a results dictionary
///
/// Dictionary to append to
/// Key to add information to
/// String value to add
public static void AppendToDictionary(ConcurrentDictionary> original, string key, ConcurrentQueue values)
{
// If the dictionary is null, just return
if (original == null)
return;
// Use a placeholder value if the key is null
key = key ?? "NO FILENAME";
// Add the key if needed and then append the lists
original.TryAdd(key, new ConcurrentQueue());
original[key].AddRange(values);
}
///
/// Append one results dictionary to another
///
/// Dictionary to append to
/// Dictionary to pull from
public static void AppendToDictionary(ConcurrentDictionary> original, ConcurrentDictionary> addition)
{
// If either dictionary is missing, just return
if (original == null || addition == null)
return;
// Loop through each of the addition keys and add accordingly
foreach (string key in addition.Keys)
{
original.TryAdd(key, new ConcurrentQueue());
original[key].AddRange(addition[key]);
}
}
///
/// Remove empty or null keys from a results dictionary
///
/// Dictionary to clean
public static void ClearEmptyKeys(ConcurrentDictionary> original)
{
// If the dictionary is missing, we can't do anything
if (original == null)
return;
// Get a list of all of the keys
var keys = original.Keys.ToList();
// Iterate and reset keys
for (int i = 0; i < keys.Count; i++)
{
// Get the current key
string key = keys[i];
// If the key is empty, remove it
if (original[key] == null || !original[key].Any())
original.TryRemove(key, out _);
}
}
///
/// Prepend a parent path from dictionary keys, if possible
///
/// Dictionary to strip values from
/// Path to strip from the keys
public static void PrependToKeys(ConcurrentDictionary> original, string pathToPrepend)
{
// If the dictionary is missing, we can't do anything
if (original == null)
return;
// Use a placeholder value if the path is null
pathToPrepend = (pathToPrepend ?? "ARCHIVE").TrimEnd(Path.DirectorySeparatorChar);
// Get a list of all of the keys
var keys = original.Keys.ToList();
// Iterate and reset keys
for (int i = 0; i < keys.Count; i++)
{
// Get the current key
string currentKey = keys[i];
// Otherwise, get the new key name and transfer over
string newKey = $"{pathToPrepend}{Path.DirectorySeparatorChar}{currentKey.Trim(Path.DirectorySeparatorChar)}";
original[newKey] = original[currentKey];
original.TryRemove(currentKey, out _);
}
}
///
/// Strip a parent path from dictionary keys, if possible
///
/// Dictionary to strip values from
/// Path to strip from the keys
public static void StripFromKeys(ConcurrentDictionary> original, string pathToStrip)
{
// If either is missing, we can't do anything
if (original == null || string.IsNullOrEmpty(pathToStrip))
return;
// Get a list of all of the keys
var keys = original.Keys.ToList();
// Iterate and reset keys
for (int i = 0; i < keys.Count; i++)
{
// Get the current key
string currentKey = keys[i];
// If the key doesn't start with the path, don't touch it
if (!currentKey.StartsWith(pathToStrip, StringComparison.OrdinalIgnoreCase))
continue;
// Otherwise, get the new key name and transfer over
string newKey = currentKey.Substring(pathToStrip.Length);
original[newKey] = original[currentKey];
original.TryRemove(currentKey, out _);
}
}
#endregion
#region Concurrent Manipulation
///
/// Add a range of values from one queue to another
///
/// Queue to add data to
/// Queue to get data from
public static void AddRange(this ConcurrentQueue original, ConcurrentQueue values)
{
while (!values.IsEmpty)
{
if (!values.TryDequeue(out string value))
return;
original.Enqueue(value);
}
}
#endregion
#region Executable Information
///
/// Get the company name as reported by the filesystem
///
/// PortableExecutable representing the file contents
/// Company name string, null on error
public static string GetCompanyName(PortableExecutable pex) => GetResourceString(pex, "CompanyName");
///
/// Get the file description as reported by the filesystem
///
/// PortableExecutable representing the file contents
/// Description string, null on error
public static string GetFileDescription(PortableExecutable pex) => GetResourceString(pex, "FileDescription");
///
/// Get the file version as reported by the filesystem
///
/// Byte array representing the file contents
/// Version string, null on error
public static string GetFileVersion(byte[] fileContent)
{
if (fileContent == null || !fileContent.Any())
return null;
return GetFileVersion(new PortableExecutable(fileContent, 0));
}
///
/// Get the file version as reported by the filesystem
///
/// File to check for version
/// Version string, null on error
public static string GetFileVersion(string file)
{
var fvinfo = GetFileVersionInfo(file);
if (fvinfo?.FileVersion == null)
return string.Empty;
if (fvinfo.FileVersion != "")
return fvinfo.FileVersion.Replace(", ", ".");
else
return fvinfo.ProductVersion.Replace(", ", ".");
}
///
/// Get the file version as reported by the filesystem
///
/// PortableExecutable representing the file contents
/// Version string, null on error
public static string GetFileVersion(PortableExecutable pex)
{
string version = GetResourceString(pex, "FileVersion");
if (!string.IsNullOrWhiteSpace(version))
return version.Replace(", ", ".");
version = GetResourceString(pex, "ProductVersion");
if (!string.IsNullOrWhiteSpace(version))
return version.Replace(", ", ".");
return null;
}
///
/// Wrapper for GetFileVersion for use in content matching
///
/// File to check for version
/// Byte array representing the file contents
/// Last matched positions in the contents
/// Version string, null on error
public static string GetFileVersion(string file, byte[] fileContent, List positions) => GetFileVersion(fileContent);
///
/// Wrapper for GetFileVersion for use in path matching
///
/// File to check for version
/// Full list of input paths
/// Version string, null on error
public static string GetFileVersion(string firstMatchedString, IEnumerable files) => GetFileVersion(firstMatchedString);
///
/// Get the internal name as reported by the filesystem
///
/// PortableExecutable representing the file contents
/// Internal name string, null on error
public static string GetInternalName(PortableExecutable pex) => GetResourceString(pex, "InternalName");
///
/// Get the legal copyright as reported by the filesystem
///
/// PortableExecutable representing the file contents
/// Legal copyright string, null on error
public static string GetLegalCopyright(PortableExecutable pex) => GetResourceString(pex, "LegalCopyright");
///
/// Get the assembly version as determined by an embedded assembly manifest
///
/// Byte array representing the file contents
/// Version string, null on error
public static string GetManifestDescription(PortableExecutable pex)
{
// If we don't have a PE executable, just return null
var resourceSection = pex?.ResourceSection;
if (resourceSection == null)
return null;
// Read in the manifest to a string
string manifestString = FindAssemblyManifest(pex.ResourceSection);
if (string.IsNullOrWhiteSpace(manifestString))
return null;
// Try to read the XML in from the string
try
{
// Try to read the assembly
var assemblyNode = GetAssemblyNode(manifestString);
if (assemblyNode == null)
return null;
// Return the content of the description node, if possible
var descriptionNode = assemblyNode["description"];
if (descriptionNode == null)
return null;
return descriptionNode.InnerXml;
}
catch
{
return null;
}
}
///
/// Get the assembly version as determined by an embedded assembly manifest
///
/// PortableExecutable representing the file contents
/// Version string, null on error
public static string GetManifestVersion(PortableExecutable pex)
{
// If we don't have a PE executable, just return null
var resourceSection = pex?.ResourceSection;
if (resourceSection == null)
return null;
// Read in the manifest to a string
string manifestString = FindAssemblyManifest(pex.ResourceSection);
if (string.IsNullOrWhiteSpace(manifestString))
return null;
// Try to read the XML in from the string
try
{
// Try to read the assembly
var assemblyNode = GetAssemblyNode(manifestString);
if (assemblyNode == null)
return null;
// Try to read the assemblyIdentity
var assemblyIdentityNode = assemblyNode["assemblyIdentity"];
if (assemblyIdentityNode == null)
return null;
// Return the version attribute, if possible
return assemblyIdentityNode.GetAttribute("version");
}
catch
{
return null;
}
}
///
/// Get the original filename as reported by the filesystem
///
/// PortableExecutable representing the file contents
/// Original filename string, null on error
public static string GetOriginalFileName(PortableExecutable pex) => GetResourceString(pex, "OriginalFileName");
///
/// Get the product name as reported by the filesystem
///
/// PortableExecutable representing the file contents
/// Product name string, null on error
public static string GetProductName(PortableExecutable pex) => GetResourceString(pex, "ProductName");
///
/// Find resource data in a ResourceSection, if possible
///
/// ResourceSection from the executable
/// String to use if checking for data starting with a string
/// String to use if checking for data contains a string
/// String to use if checking for data ending with a string
/// Full encoded resource data, null on error
public static ResourceDataEntry FindResourceInSection(ResourceSection rs, string dataStart = null, string dataContains = null, string dataEnd = null)
{
if (rs == null)
return null;
return FindResourceInTable(rs.ResourceDirectoryTable, dataStart, dataContains, dataEnd);
}
///
/// Find resource data in a ResourceDirectoryTable, if possible
///
/// ResourceDirectoryTable representing a layer
/// String to use if checking for data starting with a string
/// String to use if checking for data contains a string
/// String to use if checking for data ending with a string
/// Full encoded resource data, null on error
private static ResourceDataEntry FindResourceInTable(ResourceDirectoryTable rdt, string dataStart, string dataContains, string dataEnd)
{
if (rdt == null)
return null;
try
{
foreach (var rdte in rdt.NamedEntries)
{
if (rdte.IsResourceDataEntry() && rdte.DataEntry != null)
{
if (dataStart != null && rdte.DataEntry.DataAsUTF8String.StartsWith(dataStart))
return rdte.DataEntry;
else if (dataContains != null && rdte.DataEntry.DataAsUTF8String.Contains(dataContains))
return rdte.DataEntry;
else if (dataEnd != null && rdte.DataEntry.DataAsUTF8String.EndsWith(dataStart))
return rdte.DataEntry;
}
else
{
var manifest = FindResourceInTable(rdte.Subdirectory, dataStart, dataContains, dataEnd);
if (manifest != null)
return manifest;
}
}
foreach (var rdte in rdt.IdEntries)
{
if (rdte.IsResourceDataEntry() && rdte.DataEntry != null)
{
if (dataStart != null && rdte.DataEntry.DataAsUTF8String.StartsWith(dataStart))
return rdte.DataEntry;
else if (dataContains != null && rdte.DataEntry.DataAsUTF8String.Contains(dataContains))
return rdte.DataEntry;
else if (dataEnd != null && rdte.DataEntry.DataAsUTF8String.EndsWith(dataStart))
return rdte.DataEntry;
}
else
{
var manifest = FindResourceInTable(rdte.Subdirectory, dataStart, dataContains, dataEnd);
if (manifest != null)
return manifest;
}
}
}
catch { }
return null;
}
///
/// Find the assembly manifest from a resource section, if possible
///
/// ResourceSection from the executable
/// Full assembly manifest, null on error
private static string FindAssemblyManifest(ResourceSection rs) => FindResourceInSection(rs, dataContains: "
/// Get the assembly identity node from an embedded manifest
///
/// String representing the XML document
/// Assembly identity node, if possible
private static XmlElement GetAssemblyNode(string manifestString)
{
// An invalid string means we can't read it
if (string.IsNullOrWhiteSpace(manifestString))
return null;
try
{
// Load the XML string as a document
var manifestDoc = new XmlDocument();
manifestDoc.LoadXml(manifestString);
// If the XML has no children, it's invalid
if (!manifestDoc.HasChildNodes)
return null;
// Try to read the assembly node
return manifestDoc["assembly"];
}
catch (Exception ex)
{
return null;
}
}
///
/// Get the file version info object related to a path, if possible
///
/// File to get information for
/// FileVersionInfo object on success, null on error
private static FileVersionInfo GetFileVersionInfo(string file)
{
if (file == null || !File.Exists(file))
return null;
try
{
return FileVersionInfo.GetVersionInfo(file);
}
catch
{
return null;
}
}
///
/// Get a resource string from the version info
///
/// PortableExecutable representing the file contents
/// Original filename string, null on error
private static string GetResourceString(PortableExecutable pex, string key)
{
var resourceStrings = GetVersionInfo(pex)?.ChildrenStringFileInfo?.Children?.Children;
if (resourceStrings == null)
return null;
var value = resourceStrings.FirstOrDefault(s => s.Key == key);
if (!string.IsNullOrWhiteSpace(value?.Value))
return value.Value.Trim(' ', '\0');
return null;
}
///
/// Get the version info object related to file contents, if possible
///
/// PortableExecutable representing the file contents
/// VersionInfo object on success, null on error
private static VersionInfo GetVersionInfo(PortableExecutable pex)
{
// If we don't have a PE executable, just return null
var resourceSection = pex?.ResourceSection;
if (resourceSection == null)
return null;
// Try to get the matching resource
var resource = FindResourceInSection(resourceSection, dataContains: "V\0S\0_\0V\0E\0R\0S\0I\0O\0N\0_\0I\0N\0F\0O\0");
if (resource?.Data == null)
return null;
try
{
int index = 0;
return VersionInfo.Deserialize(resource.Data, ref index);
}
catch (Exception ex)
{
// Console.WriteLine(ex);
return null;
}
}
#endregion
}
}