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

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