mirror of
https://github.com/andreacristaldi/DefenderRuleParser
synced 2026-06-16 13:55:00 +00:00
260 lines
9.1 KiB
C#
260 lines
9.1 KiB
C#
// DefenderRuleParser
|
|
// Author: Andrea Cristaldi 2025 - https://github.com/andreacristaldi/DefenderRuleParser
|
|
// This project is licensed under the Apache 2.0 License.
|
|
/*
|
|
* Summary: PESTATIC parser — base static-hash list handler.
|
|
* Origin: dump-driven; recognizes canonical stride and captures raw hex.
|
|
* Role: Normalizes to uppercase hex for downstream matching.
|
|
*/
|
|
using DefenderRuleParser2.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace DefenderRuleParser2.Parsers
|
|
{
|
|
|
|
public sealed class PestaticParser : ISignatureParser
|
|
{
|
|
private static readonly int[] CandidateStrides = { 16, 12, 20, 24 };
|
|
private static readonly int[] TryHeaderSizes = { 4, 2, 0 };
|
|
private static readonly int[] AcceptedTailPads = { 0, 4, 8 };
|
|
|
|
|
|
private const int MaxRecords = 1_000_000;
|
|
|
|
public void Parse(BinaryReader reader, int size, uint threatId)
|
|
{
|
|
long offset = reader.BaseStream.Position;
|
|
var patterns = new List<string>();
|
|
|
|
try
|
|
{
|
|
byte[] buffer = reader.ReadBytes(size);
|
|
if (buffer == null || buffer.Length == 0)
|
|
{
|
|
Logger.Warn($"[PESTATIC] Empty payload @0x{offset:X}");
|
|
return;
|
|
}
|
|
|
|
|
|
foreach (int hdr in TryHeaderSizes)
|
|
{
|
|
foreach (int stride in CandidateStrides)
|
|
{
|
|
if (TryParseGrid(buffer, hdr, stride, out var entries, out var usedLen))
|
|
{
|
|
Logger.Info($"[PESTATIC] stride={stride}, header={hdr}, count={entries.Count}, used={usedLen} bytes");
|
|
|
|
|
|
for (int i = 0; i < entries.Count; i++)
|
|
{
|
|
byte[] chunk = entries[i];
|
|
patterns.Add(ToHexSpaced(chunk, 0, chunk.Length));
|
|
LogEntryHeuristics(i, chunk, stride);
|
|
}
|
|
|
|
Emit(threatId, offset, patterns);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (buffer.Length % 16 != 0)
|
|
Logger.Warn($"[PESTATIC] ! Misaligned data length: {buffer.Length} byte(s) (no grid match)");
|
|
|
|
string hex = ToHexSpaced(buffer, 0, buffer.Length);
|
|
patterns.Add(hex);
|
|
|
|
Logger.Info($"[PESTATIC] Threat ID: {threatId}, Entries: 1 (raw blob)");
|
|
Logger.HexDump($"[PESTATIC] RAW @0x{offset:X}", buffer, offset);
|
|
|
|
Emit(threatId, offset, patterns);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Error($"[PESTATIC] Error parsing at 0x{offset:X}: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
reader.BaseStream.Seek(offset + size, SeekOrigin.Begin);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static bool TryParseGrid(byte[] b, int headerSize, int stride, out List<byte[]> entries, out int usedLength)
|
|
{
|
|
entries = null;
|
|
usedLength = 0;
|
|
|
|
if (!(headerSize == 0 || headerSize == 2 || headerSize == 4)) return false;
|
|
if (stride <= 0) return false;
|
|
if (b == null || b.Length < headerSize + stride) return false;
|
|
if (b.Length < headerSize) return false;
|
|
|
|
int gridStart = headerSize;
|
|
int count;
|
|
|
|
if (headerSize == 2)
|
|
{
|
|
if (b.Length < 2) return false;
|
|
count = (b[0] | (b[1] << 8));
|
|
if (count <= 0 || count > MaxRecords) return false;
|
|
|
|
foreach (var pad in AcceptedTailPads)
|
|
{
|
|
if (gridStart + count * stride + pad == b.Length)
|
|
{
|
|
usedLength = gridStart + count * stride;
|
|
return ExtractChunks(b, gridStart, stride, count, out entries);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
else if (headerSize == 4)
|
|
{
|
|
if (b.Length < 4) return false;
|
|
count = (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24));
|
|
if (count <= 0 || count > MaxRecords) return false;
|
|
|
|
foreach (var pad in AcceptedTailPads)
|
|
{
|
|
if (gridStart + count * stride + pad == b.Length)
|
|
{
|
|
usedLength = gridStart + count * stride;
|
|
return ExtractChunks(b, gridStart, stride, count, out entries);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
int len = b.Length - gridStart;
|
|
if (len < stride) return false;
|
|
|
|
if (len % stride == 0)
|
|
{
|
|
count = len / stride;
|
|
if (count <= 0 || count > MaxRecords) return false;
|
|
usedLength = gridStart + count * stride;
|
|
return ExtractChunks(b, gridStart, stride, count, out entries);
|
|
}
|
|
|
|
foreach (var pad in AcceptedTailPads)
|
|
{
|
|
int dataLen = len - pad;
|
|
if (dataLen > 0 && dataLen % stride == 0)
|
|
{
|
|
count = dataLen / stride;
|
|
if (count <= 0 || count > MaxRecords) return false;
|
|
usedLength = gridStart + count * stride;
|
|
return ExtractChunks(b, gridStart, stride, count, out entries);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static bool ExtractChunks(byte[] b, int start, int stride, int count, out List<byte[]> list)
|
|
{
|
|
list = new List<byte[]>(count);
|
|
int p = start;
|
|
|
|
for (int i = 0; i < count; i++, p += stride)
|
|
{
|
|
if (p + stride > b.Length) return false;
|
|
var chunk = new byte[stride];
|
|
Buffer.BlockCopy(b, p, chunk, 0, stride);
|
|
list.Add(chunk);
|
|
}
|
|
return list.Count > 0;
|
|
}
|
|
|
|
|
|
|
|
private static void LogEntryHeuristics(int idx, byte[] chunk, int stride)
|
|
{
|
|
|
|
string hex = ToHexSpaced(chunk, 0, chunk.Length);
|
|
|
|
|
|
string dwords = null;
|
|
if (chunk.Length >= 16)
|
|
{
|
|
uint d0 = ReadU32LE(chunk, 0);
|
|
uint d1 = ReadU32LE(chunk, 4);
|
|
uint d2 = ReadU32LE(chunk, 8);
|
|
uint d3 = ReadU32LE(chunk, 12);
|
|
dwords = string.Format("d0=0x{0:X8} d1=0x{1:X8} d2=0x{2:X8} d3=0x{3:X8}", d0, d1, d2, d3);
|
|
}
|
|
|
|
|
|
var hints = new List<string>(3);
|
|
if (chunk.Length >= 4)
|
|
{
|
|
uint x0 = ReadU32LE(chunk, 0);
|
|
if (x0 == 0 || x0 == 0xFFFFFFFFu) hints.Add("crc-ish degenerate");
|
|
}
|
|
if (chunk.Length >= 8)
|
|
{
|
|
uint x1 = ReadU32LE(chunk, 4);
|
|
if (x1 < 0x01000000) hints.Add("index-ish small");
|
|
}
|
|
if (chunk.Length >= 12)
|
|
{
|
|
uint x2 = ReadU32LE(chunk, 8);
|
|
if (x2 > 0 && x2 < (256 * 1024 * 1024)) hints.Add("size-ish");
|
|
}
|
|
|
|
var sb = new StringBuilder();
|
|
sb.AppendFormat(" [{0,3}] {1}", idx + 1, hex);
|
|
if (!string.IsNullOrEmpty(dwords)) sb.Append(" | ").Append(dwords);
|
|
if (hints.Count > 0) sb.Append(" | ").Append(string.Join(", ", hints.ToArray()));
|
|
Logger.Info(sb.ToString());
|
|
}
|
|
|
|
private static void Emit(uint threatId, long offset, List<string> patterns)
|
|
{
|
|
Logger.Info($"[PESTATIC] Threat ID: {threatId}, Entries: {patterns.Count}");
|
|
if (ThreatDatabase.TryGetThreat(threatId, out var threat))
|
|
{
|
|
threat.Signatures.Add(new SignatureEntry
|
|
{
|
|
Type = "SIGNATURE_TYPE_PESTATIC",
|
|
Offset = offset,
|
|
Pattern = patterns,
|
|
Parsed = true,
|
|
ConditionType = "PRESENT",
|
|
ConditionValue = 1
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static uint ReadU32LE(byte[] b, int ofs)
|
|
{
|
|
if (ofs + 3 >= b.Length) return 0;
|
|
return (uint)(b[ofs] | (b[ofs + 1] << 8) | (b[ofs + 2] << 16) | (b[ofs + 3] << 24));
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|