mirror of
https://github.com/andreacristaldi/DefenderRuleParser
synced 2026-06-16 13:55:00 +00:00
224 lines
7.5 KiB
C#
224 lines
7.5 KiB
C#
// 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<string> { 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();
|
|
}
|
|
}
|
|
}
|
|
|