using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; namespace BinaryObjectScanner.Matching { /// /// Helper class for matching /// public static class MatchUtil { #region Array Content Matching /// /// Get all content matches for a given list of matchers /// /// File to check for matches /// Array to search /// Enumerable of ContentMatchSets to be run on the file /// True to include positional data, false otherwise /// List of strings representing the matched protections, null or empty otherwise public static ConcurrentQueue GetAllMatches( string file, byte[] stack, IEnumerable matchers, bool includeDebug = false) { return FindAllMatches(file, stack, matchers, includeDebug, false); } /// /// Get first content match for a given list of matchers /// /// File to check for matches /// Array to search /// Enumerable of ContentMatchSets to be run on the file /// True to include positional data, false otherwise /// String representing the matched protection, null otherwise public static string GetFirstMatch( string file, byte[] stack, IEnumerable matchers, bool includeDebug = false) { var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true); if (contentMatches == null || !contentMatches.Any()) return null; return contentMatches.First(); } /// /// Get the required set of content matches on a per Matcher basis /// /// File to check for matches /// Array to search /// Enumerable of ContentMatchSets to be run on the file /// True to include positional data, false otherwise /// True to stop after the first match, false otherwise /// List of strings representing the matched protections, null or empty otherwise private static ConcurrentQueue FindAllMatches( string file, byte[] stack, IEnumerable matchers, bool includeDebug, bool stopAfterFirst) { // If there's no mappings, we can't match if (matchers == null || !matchers.Any()) return null; // Initialize the queue of matched protections var matchedProtections = new ConcurrentQueue(); // Loop through and try everything otherwise foreach (var matcher in matchers) { // Determine if the matcher passes (bool passes, List positions) = matcher.MatchesAll(stack); if (!passes) continue; // Format the list of all positions found string positionsString = string.Join(", ", positions); // If we there is no version method, just return the protection name if (matcher.GetArrayVersion == null) { matchedProtections.Enqueue((matcher.ProtectionName ?? "Unknown Protection") + (includeDebug ? $" (Index {positionsString})" : string.Empty)); } // Otherwise, invoke the version method else { // A null version returned means the check didn't pass at the version step string version = matcher.GetArrayVersion(file, stack, positions); if (version == null) continue; matchedProtections.Enqueue($"{matcher.ProtectionName ?? "Unknown Protection"} {version}".Trim() + (includeDebug ? $" (Index {positionsString})" : string.Empty)); } // If we're stopping after the first protection, bail out here if (stopAfterFirst) return matchedProtections; } return matchedProtections; } #endregion #region Stream Content Matching /// /// Get all content matches for a given list of matchers /// /// File to check for matches /// Stream to search /// Enumerable of ContentMatchSets to be run on the file /// True to include positional data, false otherwise /// List of strings representing the matched protections, null or empty otherwise public static ConcurrentQueue GetAllMatches( string file, Stream stack, IEnumerable matchers, bool includeDebug = false) { return FindAllMatches(file, stack, matchers, includeDebug, false); } /// /// Get first content match for a given list of matchers /// /// File to check for matches /// Stream to search /// Enumerable of ContentMatchSets to be run on the file /// True to include positional data, false otherwise /// String representing the matched protection, null otherwise public static string GetFirstMatch( string file, Stream stack, IEnumerable matchers, bool includeDebug = false) { var contentMatches = FindAllMatches(file, stack, matchers, includeDebug, true); if (contentMatches == null || !contentMatches.Any()) return null; return contentMatches.First(); } /// /// Get the required set of content matches on a per Matcher basis /// /// File to check for matches /// Stream to search /// Enumerable of ContentMatchSets to be run on the file /// True to include positional data, false otherwise /// True to stop after the first match, false otherwise /// List of strings representing the matched protections, null or empty otherwise private static ConcurrentQueue FindAllMatches( string file, Stream stack, IEnumerable matchers, bool includeDebug, bool stopAfterFirst) { // If there's no mappings, we can't match if (matchers == null || !matchers.Any()) return null; // Initialize the queue of matched protections var matchedProtections = new ConcurrentQueue(); // Loop through and try everything otherwise foreach (var matcher in matchers) { // Determine if the matcher passes (bool passes, List positions) = matcher.MatchesAll(stack); if (!passes) continue; // Format the list of all positions found string positionsString = string.Join(", ", positions); // If we there is no version method, just return the protection name if (matcher.GetStreamVersion == null) { matchedProtections.Enqueue((matcher.ProtectionName ?? "Unknown Protection") + (includeDebug ? $" (Index {positionsString})" : string.Empty)); } // Otherwise, invoke the version method else { // A null version returned means the check didn't pass at the version step string version = matcher.GetStreamVersion(file, stack, positions); if (version == null) continue; matchedProtections.Enqueue($"{matcher.ProtectionName ?? "Unknown Protection"} {version}".Trim() + (includeDebug ? $" (Index {positionsString})" : string.Empty)); } // If we're stopping after the first protection, bail out here if (stopAfterFirst) return matchedProtections; } return matchedProtections; } #endregion #region Path Matching /// /// Get all path matches for a given list of matchers /// /// File path to check for matches /// Enumerable of PathMatchSets to be run on the file /// True if any path match is a success, false if all have to match /// List of strings representing the matched protections, null or empty otherwise public static ConcurrentQueue GetAllMatches(string file, IEnumerable matchers, bool any = false) { return FindAllMatches(new List { file }, matchers, any, false); } // /// Get all path matches for a given list of matchers /// /// File paths to check for matches /// Enumerable of PathMatchSets to be run on the file /// True if any path match is a success, false if all have to match /// List of strings representing the matched protections, null or empty otherwise public static ConcurrentQueue GetAllMatches(IEnumerable files, IEnumerable matchers, bool any = false) { return FindAllMatches(files, matchers, any, false); } /// /// Get first path match for a given list of matchers /// /// File path to check for matches /// Enumerable of PathMatchSets to be run on the file /// True if any path match is a success, false if all have to match /// String representing the matched protection, null otherwise public static string GetFirstMatch(string file, IEnumerable matchers, bool any = false) { var contentMatches = FindAllMatches(new List { file }, matchers, any, true); if (contentMatches == null || !contentMatches.Any()) return null; return contentMatches.First(); } /// /// Get first path match for a given list of matchers /// /// File paths to check for matches /// Enumerable of PathMatchSets to be run on the file /// True if any path match is a success, false if all have to match /// String representing the matched protection, null otherwise public static string GetFirstMatch(IEnumerable files, IEnumerable matchers, bool any = false) { var contentMatches = FindAllMatches(files, matchers, any, true); if (contentMatches == null || !contentMatches.Any()) return null; return contentMatches.First(); } /// /// Get the required set of path matches on a per Matcher basis /// /// File paths to check for matches /// Enumerable of PathMatchSets to be run on the file /// True if any path match is a success, false if all have to match /// True to stop after the first match, false otherwise /// List of strings representing the matched protections, null or empty otherwise private static ConcurrentQueue FindAllMatches(IEnumerable files, IEnumerable matchers, bool any, bool stopAfterFirst) { // If there's no mappings, we can't match if (matchers == null || !matchers.Any()) return new ConcurrentQueue(); // Initialize the list of matched protections var matchedProtections = new ConcurrentQueue(); // Loop through and try everything otherwise foreach (var matcher in matchers) { // Determine if the matcher passes bool passes; string firstMatchedString; if (any) { (bool anyPasses, string matchedString) = matcher.MatchesAny(files); passes = anyPasses; firstMatchedString = matchedString; } else { (bool allPasses, List matchedStrings) = matcher.MatchesAll(files); passes = allPasses; firstMatchedString = matchedStrings.FirstOrDefault(); } // If we don't have a pass, just continue if (!passes) continue; // If we there is no version method, just return the protection name if (matcher.GetVersion == null) { matchedProtections.Enqueue(matcher.ProtectionName ?? "Unknown Protection"); } // Otherwise, invoke the version method else { // A null version returned means the check didn't pass at the version step string version = matcher.GetVersion(firstMatchedString, files); if (version == null) continue; matchedProtections.Enqueue($"{matcher.ProtectionName ?? "Unknown Protection"} {version}".Trim()); } // If we're stopping after the first protection, bail out here if (stopAfterFirst) return matchedProtections; } return matchedProtections; } #endregion } }