using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using BinaryObjectScanner.Interfaces; using BinaryObjectScanner.Utilities; using SabreTools.Serialization.Wrappers; namespace BinaryObjectScanner.FileType { /// /// Executable or library /// /// /// Due to the complexity of executables, all extraction handling /// another class that is used by the scanner /// public class Executable : IDetectable { #region Properties /// /// Determines if game engines are counted as detected protections or not /// public bool IncludeGameEngines { get; set; } /// /// Determines if packers are counted as detected protections or not /// public bool IncludePackers { get; set; } /// /// Cache for all IContentCheck types /// public static IEnumerable ContentCheckClasses { get { if (contentCheckClasses == null) contentCheckClasses = InitCheckClasses(); return contentCheckClasses ?? Enumerable.Empty(); } } /// /// Cache for all ILinearExecutableCheck types /// public static IEnumerable LinearExecutableCheckClasses { get { if (linearExecutableCheckClasses == null) linearExecutableCheckClasses = InitCheckClasses(); return linearExecutableCheckClasses ?? Enumerable.Empty(); } } /// /// Cache for all IMSDOSExecutableCheck types /// public static IEnumerable MSDOSExecutableCheckClasses { get { if (msdosExecutableCheckClasses == null) msdosExecutableCheckClasses = InitCheckClasses(); return msdosExecutableCheckClasses ?? Enumerable.Empty(); } } /// /// Cache for all INewExecutableCheck types /// public static IEnumerable NewExecutableCheckClasses { get { if (newExecutableCheckClasses == null) newExecutableCheckClasses = InitCheckClasses(); return newExecutableCheckClasses ?? Enumerable.Empty(); } } /// /// Cache for all IPortableExecutableCheck types /// public static IEnumerable PortableExecutableCheckClasses { get { if (portableExecutableCheckClasses == null) portableExecutableCheckClasses = InitCheckClasses(); return portableExecutableCheckClasses ?? Enumerable.Empty(); } } #endregion #region Internal Instances /// /// Cache for all IContentCheck types /// #if NET48 private static IEnumerable contentCheckClasses; #else private static IEnumerable? contentCheckClasses; #endif /// /// Cache for all ILinearExecutableCheck types /// #if NET48 private static IEnumerable linearExecutableCheckClasses; #else private static IEnumerable? linearExecutableCheckClasses; #endif /// /// Cache for all IMSDOSExecutableCheck types /// #if NET48 private static IEnumerable msdosExecutableCheckClasses; #else private static IEnumerable? msdosExecutableCheckClasses; #endif /// /// Cache for all INewExecutableCheck types /// #if NET48 private static IEnumerable newExecutableCheckClasses; #else private static IEnumerable? newExecutableCheckClasses; #endif /// /// Cache for all IPortableExecutableCheck types /// #if NET48 private static IEnumerable portableExecutableCheckClasses; #else private static IEnumerable? portableExecutableCheckClasses; #endif #endregion /// #if NET48 public string Detect(string file, bool includeDebug) #else public string? Detect(string file, bool includeDebug) #endif { if (!File.Exists(file)) return null; using (var fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read)) { return Detect(fs, file, includeDebug); } } /// #if NET48 public string Detect(Stream stream, string file, bool includeDebug) #else public string? Detect(Stream stream, string file, bool includeDebug) #endif { // Try to create a wrapper for the proper executable type var wrapper = WrapperFactory.CreateExecutableWrapper(stream); if (wrapper == null) return null; // Create the internal queue var protections = new ConcurrentQueue(); // Only use generic content checks if we're in debug mode if (includeDebug) { var subProtections = RunContentChecks(file, stream, includeDebug); if (subProtections != null) protections.AddRange(subProtections.Values.ToArray()); } if (wrapper is MSDOS mz) { var subProtections = RunMSDOSExecutableChecks(file, stream, mz, includeDebug); if (subProtections != null) protections.AddRange(subProtections.Values.ToArray()); } else if (wrapper is LinearExecutable lex) { var subProtections = RunLinearExecutableChecks(file, stream, lex, includeDebug); if (subProtections != null) protections.AddRange(subProtections.Values.ToArray()); } else if (wrapper is NewExecutable nex) { var subProtections = RunNewExecutableChecks(file, stream, nex, includeDebug); if (subProtections != null) protections.AddRange(subProtections.Values.ToArray()); } else if (wrapper is PortableExecutable pex) { var subProtections = RunPortableExecutableChecks(file, stream, pex, includeDebug); if (subProtections != null) protections.AddRange(subProtections.Values.ToArray()); } return string.Join(";", protections); } #region Check Runners /// /// Handle a single file based on all content check implementations /// /// 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 #if NET48 public ConcurrentDictionary RunContentChecks(string file, Stream stream, bool includeDebug) #else public ConcurrentDictionary? RunContentChecks(string? file, Stream stream, bool includeDebug) #endif { // If we have an invalid file if (string.IsNullOrWhiteSpace(file)) return null; else if (!File.Exists(file)) return null; // Read the file contents #if NET48 byte[] fileContent = null; #else byte[]? fileContent = null; #endif try { using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true)) { fileContent = br.ReadBytes((int)stream.Length); if (fileContent == null) return null; } } catch (Exception ex) { if (includeDebug) Console.WriteLine(ex); return null; } // Create the output dictionary var protections = new ConcurrentDictionary(); // Iterate through all checks Parallel.ForEach(ContentCheckClasses, checkClass => { // Get the protection for the class, if possible string protection = checkClass.CheckContents(file, fileContent, includeDebug); if (string.IsNullOrWhiteSpace(protection)) return; // If we are filtering on game engines if (CheckIfGameEngine(checkClass) && !IncludeGameEngines) return; // If we are filtering on packers if (CheckIfPacker(checkClass) && !IncludePackers) return; protections.TryAdd(checkClass, protection); }); return protections; } /// /// Handle a single file based on all linear executable check implementations /// /// Name of the source file of the executable, for tracking /// Executable to scan /// True to include debug data, false otherwise /// Set of protections in file, null on error public ConcurrentDictionary RunLinearExecutableChecks(string file, Stream stream, LinearExecutable lex, bool includeDebug) { // Create the output dictionary var protections = new ConcurrentDictionary(); // Iterate through all checks Parallel.ForEach(LinearExecutableCheckClasses, checkClass => { // Get the protection for the class, if possible string protection = checkClass.CheckLinearExecutable(file, lex, includeDebug); if (string.IsNullOrWhiteSpace(protection)) return; // If we are filtering on game engines if (CheckIfGameEngine(checkClass) && !IncludeGameEngines) return; // If we are filtering on packers if (CheckIfPacker(checkClass) && !IncludePackers) return; protections.TryAdd(checkClass, protection); }); return protections; } /// /// Handle a single file based on all MS-DOS executable check implementations /// /// Name of the source file of the executable, for tracking /// Executable to scan /// True to include debug data, false otherwise /// Set of protections in file, null on error public ConcurrentDictionary RunMSDOSExecutableChecks(string file, Stream stream, MSDOS mz, bool includeDebug) { // Create the output dictionary var protections = new ConcurrentDictionary(); // Iterate through all checks Parallel.ForEach(MSDOSExecutableCheckClasses, checkClass => { // Get the protection for the class, if possible string protection = checkClass.CheckMSDOSExecutable(file, mz, includeDebug); if (string.IsNullOrWhiteSpace(protection)) return; // If we are filtering on game engines if (CheckIfGameEngine(checkClass) && !IncludeGameEngines) return; // If we are filtering on packers if (CheckIfPacker(checkClass) && !IncludePackers) return; protections.TryAdd(checkClass, protection); }); return protections; } /// /// Handle a single file based on all new executable check implementations /// /// Name of the source file of the executable, for tracking /// Executable to scan /// True to include debug data, false otherwise /// Set of protections in file, null on error public ConcurrentDictionary RunNewExecutableChecks(string file, Stream stream, NewExecutable nex, bool includeDebug) { // Create the output dictionary var protections = new ConcurrentDictionary(); // Iterate through all checks Parallel.ForEach(NewExecutableCheckClasses, checkClass => { // Get the protection for the class, if possible string protection = checkClass.CheckNewExecutable(file, nex, includeDebug); if (string.IsNullOrWhiteSpace(protection)) return; // If we are filtering on game engines if (CheckIfGameEngine(checkClass) && !IncludeGameEngines) return; // If we are filtering on packers if (CheckIfPacker(checkClass) && !IncludePackers) return; protections.TryAdd(checkClass, protection); }); return protections; } /// /// Handle a single file based on all portable executable check implementations /// /// Name of the source file of the executable, for tracking /// Executable to scan /// True to include debug data, false otherwise /// Set of protections in file, null on error public ConcurrentDictionary RunPortableExecutableChecks(string file, Stream stream, PortableExecutable pex, bool includeDebug) { // Create the output dictionary var protections = new ConcurrentDictionary(); // Iterate through all checks Parallel.ForEach(PortableExecutableCheckClasses, checkClass => { // Get the protection for the class, if possible string protection = checkClass.CheckPortableExecutable(file, pex, includeDebug); if (string.IsNullOrWhiteSpace(protection)) return; // If we are filtering on game engines if (CheckIfGameEngine(checkClass) && !IncludeGameEngines) return; // If we are filtering on packers if (CheckIfPacker(checkClass) && !IncludePackers) return; protections.TryAdd(checkClass, protection); }); return protections; } #endregion #region Initializers /// /// Initialize all implementations of a type /// #if NET48 private static IEnumerable InitCheckClasses() #else private static IEnumerable? InitCheckClasses() #endif => InitCheckClasses(typeof(GameEngine._DUMMY).Assembly) ?? Enumerable.Empty() .Concat(InitCheckClasses(typeof(Packer._DUMMY).Assembly) ?? Enumerable.Empty()) .Concat(InitCheckClasses(typeof(Protection._DUMMY).Assembly) ?? Enumerable.Empty()); /// /// Initialize all implementations of a type /// #if NET48 private static IEnumerable InitCheckClasses(Assembly assembly) #else private static IEnumerable? InitCheckClasses(Assembly assembly) #endif { return assembly.GetTypes() .Where(t => t.IsClass && t.GetInterface(typeof(T).Name) != null) .Select(t => (T)Activator.CreateInstance(t)); } #endregion #region Helpers /// /// Check to see if an implementation is a game engine using reflection /// /// Implementation that was last used to check private static bool CheckIfGameEngine(object impl) { return impl.GetType().Namespace.ToLowerInvariant().Contains("gameengine"); } /// /// Check to see if an implementation is a packer using reflection /// /// Implementation that was last used to check private static bool CheckIfPacker(object impl) { return impl.GetType().Namespace.ToLowerInvariant().Contains("packer"); } #endregion } }