fix: 六引擎误报根因修复 — 解包递归、YARA 恒真规则、签名分级、real_type 溢出

检测范围与证据链修复,不改裁决口径(口径改动在下一条提交)。

- extractor:zip/tar 成员在超限分支外漏加回扫描队列,解包后内容永远不参与检测;
  zip 目录条目被当成内嵌对象;tar 超限成员先落盘不删。递归真正生效后深扫耗时可测。
- clamav:_HIGH/_MEDIUM/_UNWANTED 关键词是小写,而签名名是 "Win.Trojan..." 大小写混排,
  分级判定恒不命中全走默认档;改统一大写匹配。超过 clamd MaxFileSize 的裸奔区间
  (1G~1.5G) 新增 ClamAVSkippedSizeLimit(30) — 按"不可扫描不放行"进复核。
- yara:pe.yar C1 系列改用导入表判定,不再靠裸子串命中字符串表;
  linux.yar C7 多条规则条件恒真(裸 nc/fetch 之类)已收紧;C8/C3 相应收窄。
- structure:DangerousExecutableExtension 是载体信号而非恶意行为,critical 权重 45→35,
  不再作为把中低置信度信号一起顶过 block_score 的底座。
- scanner/documents:real_type 拼进 libmagic 对 MSI/OLE 的元数据串实测 334+ 字符,
  超列宽导致整条 scan_tasks INSERT 连 findings 一起丢(被拒文件不留痕)→
  列宽 256→512,migrate.sql 补 ALTER。
This commit is contained in:
2026-11-02 20:26:56 +08:00
parent 64b682319e
commit b6a004bdb7
13 changed files with 636 additions and 311 deletions

View File

@@ -56,6 +56,19 @@ EML_EXTS = {".eml", ".msg"}
# 带 VBA 宏的 Office 扩展名(用于 VBA 纯文本扫描触发)
VBA_MACRO_OFFICE_EXTS = OLE_EXTS | OOXML_EXTS | {".bas", ".vba", ".cls", ".frm"}
# OOXML 包内的内嵌 OLE 部件目录
EMBED_PREFIX_RE = re.compile(r"^(?:word|xl|ppt)/embeddings/.+")
# 内嵌对象中的办公载体:文档互嵌(子表格/图表/PDF)是正常用法,不等于藏可执行
OFFICE_PART_EXTS = {
".docx", ".docm", ".xlsx", ".xlsm", ".pptx", ".pptm",
".doc", ".xls", ".ppt", ".pdf", ".rtf", ".xml", ".txt", ".csv",
".htm", ".html", ".wmf", ".emf", ".bin",
}
# 内嵌部件里直接判定为可执行对象的扩展名
EXEC_PART_EXTS = {".exe", ".dll", ".scr", ".js", ".jse", ".vbs", ".vbe", ".bat", ".cmd", ".ps1", ".hta", ".com", ".msi"}
# VBA 自动执行入口关键词
VBA_AUTOEXEC_PATTERNS = [
r"Sub\s+AutoOpen\s*\(", r"Sub\s+AutoExec\s*\(", r"Sub\s+AutoNew\s*\(",
@@ -211,14 +224,16 @@ class DocumentEngine(Engine):
else:
parser.close()
except Exception as exc: # noqa: BLE001
# 宏流解析不了 = 内容不可判定。这类畸形 OLE 常被用来探测解析器异常,
# 给 0 分等于"看不懂就放行",落人工复核。
findings.append(
Finding(
engine=self.name,
layer=self.layer,
severity=Severity.LOW.value,
severity=Severity.MEDIUM.value,
rule="OleToolsUnavailable",
detail=f"VBA 宏解析失败:{exc}",
weight=0.0,
weight=30.0,
)
)
@@ -407,6 +422,24 @@ class DocumentEngine(Engine):
"excerpt": excerpt,
}
def _looks_like_executable(self, zf: Any, name: str) -> bool:
"""判断内嵌部件是否为可执行载荷(扩展名 + 文件头魔数)。
oleObject1.bin 之类的外壳里可能装着真正的 PE,光看扩展名会漏;
反之把每个 .bin 都判成可执行又会拦掉正常的图表/子文档。
"""
if os.path.splitext(name)[1].lower() in EXEC_PART_EXTS:
return True
try:
with zf.open(name) as fp:
head = fp.read(1 << 20)
except Exception: # noqa: BLE001
return False
if head[:2] == b"MZ" or head[:4] in (b"\x7fELF", b"\xca\xfe\xba\xbe"):
return True
# OLE 复合容器(Package/Ole10Native)内嵌 PE:找 MZ..PE 头
return bool(re.search(rb"MZ[\x00-\xff]{0,64}PE\x00\x00", head))
def _scan_ooxml(self, target: ScanTarget) -> List[Finding]:
"""检测 OOXML(docx/xlsx/pptx)中的外部引用与外部链接。"""
findings: List[Finding] = []
@@ -429,9 +462,11 @@ class DocumentEngine(Engine):
weight=45.0,
)
)
if name.startswith("word/embeddings/") or name.startswith("xl/embeddings/") or name.startswith("ppt/embeddings/"):
# 内嵌 OLE 部件。zip 目录条目(以 / 结尾、长度为 0)不是部件——
# PowerPoint 导出的包常带空的 ppt/embeddings/ 目录,按文件处理会凭空报内嵌对象。
if EMBED_PREFIX_RE.match(name) and not name.endswith("/"):
embedded_ext = os.path.splitext(name)[1].lower()
if embedded_ext in {".exe", ".dll", ".scr", ".js", ".vbs", ".bat", ".bin"}:
if self._looks_like_executable(zf, name):
findings.append(
Finding(
engine=self.name,
@@ -442,6 +477,17 @@ class DocumentEngine(Engine):
weight=75.0,
)
)
elif embedded_ext in OFFICE_PART_EXTS:
findings.append(
Finding(
engine=self.name,
layer=self.layer,
severity=Severity.LOW.value,
rule="EmbeddedObject",
detail=f"文档内嵌办公对象:{name}",
weight=5.0,
)
)
else:
findings.append(
Finding(