// DefenderRuleParser // Author: Andrea Cristaldi 2025 - https://github.com/andreacristaldi/DefenderRuleParser // This project is licensed under the Apache 2.0 License. /* * Summary: Simple text extractor for diagnostic/aux strings present in blobs. * Origin: dump-driven; UTF-16LE/ASCII detection based on byte distribution. * Role: Feeds readable strings to logs and exporters. */ using System; using System.Collections.Generic; using System.IO; using System.Text; using DefenderRuleParser2.Models; using DefenderRuleParser2.Parsers.Wildcards; namespace DefenderRuleParser2.Parsers { public class GenericTextDumpParser : ISignatureParser { private readonly string _signatureType; public GenericTextDumpParser(string signatureType) { _signatureType = signatureType; } public void Parse(BinaryReader reader, int size, uint threatId) { long offset = reader.BaseStream.Position; try { byte[] buffer = reader.ReadBytes(size); if (buffer == null) buffer = new byte[0]; string hex = ToHexSpaced(buffer, 0, buffer.Length); bool isWild = LooksLikeWildcardPattern(buffer); string humanPrimary = null; if (isWild) { int consumed; bool hadTerm; var tokens = WildcardPattern.Tokenize(buffer, 0, buffer.Length, out consumed, out hadTerm); string human = WildcardPattern.RenderHuman(tokens, Encoding.ASCII); string yara = WildcardPattern.RenderYaraHex(tokens); var sbAsciiOnly = new StringBuilder(); for (int i = 0; i < tokens.Count; i++) { var lit = tokens[i] as TokLiteral; if (lit != null && lit.Bytes != null && lit.Bytes.Length > 0) sbAsciiOnly.Append(Encoding.ASCII.GetString(lit.Bytes)); } string asciiOnly = sbAsciiOnly.ToString(); Logger.Info(string.Format("[{0}] Threat ID: {1}, Size: {2} bytes (wildcarded)", _signatureType, threatId, size)); if (!string.IsNullOrEmpty(asciiOnly)) Logger.Info(" · ASCII-only : " + asciiOnly); if (!string.IsNullOrEmpty(human)) Logger.Info(" · WILD/Human : " + human); if (!string.IsNullOrEmpty(yara)) Logger.Info(" · WILD/YARA : " + yara); humanPrimary = !string.IsNullOrEmpty(human) ? human : asciiOnly; } else { string ascii = ExtractAsciiStrings(buffer, 4); string u16 = ExtractUtf16AsciiStrings(buffer, 4); var combined = new StringBuilder(); if (!string.IsNullOrEmpty(ascii)) { combined.AppendLine(ascii.TrimEnd()); } if (!string.IsNullOrEmpty(u16)) { if (combined.Length > 0) combined.AppendLine(); combined.AppendLine(u16.TrimEnd()); } humanPrimary = combined.ToString().Trim(); Logger.Info(string.Format("[{0}] Threat ID: {1}, Size: {2} bytes", _signatureType, threatId, size)); if (!string.IsNullOrEmpty(ascii)) { Logger.Info(" > ASCII strings:"); Logger.Info(ascii); } if (!string.IsNullOrEmpty(u16)) { Logger.Info(" > UTF-16LE strings:"); Logger.Info(u16); } } Logger.Info(" > HEX:"); Logger.Info(hex); if (ThreatDatabase.TryGetThreat(threatId, out var threat)) { threat.Signatures.Add(new SignatureEntry { Type = _signatureType, Offset = offset, Pattern = new List { humanPrimary ?? string.Empty, hex }, Parsed = true, ConditionType = "PRESENT", ConditionValue = 1 }); } } catch (Exception ex) { Logger.Error(string.Format("[!] {0} Error parsing at offset 0x{1:X}: {2}", _signatureType, offset, ex.Message)); } finally { reader.BaseStream.Seek(offset + size, SeekOrigin.Begin); } } private static bool LooksLikeWildcardPattern(byte[] data) { if (data == null || data.Length < 2) return false; for (int i = 0; i < data.Length - 1; i++) { if (data[i] != 0x90) continue; byte n = data[i + 1]; if (n == 0x00 || n == 0x90 || n < 0x32) return true; } return false; } private static string ExtractAsciiStrings(byte[] buffer, int minLen) { if (buffer == null || buffer.Length == 0) return string.Empty; var sb = new StringBuilder(); var tmp = new StringBuilder(); for (int i = 0; i < buffer.Length; i++) { byte b = buffer[i]; if (b >= 0x20 && b <= 0x7E) { tmp.Append((char)b); } else { if (tmp.Length >= minLen) sb.AppendLine(tmp.ToString()); tmp.Length = 0; } } if (tmp.Length >= minLen) sb.AppendLine(tmp.ToString()); return sb.ToString().TrimEnd(); } private static string ExtractUtf16AsciiStrings(byte[] buffer, int minLen) { if (buffer == null || buffer.Length < 2) return string.Empty; var sb = new StringBuilder(); var tmp = new StringBuilder(); int i = 0; while (i + 1 < buffer.Length) { byte lo = buffer[i]; byte hi = buffer[i + 1]; bool printablePair = (hi == 0x00) && (lo >= 0x20 && lo <= 0x7E); if (printablePair) { tmp.Append((char)lo); i += 2; } else { if (tmp.Length >= minLen) sb.AppendLine(tmp.ToString()); tmp.Length = 0; i += 2; } } if (tmp.Length >= minLen) sb.AppendLine(tmp.ToString()); return sb.ToString().TrimEnd(); } private static string ToHexSpaced(byte[] b, int ofs, int len) { if (b == null || len <= 0 || ofs < 0 || ofs + len > b.Length) return ""; var sb = new StringBuilder(len * 3); for (int i = 0; i < len; i++) { sb.Append(b[ofs + i].ToString("X2")); if (i + 1 < len) sb.Append(' '); } return sb.ToString(); } } }