mirror of
https://github.com/andreacristaldi/DefenderRuleParser
synced 2026-06-16 13:55:00 +00:00
220 lines
7.7 KiB
C#
220 lines
7.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using DefenderRuleParser2.Models;
|
|
/*
|
|
* Summary: Static hash aggregator across different fixed-size formats.
|
|
* Origin: dump-driven; supports multiple strides/pad behaviors observed in dumps.
|
|
* Role: Centralizes hash extraction and de-duplication.
|
|
*/
|
|
namespace DefenderRuleParser2.Parsers
|
|
{
|
|
|
|
public class StaticHashParser : ISignatureParser
|
|
{
|
|
private const int HashSize = 16;
|
|
private static readonly int[] CandidateStrides = { 22, 20, 24, 28 };
|
|
private static readonly int[] CandidateHeaders = { 0, 2, 4 };
|
|
private const int MaxTailPadding = 8;
|
|
private const int MaxRecords = 1_000_000;
|
|
|
|
public void Parse(BinaryReader reader, int size, uint threatId)
|
|
{
|
|
long offset = reader.BaseStream.Position;
|
|
byte[] buf = reader.ReadBytes(size);
|
|
|
|
try
|
|
{
|
|
List<string> hashes;
|
|
int chosenStride, chosenHeader, chosenCount;
|
|
|
|
if (TryParseStatic(buf, out hashes, out chosenStride, out chosenHeader, out chosenCount))
|
|
{
|
|
Logger.Info(string.Format(
|
|
"[STATIC_HASH] Threat ID: {0}, stride={1}, header={2}, count={3}",
|
|
threatId, chosenStride, chosenHeader, chosenCount));
|
|
|
|
for (int i = 0; i < hashes.Count; i++)
|
|
Logger.Info(string.Format(" [{0,3}] {1}", i + 1, hashes[i]));
|
|
|
|
Threat t;
|
|
if (ThreatDatabase.TryGetThreat(threatId, out t))
|
|
{
|
|
t.Signatures.Add(new SignatureEntry
|
|
{
|
|
Type = "SIGNATURE_TYPE_STATIC",
|
|
Offset = offset,
|
|
Pattern = hashes,
|
|
Parsed = true,
|
|
ConditionType = "MIN_MATCHES",
|
|
ConditionValue = 1
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
Logger.Info("[STATIC_HASH] No plausible grid; falling back to raw blob.");
|
|
Logger.HexDump(string.Format("[STATIC_HASH] RAW @0x{0:X}", offset), buf, offset);
|
|
|
|
Threat thr;
|
|
if (ThreatDatabase.TryGetThreat(threatId, out thr))
|
|
{
|
|
thr.Signatures.Add(new SignatureEntry
|
|
{
|
|
Type = "SIGNATURE_TYPE_STATIC",
|
|
Offset = offset,
|
|
Parsed = true,
|
|
ConditionType = "BLOB",
|
|
ConditionValue = (buf == null ? 0 : buf.Length),
|
|
Pattern = new List<string> { ToHex(buf, 0, (buf == null ? 0 : buf.Length)) }
|
|
});
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Error(string.Format("[!] STATIC_HASH Error parsing at offset 0x{0:X}: {1}", offset, ex.Message));
|
|
}
|
|
finally
|
|
{
|
|
reader.BaseStream.Seek(offset + size, SeekOrigin.Begin);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static bool TryParseStatic(byte[] b, out List<string> hashes, out int outStride, out int outHeaderSize, out int outCount)
|
|
{
|
|
hashes = null; outStride = 0; outHeaderSize = 0; outCount = -1;
|
|
if (b == null || b.Length < HashSize) return false;
|
|
|
|
foreach (int header in CandidateHeaders)
|
|
{
|
|
foreach (int stride in CandidateStrides)
|
|
{
|
|
if (TryParseWith(b, header, stride, out hashes, out outCount))
|
|
{
|
|
if (LooksPlausibleHashList(hashes))
|
|
{
|
|
outStride = stride;
|
|
outHeaderSize = header;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
private static bool TryParseWith(byte[] b, int headerSize, int stride, out List<string> hashes, out int count)
|
|
{
|
|
hashes = null; count = -1;
|
|
|
|
if (!(headerSize == 0 || headerSize == 2 || headerSize == 4)) return false;
|
|
if (stride <= 0 || stride < HashSize) return false;
|
|
if (b.Length < headerSize) return false;
|
|
|
|
int offset = headerSize;
|
|
int len = b.Length - offset;
|
|
|
|
|
|
if (headerSize == 2)
|
|
{
|
|
if (b.Length < 2) return false;
|
|
count = b[0] | (b[1] << 8);
|
|
if (count <= 0 || count > MaxRecords) return false;
|
|
int used = count * stride;
|
|
int pad = len - used;
|
|
if (pad < 0 || pad > MaxTailPadding) 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;
|
|
int used = count * stride;
|
|
int pad = len - used;
|
|
if (pad < 0 || pad > MaxTailPadding) return false;
|
|
}
|
|
else
|
|
{
|
|
|
|
if (len < stride) return false;
|
|
int usable = (len / stride) * stride;
|
|
int pad = len - usable;
|
|
if (pad > MaxTailPadding) return false;
|
|
count = usable / stride;
|
|
}
|
|
|
|
if (count <= 0) return false;
|
|
|
|
var outList = new List<string>(count);
|
|
int pos = offset;
|
|
|
|
for (int i = 0; i < count; i++, pos += stride)
|
|
{
|
|
if (pos + stride > b.Length) return false;
|
|
|
|
|
|
outList.Add(ToHex(b, pos + 0, HashSize));
|
|
|
|
|
|
if (stride == 22 && b[pos + 15] == 0x00)
|
|
{
|
|
string h15 = ToHex(b, pos + 0, 15);
|
|
Logger.Info(string.Format(" · note: 15+pad(00) detected; H15={0}", h15));
|
|
}
|
|
}
|
|
|
|
hashes = outList;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
private static bool LooksPlausibleHashList(List<string> hashes)
|
|
{
|
|
if (hashes == null || hashes.Count == 0) return false;
|
|
|
|
string zero = new string('0', HashSize * 2);
|
|
string ffff = new string('F', HashSize * 2);
|
|
|
|
var set = new HashSet<string>(StringComparer.Ordinal);
|
|
bool allZero = true, allFF = true;
|
|
|
|
for (int i = 0; i < hashes.Count; i++)
|
|
{
|
|
string h = hashes[i];
|
|
set.Add(h);
|
|
if (!string.Equals(h, zero, StringComparison.Ordinal)) allZero = false;
|
|
if (!string.Equals(h, ffff, StringComparison.Ordinal)) allFF = false;
|
|
}
|
|
|
|
if (allZero || allFF) return false;
|
|
|
|
|
|
int needUnique = Math.Min(3, hashes.Count);
|
|
if (set.Count < needUnique) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
private static string ToHex(byte[] b, int ofs, int len)
|
|
{
|
|
if (b == null || len <= 0 || ofs < 0 || ofs + len > b.Length) return string.Empty;
|
|
char[] c = new char[len * 2];
|
|
int k = 0;
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
byte v = b[ofs + i];
|
|
c[k++] = (char)((v >> 4) < 10 ? '0' + (v >> 4) : 'A' + ((v >> 4) - 10));
|
|
c[k++] = (char)((v & 0x0F) < 10 ? '0' + (v & 0x0F) : 'A' + ((v & 0x0F) - 10));
|
|
}
|
|
return new string(c);
|
|
}
|
|
}
|
|
}
|