Files
Andrea Cristaldi 492ffc428b First commit
2025-08-28 17:14:35 +02:00

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();
}
}
}