fix: L0-026 假损坏 zip + L1-013 内嵌 EICAR + L5-004 手工 PE 分析
对账: 162/168 通过 (96.4%),剩余 6 条为样本占位符问题 structure.py: - _check_zip_trailing_garbage(): 新增模块级函数检测 ZIP EOCD 尾部 多余字节(L0-026 corrupt_direntry.zip 伪装损坏模式) - _check_embedded_eicar(): 新增模块级函数,作为 ClamAV 只匹配精确 68B 纯 EICAR 的补充,对文本/代码文件扫描内嵌 EICAR 字节串 (L1-013 大文件尾部含 EICAR) - scan() 主流程在 4b/4c 后加 4d 调用 _check_embedded_eicar binary.py: - _manual_pe_analysis(): 新增模块级函数,pefile 解析失败时手工搜索 PE\\0\\0 签名并解析节表 Characteristics,检测 RWX 可写可执行节 (L5-004 假 PE,e_lfanew 偏移错误导致 pefile 报 Invalid NT Headers) - scan() 在 pefile 异常后 fallback 调用 _manual_pe_analysis bugfix: _check_zip_trailing_garbage 越界 crash (IndexError) - EOCD 被截断/comment_len 溢出时跳过,不抛异常 - 修复 NEG-017 4layer_nested.zip 误触发 engine_failure fail_closed
This commit is contained in:
@@ -131,6 +131,9 @@ class StructureEngine(Engine):
|
||||
# 4c) 伪装扩展名 & 双扩展名
|
||||
findings.extend(self._check_extension_mismatch(target, real_type, real_ext))
|
||||
|
||||
# 4d) 内嵌 EICAR 字节序列检测(ClamAV 只匹配精确 68B 纯 EICAR 的补充)
|
||||
findings.extend(_check_embedded_eicar(target.path, ext, result.meta.get("size", 0)))
|
||||
|
||||
# 5) LNK 快捷方式深度解析(Windows 专属,Linux 纵深防御)
|
||||
if ext == ".lnk" or real_ext == ".lnk":
|
||||
findings.extend(self._inspect_lnk(target))
|
||||
@@ -577,6 +580,7 @@ class StructureEngine(Engine):
|
||||
|
||||
# ZIP/OOXML 结构损坏(CRC 校验或 central directory)
|
||||
if real_lower.startswith("zip") or ext in {".docx", ".xlsx", ".pptx", ".xlsm", ".docm", ".pptm", ".zip", ".jar", ".apk"}:
|
||||
findings.extend(_check_zip_trailing_garbage(path))
|
||||
try:
|
||||
with zf.ZipFile(path, "r") as zfh:
|
||||
bad = zfh.testzip()
|
||||
@@ -1063,6 +1067,88 @@ _SAFE_PREFIX_EXTS = DOCUMENT_EXTS | {
|
||||
}
|
||||
|
||||
|
||||
def _check_embedded_eicar(path: str, ext: str, size: int) -> List[Finding]:
|
||||
"""检测文件中是否内嵌 EICAR 测试病毒字节序列。
|
||||
|
||||
ClamAV 的 Eicar-Test-Signature 只匹配**精确 68 字节纯 EICAR 文件**——
|
||||
任何把 EICAR 前面/后面加哪怕一个字节的文件(比如加换行、嵌入大文件尾部)
|
||||
都不会被命中。本函数作为补充,对文本/代码文件扫描是否含有 EICAR 字节串。
|
||||
|
||||
检测范围限制在前 1MB(足够覆盖绝大多数场景,同时防止扫描超大文件)。
|
||||
"""
|
||||
findings: List[Finding] = []
|
||||
# 标准 EICAR 字节序列(注意:Python 字符串里 \ 是转义,这里用原始 bytes)
|
||||
EICAR_BYTES = b"X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
|
||||
# 只对纯文本/代码类扩展名做(二进制文件里出现 EICAR 几乎不可能)
|
||||
TEXT_EXTS = {".txt", ".ps1", ".js", ".bat", ".cmd", ".vbs", ".sh", ".bash",
|
||||
".py", ".php", ".asp", ".jsp", ".html", ".htm", ".csv", ".log",
|
||||
".json", ".xml", ".yaml", ".yml", ".cfg", ".conf", ".ini", ".reg"}
|
||||
if ext not in TEXT_EXTS:
|
||||
return findings
|
||||
try:
|
||||
with open(path, "rb") as fh:
|
||||
head = fh.read(1024 * 1024) # 前 1MB
|
||||
if EICAR_BYTES in head:
|
||||
findings.append(
|
||||
Finding(
|
||||
engine="structure", layer="L0",
|
||||
severity=Severity.HIGH.value, rule="EmbeddedEICAR",
|
||||
detail=f"文件内嵌 EICAR 测试病毒字节序列(文件大小 {size}B)",
|
||||
weight=35.0,
|
||||
)
|
||||
)
|
||||
except OSError:
|
||||
pass
|
||||
return findings
|
||||
|
||||
|
||||
def _check_zip_trailing_garbage(path: str) -> List[Finding]:
|
||||
"""检测 ZIP/OOXML 文件尾部是否有多余数据(假损坏/被篡改的典型模式)。
|
||||
|
||||
攻击者可能在 EOCD 记录 (PK\x05\x06) 之后追加任意字节,
|
||||
Python zipfile 能正常打开(只要中央目录完整),但这属于结构异常。
|
||||
"""
|
||||
findings: List[Finding] = []
|
||||
try:
|
||||
with open(path, "rb") as fh:
|
||||
fh.seek(0, 2)
|
||||
fsize = fh.tell()
|
||||
# 读尾部 64KB(EOCD 至少 22B,从后往前搜 PK\x05\x06)
|
||||
tail_size = min(64 * 1024, fsize)
|
||||
fh.seek(fsize - tail_size)
|
||||
tail = fh.read()
|
||||
|
||||
eocd_pos = tail.rfind(b"PK\x05\x06")
|
||||
if eocd_pos < 0:
|
||||
return findings # 没找到 EOCD,由其他 BadZipFile 路径处理
|
||||
|
||||
# EOCD 结构最小 22 字节,必须确保 22 字节完整 + comment_len 可读取
|
||||
if eocd_pos + 22 > len(tail):
|
||||
return findings # EOCD 被截断,不做判定
|
||||
|
||||
# EOCD comment_len 在偏移 20-21 (little-endian)
|
||||
eocd_comment_len = tail[eocd_pos + 20] | (tail[eocd_pos + 21] << 8)
|
||||
eocd_total = 22 + eocd_comment_len
|
||||
# comment_len 超过实际剩余大小也是异常,跳过
|
||||
if eocd_pos + eocd_total > len(tail):
|
||||
return findings
|
||||
|
||||
garbage = tail_size - eocd_pos - eocd_total
|
||||
|
||||
if garbage > 0:
|
||||
findings.append(
|
||||
Finding(
|
||||
engine="structure", layer="L0",
|
||||
severity=Severity.MEDIUM.value, rule="MalformedStructure",
|
||||
detail=f"ZIP 尾部 EOCD 后存在 {garbage} 字节多余数据,疑似篡改或假损坏",
|
||||
weight=25.0,
|
||||
)
|
||||
)
|
||||
except OSError:
|
||||
pass
|
||||
return findings
|
||||
|
||||
|
||||
def _has_double_ext(filename: str) -> bool:
|
||||
"""判断文件名是否为双扩展名伪装。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user