mirror of
https://github.com/andreacristaldi/DefenderRuleParser
synced 2026-06-16 13:55:00 +00:00
165 lines
6.7 KiB
C#
165 lines
6.7 KiB
C#
// DefenderRuleParser
|
|
// Author: Andrea Cristaldi 2025 - https://github.com/andreacristaldi/DefenderRuleParser
|
|
// This project is licensed under the Apache 2.0 License.
|
|
/*
|
|
* Summary: PESTATIC-EX parser — extended static-hash/signature sets.
|
|
* Origin: dump-driven; header/stride gleaned from repeated 22-byte patterns.
|
|
* Role: Emits fixed hashes and flags; notes padding and anomalies.
|
|
*/
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using DefenderRuleParser2.Models;
|
|
using DefenderRuleParser2.Parsers.Wildcards;
|
|
|
|
namespace DefenderRuleParser2.Parsers
|
|
{
|
|
|
|
public class PestaticExParser : ISignatureParser
|
|
{
|
|
public void Parse(BinaryReader reader, int size, uint threatId)
|
|
{
|
|
long offset = reader.BaseStream.Position;
|
|
var fragments = new List<string>();
|
|
var logic = new List<SubRuleLogic>();
|
|
|
|
try
|
|
{
|
|
byte[] buffer = reader.ReadBytes(size);
|
|
if (buffer == null || buffer.Length < 5)
|
|
{
|
|
Logger.Warn(string.Format("[PESTATICEX] Signature too small ({0}) @0x{1:X}", (buffer == null ? 0 : buffer.Length), offset));
|
|
return;
|
|
}
|
|
|
|
using (var ms = new MemoryStream(buffer))
|
|
using (var br = new BinaryReader(ms))
|
|
{
|
|
ushort threshold = br.ReadUInt16();
|
|
ushort subRuleCount = br.ReadUInt16();
|
|
byte control = br.ReadByte();
|
|
|
|
Logger.Info(string.Format("[PESTATICEX] Threat ID: {0}, Threshold: {1}, SubRules: {2}, Flags=0x{3:X2}",
|
|
threatId, threshold, subRuleCount, control));
|
|
|
|
for (int i = 0; i < subRuleCount && (br.BaseStream.Position + 3) <= br.BaseStream.Length; i++)
|
|
{
|
|
long subStart = br.BaseStream.Position;
|
|
|
|
ushort weight = br.ReadUInt16();
|
|
byte length = br.ReadByte();
|
|
|
|
if (br.BaseStream.Position + length > br.BaseStream.Length)
|
|
{
|
|
Logger.Warn(string.Format(" ! SubRule #{0} truncated (need {1}B).", i + 1, length));
|
|
break;
|
|
}
|
|
|
|
byte[] payload = br.ReadBytes(length);
|
|
|
|
|
|
bool isWild = LooksLikeWildcardPattern(payload);
|
|
|
|
string humanOut = null;
|
|
if (isWild)
|
|
{
|
|
int consumed;
|
|
bool hadTerm;
|
|
var tokens = WildcardPattern.Tokenize(payload, 0, payload.Length, out consumed, out hadTerm);
|
|
|
|
|
|
string human = WildcardPattern.RenderHuman(tokens, Encoding.ASCII);
|
|
string yara = WildcardPattern.RenderYaraHex(tokens);
|
|
|
|
var sbAscii = new StringBuilder();
|
|
for (int t = 0; t < tokens.Count; t++)
|
|
{
|
|
var lit = tokens[t] as TokLiteral;
|
|
if (lit != null && lit.Bytes != null && lit.Bytes.Length > 0)
|
|
sbAscii.Append(Encoding.ASCII.GetString(lit.Bytes));
|
|
}
|
|
string asciiOnly = sbAscii.ToString();
|
|
|
|
Logger.Info(string.Format(" > SubRule #{0}: Weight={1}, WildTokenized=True, Len={2}, Term={3}",
|
|
i + 1, weight, payload.Length, (hadTerm ? "yes" : "no")));
|
|
if (!string.IsNullOrEmpty(human)) Logger.Info(" · Human : " + human);
|
|
if (!string.IsNullOrEmpty(yara)) Logger.Info(" · YARA hex: " + yara);
|
|
if (!string.IsNullOrEmpty(asciiOnly)) Logger.Info(" · ASCII : " + asciiOnly);
|
|
|
|
humanOut = !string.IsNullOrEmpty(human) ? human : asciiOnly;
|
|
}
|
|
else
|
|
{
|
|
string text = DecodeBestEffort(payload);
|
|
Logger.Info(string.Format(" > SubRule #{0}: Weight={1}, Pattern=\"{2}\"", i + 1, weight, text));
|
|
humanOut = text;
|
|
}
|
|
|
|
fragments.Add(humanOut ?? string.Empty);
|
|
logic.Add(new SubRuleLogic
|
|
{
|
|
Pattern = humanOut ?? string.Empty,
|
|
Weight = weight,
|
|
Control = 0
|
|
});
|
|
}
|
|
|
|
if (ThreatDatabase.TryGetThreat(threatId, out var threat))
|
|
{
|
|
threat.Signatures.Add(new SignatureEntry
|
|
{
|
|
Type = "SIGNATURE_TYPE_PESTATICEX",
|
|
Offset = offset,
|
|
Pattern = fragments,
|
|
Parsed = true,
|
|
Logic = new SignatureLogic
|
|
{
|
|
Threshold = threshold,
|
|
SubRules = logic
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Error(string.Format("[!] PESTATICEX Error parsing at 0x{0:X}: {1}", 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 DecodeBestEffort(byte[] bytes)
|
|
{
|
|
if (bytes == null || bytes.Length == 0) return string.Empty;
|
|
|
|
int nullOdd = 0;
|
|
for (int i = 1; i < bytes.Length; i += 2)
|
|
if (bytes[i] == 0x00) nullOdd++;
|
|
|
|
bool utf16 = (bytes.Length >= 4) && (nullOdd >= bytes.Length / 4);
|
|
string s = utf16 ? Encoding.Unicode.GetString(bytes) : Encoding.UTF8.GetString(bytes);
|
|
return s.TrimEnd('\0');
|
|
}
|
|
}
|
|
}
|
|
|
|
|