diff --git a/BinaryObjectScanner/FileType/Executable.cs b/BinaryObjectScanner/FileType/Executable.cs index faca897c..4890f08a 100644 --- a/BinaryObjectScanner/FileType/Executable.cs +++ b/BinaryObjectScanner/FileType/Executable.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using BinaryObjectScanner.Data; using BinaryObjectScanner.Interfaces; using SabreTools.IO.Extensions; +using SabreTools.Serialization.Interfaces; using SabreTools.Serialization.Wrappers; namespace BinaryObjectScanner.FileType @@ -44,43 +46,94 @@ namespace BinaryObjectScanner.FileType /// public string? Detect(Stream stream, string file, bool includeDebug) { - // Try to create a wrapper for the proper executable type - var wrapper = WrapperFactory.CreateExecutableWrapper(stream); - if (wrapper == null) + // Get all non-nested protections + var protections = DetectDict(stream, file, scanner: null, includeDebug); + if (protections.Count == 0) return null; // Create the internal list - var protections = new List(); + var protectionList = new List(); + foreach (string key in protections.Keys) + { + protectionList.AddRange(protections[key]); + } + + return string.Join(";", [.. protections]); + } + + /// + /// + /// Ideally, we wouldn't need to circumvent the proper handling of file types just for Executable, + /// but due to the complexity of scanning, this is not currently possible. + /// + public ProtectionDictionary DetectDict(Stream stream, string file, Scanner? scanner, bool includeDebug) + { + // Create the output dictionary + var protections = new ProtectionDictionary(); + + // Try to create a wrapper for the proper executable type + IWrapper? wrapper; + try + { + wrapper = WrapperFactory.CreateExecutableWrapper(stream); + if (wrapper == null) + return protections; + } + catch (Exception ex) + { + if (includeDebug) Console.WriteLine(ex); + return protections; + } // Only use generic content checks if we're in debug mode if (includeDebug) { - var contentProtections = RunContentChecks(file, stream, includeDebug); - protections.AddRange(contentProtections.Values); + var subProtections = RunContentChecks(file, stream, includeDebug); + protections.Append(file, subProtections.Values); } if (wrapper is MSDOS mz) { + // Standard checks var subProtections = RunExecutableChecks(file, mz, StaticChecks.MSDOSExecutableCheckClasses, includeDebug); - protections.AddRange(subProtections.Values); + protections.Append(file, subProtections.Values); + + // Extractable checks + var extractedProtections = HandleExtractableProtections(file, mz, subProtections.Keys, scanner, includeDebug); + protections.Append(extractedProtections); } else if (wrapper is LinearExecutable lex) { + // Standard checks var subProtections = RunExecutableChecks(file, lex, StaticChecks.LinearExecutableCheckClasses, includeDebug); - protections.AddRange(subProtections.Values); + protections.Append(file, subProtections.Values); + + // Extractable checks + var extractedProtections = HandleExtractableProtections(file, lex, subProtections.Keys, scanner, includeDebug); + protections.Append(extractedProtections); } else if (wrapper is NewExecutable nex) { + // Standard checks var subProtections = RunExecutableChecks(file, nex, StaticChecks.NewExecutableCheckClasses, includeDebug); - protections.AddRange(subProtections.Values); + protections.Append(file, subProtections.Values); + + // Extractable checks + var extractedProtections = HandleExtractableProtections(file, nex, subProtections.Keys, scanner, includeDebug); + protections.Append(extractedProtections); } else if (wrapper is PortableExecutable pex) { + // Standard checks var subProtections = RunExecutableChecks(file, pex, StaticChecks.PortableExecutableCheckClasses, includeDebug); - protections.AddRange(subProtections.Values); + protections.Append(file, subProtections.Values); + + // Extractable checks + var extractedProtections = HandleExtractableProtections(file, pex, subProtections.Keys, scanner, includeDebug); + protections.Append(extractedProtections); } - return string.Join(";", [.. protections]); + return protections; } #region Check Runners @@ -145,6 +198,7 @@ namespace BinaryObjectScanner.FileType /// Name of the source file of the executable, for tracking /// Executable to scan /// Set of checks to use + /// Scanner for handling recursive protections /// True to include debug data, false otherwise /// Set of protections in file, empty on error public IDictionary RunExecutableChecks(string file, T exe, List checks, bool includeDebug) @@ -176,6 +230,74 @@ namespace BinaryObjectScanner.FileType return protections; } + /// + /// Handle extractable protections, such as executable packers + /// + /// Name of the source file of the stream, for tracking + /// Executable to scan the contents of + /// Set of classes returned from Exectuable scans + /// Scanner for handling recursive protections + /// True to include debug data, false otherwise + /// Set of protections found from extraction, empty on error + private ProtectionDictionary HandleExtractableProtections(string file, T exe, IEnumerable checks, Scanner? scanner, bool includeDebug) + where T : WrapperBase + where U : IExecutableCheck + { + // Create the output dictionary + var protections = new ProtectionDictionary(); + + // If we have an invalid set of classes + if (checks == null || !checks.Any()) + return protections; + + // If we have any extractable packers + var extractables = checks + .Where(c => c is IExtractableExecutable) + .Select(c => c as IExtractableExecutable); + extractables.IterateWithAction(extractable => + { + // If we have an invalid extractable somehow + if (extractable == null) + return; + + // If the extractable file itself fails + try + { + // Extract and get the output path + string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + bool extracted = extractable.Extract(file, exe, tempPath, includeDebug); + + // Collect and format all found protections + ProtectionDictionary? subProtections = null; + if (extracted) + subProtections = scanner?.GetProtections(tempPath); + + // If temp directory cleanup fails + try + { + if (Directory.Exists(tempPath)) + Directory.Delete(tempPath, true); + } + catch (Exception ex) + { + if (includeDebug) Console.WriteLine(ex); + } + + // Prepare the returned protections + subProtections?.StripFromKeys(tempPath); + subProtections?.PrependToKeys(file); + if (subProtections != null) + protections.Append(subProtections); + } + catch (Exception ex) + { + if (includeDebug) Console.WriteLine(ex); + } + }); + + return protections; + } + #endregion #region Helpers diff --git a/BinaryObjectScanner/Handler.cs b/BinaryObjectScanner/Handler.cs deleted file mode 100644 index 1ccfc436..00000000 --- a/BinaryObjectScanner/Handler.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using BinaryObjectScanner.Data; -using BinaryObjectScanner.Interfaces; - -namespace BinaryObjectScanner -{ - internal static class Handler - { - #region Multiple Implementation Wrappers - - /// - /// Handle a single path based on all path check implementations - /// - /// Path of the file or directory to check - /// Scanner object to use for options and scanning - /// Set of protections in file, null on error - public static ProtectionDictionary HandlePathChecks(string path, IEnumerable? files) - { - // Create the output dictionary - var protections = new ProtectionDictionary(); - - // Preprocess the list of files - files = files? - .Select(f => f.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))? - .ToList(); - - // Iterate through all checks - StaticChecks.PathCheckClasses.IterateWithAction(checkClass => - { - var subProtections = checkClass.PerformCheck(path, files); - protections.Append(path, subProtections); - }); - - return protections; - } - - #endregion - - #region Single Implementation Handlers - - /// - /// Handle files based on an IDetectable implementation - /// - /// IDetectable class representing the file type - /// Name of the source file of the stream, for tracking - /// Stream to scan the contents of - /// True to include debug data, false otherwise - /// Set of protections in file, null on error - public static List? HandleDetectable(IDetectable impl, string fileName, Stream stream, bool includeDebug) - { - var protection = impl.Detect(stream, fileName, includeDebug); - return ProcessProtectionString(protection); - } - - /// - /// Handle files based on an IPathCheck implementation - /// - /// IPathCheck class representing the file type - /// Path of the file or directory to check - /// Set of protections in path, empty on error - private static List PerformCheck(this IPathCheck impl, string? path, IEnumerable? files) - { - // If we have an invalid path - if (string.IsNullOrEmpty(path)) - return []; - - // Setup the list - var protections = new List(); - - // If we have a file path - if (File.Exists(path)) - { - var protection = impl.CheckFilePath(path!); - var subProtections = ProcessProtectionString(protection); - if (subProtections != null) - protections.AddRange(subProtections); - } - - // If we have a directory path - if (Directory.Exists(path) && files?.Any() == true) - { - var subProtections = impl.CheckDirectoryPath(path!, files); - if (subProtections != null) - protections.AddRange(subProtections); - } - - return protections; - } - - #endregion - - #region Helpers - - /// - /// Process a protection string if it includes multiple protections - /// - /// Protection string to process - /// Set of protections parsed, null on error - private static List? ProcessProtectionString(string? protection) - { - // If we have an invalid protection string - if (string.IsNullOrEmpty(protection)) - return null; - - // Setup the output queue - var protections = new List(); - - // If we have an indicator of multiple protections - if (protection!.Contains(";")) - { - var splitProtections = protection.Split(';'); - protections.AddRange(splitProtections); - } - else - { - protections.Add(protection); - } - - return protections; - } - - #endregion - } -} diff --git a/BinaryObjectScanner/Scanner.cs b/BinaryObjectScanner/Scanner.cs index c5288265..2a71b4ae 100644 --- a/BinaryObjectScanner/Scanner.cs +++ b/BinaryObjectScanner/Scanner.cs @@ -6,7 +6,6 @@ using BinaryObjectScanner.Data; using BinaryObjectScanner.FileType; using BinaryObjectScanner.Interfaces; using SabreTools.IO.Extensions; -using SabreTools.Serialization.Interfaces; using SabreTools.Serialization.Wrappers; namespace BinaryObjectScanner @@ -70,18 +69,18 @@ namespace BinaryObjectScanner /// /// Path to scan /// Dictionary of list of strings representing the found protections - public ProtectionDictionary? GetProtections(string path) + public ProtectionDictionary GetProtections(string path) => GetProtections([path]); /// /// Scan the list of paths and get all found protections /// /// Dictionary of list of strings representing the found protections - public ProtectionDictionary? GetProtections(List? paths) + public ProtectionDictionary GetProtections(List? paths) { // If we have no paths, we can't scan if (paths == null || !paths.Any()) - return null; + return []; // Set a starting starting time for debug output DateTime startTime = DateTime.UtcNow; @@ -106,7 +105,7 @@ namespace BinaryObjectScanner // Scan for path-detectable protections if (_options.ScanPaths) { - var directoryPathProtections = Handler.HandlePathChecks(path, files); + var directoryPathProtections = HandlePathChecks(path, files); protections.Append(directoryPathProtections); } @@ -127,7 +126,7 @@ namespace BinaryObjectScanner // Scan for path-detectable protections if (_options.ScanPaths) { - var filePathProtections = Handler.HandlePathChecks(file, files: null); + var filePathProtections = HandlePathChecks(file, files: null); if (filePathProtections != null && filePathProtections.Any()) protections.Append(filePathProtections); } @@ -158,7 +157,7 @@ namespace BinaryObjectScanner // Scan for path-detectable protections if (_options.ScanPaths) { - var filePathProtections = Handler.HandlePathChecks(path, files: null); + var filePathProtections = HandlePathChecks(path, files: null); if (filePathProtections != null && filePathProtections.Any()) protections.Append(filePathProtections); } @@ -197,11 +196,11 @@ namespace BinaryObjectScanner /// /// Path to the file to scan /// Dictionary of list of strings representing the found protections - private ProtectionDictionary? GetInternalProtections(string file) + private ProtectionDictionary GetInternalProtections(string file) { // Quick sanity check before continuing if (!File.Exists(file)) - return null; + return []; // Open the file and begin scanning try @@ -226,11 +225,11 @@ namespace BinaryObjectScanner /// Name of the source file of the stream, for tracking /// Stream to scan the contents of /// Dictionary of list of strings representing the found protections - private ProtectionDictionary? GetInternalProtections(string fileName, Stream stream) + private ProtectionDictionary GetInternalProtections(string fileName, Stream stream) { // Quick sanity check before continuing if (stream == null || !stream.CanRead || !stream.CanSeek) - return null; + return []; // Initialize the protections found var protections = new ProtectionDictionary(); @@ -252,13 +251,13 @@ namespace BinaryObjectScanner { if (_options.IncludeDebug) Console.WriteLine(ex); - return null; + return []; } // Get the file type either from magic number or extension WrapperType fileType = WrapperFactory.GetFileType(magic, extension); if (fileType == WrapperType.UNKNOWN) - return null; + return []; #region Non-Archive File Types @@ -273,32 +272,16 @@ namespace BinaryObjectScanner { executable.IncludeGameEngines = _options.ScanGameEngines; executable.IncludePackers = _options.ScanPackers; - var subProtections = ProcessExecutable(executable, fileName, stream); - if (subProtections != null) - protections.Append(subProtections); + + var subProtections = executable.DetectDict(stream, fileName, this, _options.IncludeDebug); + protections.Append(subProtections); } // Otherwise, use the default implementation else { - var subProtections = Handler.HandleDetectable(detectable, fileName, stream, _options.IncludeDebug); - if (subProtections != null) - protections.Append(fileName, subProtections); - } - - var subProtection = detectable.Detect(stream, fileName, _options.IncludeDebug); - if (!string.IsNullOrEmpty(subProtection)) - { - // If we have an indicator of multiple protections - if (subProtection.Contains(';')) - { - var splitProtections = subProtection!.Split(';'); - protections.Append(fileName, splitProtections); - } - else - { - protections.Append(fileName, subProtection!); - } + var subProtection = detectable.Detect(stream, fileName, _options.IncludeDebug); + protections.Append(fileName, ProcessProtectionString(subProtection)); } } @@ -363,150 +346,96 @@ namespace BinaryObjectScanner #endregion - #region Executable Handling + #region Path Handling /// - /// Process scanning for an Executable type + /// Handle a single path based on all path check implementations /// - /// Executable instance for processing - /// Name of the source file of the stream, for tracking - /// Stream to scan the contents of - /// - /// Ideally, we wouldn't need to circumvent the proper handling of file types just for Executable, - /// but due to the complexity of scanning, this is not currently possible. - /// - private ProtectionDictionary? ProcessExecutable(Executable executable, string fileName, Stream stream) + /// Path of the file or directory to check + /// Scanner object to use for options and scanning + /// Set of protections in file, null on error + private static ProtectionDictionary HandlePathChecks(string path, IEnumerable? files) { - // Try to create a wrapper for the proper executable type - IWrapper? wrapper; - try - { - wrapper = WrapperFactory.CreateExecutableWrapper(stream); - if (wrapper == null) - return null; - } - catch (Exception ex) - { - if (_options.IncludeDebug) Console.WriteLine(ex); - return null; - } - // Create the output dictionary var protections = new ProtectionDictionary(); - // Only use generic content checks if we're in debug mode - if (_options.IncludeDebug) - { - var subProtections = executable.RunContentChecks(fileName, stream, _options.IncludeDebug); - protections.Append(fileName, subProtections.Values); - } + // Preprocess the list of files + files = files? + .Select(f => f.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar))? + .ToList(); - if (wrapper is MSDOS mz) + // Iterate through all checks + StaticChecks.PathCheckClasses.IterateWithAction(checkClass => { - // Standard checks - var subProtections = executable.RunExecutableChecks(fileName, mz, StaticChecks.MSDOSExecutableCheckClasses, _options.IncludeDebug); - protections.Append(fileName, subProtections.Values); - - // Extractable checks - var extractedProtections = HandleExtractableProtections(fileName, mz, subProtections.Keys); - protections.Append(extractedProtections); - } - else if (wrapper is LinearExecutable lex) - { - // Standard checks - var subProtections = executable.RunExecutableChecks(fileName, lex, StaticChecks.LinearExecutableCheckClasses, _options.IncludeDebug); - protections.Append(fileName, subProtections.Values); - - // Extractable checks - var extractedProtections = HandleExtractableProtections(fileName, lex, subProtections.Keys); - protections.Append(extractedProtections); - } - else if (wrapper is NewExecutable nex) - { - // Standard checks - var subProtections = executable.RunExecutableChecks(fileName, nex, StaticChecks.NewExecutableCheckClasses, _options.IncludeDebug); - protections.Append(fileName, subProtections.Values); - - // Extractable checks - var extractedProtections = HandleExtractableProtections(fileName, nex, subProtections.Keys); - protections.Append(extractedProtections); - } - else if (wrapper is PortableExecutable pex) - { - // Standard checks - var subProtections = executable.RunExecutableChecks(fileName, pex, StaticChecks.PortableExecutableCheckClasses, _options.IncludeDebug); - protections.Append(fileName, subProtections.Values); - - // Extractable checks - var extractedProtections = HandleExtractableProtections(fileName, pex, subProtections.Keys); - protections.Append(extractedProtections); - } + var subProtections = PerformCheck(checkClass, path, files); + protections.Append(path, subProtections); + }); return protections; } /// - /// Handle extractable protections, such as executable packers + /// Handle files based on an IPathCheck implementation /// - /// Name of the source file of the stream, for tracking - /// Executable to scan the contents of - /// Set of classes returned from Exectuable scans - /// Set of protections found from extraction, null on error - private ProtectionDictionary HandleExtractableProtections(string file, T exe, IEnumerable checks) - where T : WrapperBase - where U : IExecutableCheck + /// IPathCheck class representing the file type + /// Path of the file or directory to check + /// Set of protections in path, empty on error + private static List PerformCheck(IPathCheck impl, string? path, IEnumerable? files) { - // Create the output dictionary - var protections = new ProtectionDictionary(); + // If we have an invalid path + if (string.IsNullOrEmpty(path)) + return []; - // If we have an invalid set of classes - if (checks == null || !checks.Any()) - return protections; + // Setup the list + var protections = new List(); - // If we have any extractable packers - var extractables = checks - .Where(c => c is IExtractableExecutable) - .Select(c => c as IExtractableExecutable); - extractables.IterateWithAction(extractable => + // If we have a file path + if (File.Exists(path)) { - // If we have an invalid extractable somehow - if (extractable == null) - return; + var protection = impl.CheckFilePath(path!); + var subProtections = ProcessProtectionString(protection); + protections.AddRange(subProtections); + } - // If the extractable file itself fails - try - { - // Extract and get the output path - string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - bool extracted = extractable.Extract(file, exe, tempPath, _options.IncludeDebug); + // If we have a directory path + if (Directory.Exists(path) && files?.Any() == true) + { + var subProtections = impl.CheckDirectoryPath(path!, files); + if (subProtections != null) + protections.AddRange(subProtections); + } - // Collect and format all found protections - ProtectionDictionary? subProtections = null; - if (extracted) - subProtections = GetProtections(tempPath); + return protections; + } - // If temp directory cleanup fails - try - { - if (Directory.Exists(tempPath)) - Directory.Delete(tempPath, true); - } - catch (Exception ex) - { - if (_options.IncludeDebug) Console.WriteLine(ex); - } + #endregion - // Prepare the returned protections - subProtections?.StripFromKeys(tempPath); - subProtections?.PrependToKeys(file); - if (subProtections != null) - protections.Append(subProtections); - } - catch (Exception ex) - { - if (_options.IncludeDebug) Console.WriteLine(ex); - } - }); + #region Helpers + + /// + /// Process a protection string if it includes multiple protections + /// + /// Protection string to process + /// Set of protections parsed, empty on error + internal static List ProcessProtectionString(string? protection) + { + // If we have an invalid protection string + if (string.IsNullOrEmpty(protection)) + return []; + + // Setup the output queue + var protections = new List(); + + // If we have an indicator of multiple protections + if (protection!.Contains(";")) + { + var splitProtections = protection.Split(';'); + protections.AddRange(splitProtections); + } + else + { + protections.Add(protection); + } return protections; }