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