概述
邮件系统中最常见的攻击载体之一是恶意附件。攻击者通过伪装成发票、文档或图片的可执行文件、包含宏的 Office 文档、或嵌套在压缩包中的恶意载荷来绕过传统防护。attachment_scanner 是我们在 Rust 邮件核心引擎中实现的附件安全扫描模块(Phase 10.11),提供从浅层扩展名检查到深层内容分析的多层防御能力。
本模块共 958 行 Rust 代码,核心功能包括:文件类型魔数检测(识别9种文件类型)、危险扩展名检测(24种高风险扩展名)、双扩展名欺诈检测、Office VBA 宏检测(同时支持 OLE2 和 OOXML)、ZIP 归档递归扫描(纯内存 ZIP 解析)、ZIP 炸弹三重防护(文件数/解压大小/压缩比限制)、Shannon 熵分析(加壳/加密检测)、恶意模式签名检测(PE 可疑 API + 打包器 + PDF 可执行)。在 FFI 层,我们通过 security_ffi.rs 暴露了 6 个 C ABI 函数。
威胁等级模型
附件扫描器使用 5 级威胁等级,从低到高依次为 Safe、Low、Medium、High、Critical。等级之间支持 PartialOrd 比较,当同一文件触发多个检测规则时,自动取最高威胁等级。例如,一个伪装成 PDF 的 PE 文件既触发了双扩展名检测(High),又因为高熵(entropy > 7.5)被升级为 Critical。
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum ThreatLevel {
#[default]
Safe = 0,
Low = 1,
Medium = 2,
High = 3,
Critical = 4,
}
扫描流程设计
附件扫描器的核心入口是 scan_buffer 和 scan_file 两个方法,其中 deep_scan 标志位控制是否启用深度扫描。scan_buffer 的扫描流程按优先级排列:
- 危险扩展名检测 — 如果文件名以 .exe/.scr/.pif 等 24 种高风险扩展名结尾,立即返回 High 级威胁
- VBA 宏检测 — 扫描 OLE2 和 OOXML 格式的 Office 文档,查找 VBA 宏标记
- 双扩展名检测 — 识别文件名中包含危险扩展名的双扩展名欺诈
- 归档递归扫描 — 如果是 ZIP 文件且启用 deep_scan,解析 ZIP 内部结构检查嵌套威胁
- 恶意模式签名检测 — 在 PE 文件中搜索可疑 API 调用、打包器签名,在 PDF 文件中搜索 /JavaScript、/Launch、/OpenAction 等危险动作
- Shannon 熵分析 — 对 256 字节以上的文件计算熵值,高熵 + 危险扩展名 = Critical
扫描结果通过 ScanResult 结构体返回,包含 is_threat、threat_level、description、details 和 recommended_action(allow/warn/block)字段。
文件类型魔数检测
文件类型检测基于魔数(Magic Number)而非文件扩展名。detect_real_type 函数支持 9 种文件类型的识别:
fn detect_real_type(data: &[u8]) -> Option<&'static str> {
// PE: MZ magic 0x4D 0x5A
if data[0] == 0x4D && data[1] == 0x5A { return Some("application/x-dosexec"); }
// ELF: 0x7F E L F
if data[0] == 0x7F && data[1] == 0x45 && data[2] == 0x4C && data[3] == 0x46 { return Some("application/x-executable"); }
// PDF: %PDF
if data[0] == 0x25 && data[1] == 0x50 && data[2] == 0x44 && data[3] == 0x46 { return Some("application/pdf"); }
// ZIP: PK 0x03 0x04
if data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04 { return Some("application/zip"); }
// RAR, 7-Zip, PNG, JPEG, GIF ...
None
}
基于魔数的检测使得我们可以实现 is_type_mismatch 函数,识别文件声明类型与实际内容不一致的攻击:声称是 PDF 但实际是 PE、声称是图片但实际是可执行文件、声称是文档但实际是压缩包。
双扩展名欺诈检测
Windows 系统默认隐藏已知文件扩展名,攻击者利用这一点构造如 document.pdf.exe 的文件名。在用户看来,这只显示为 document.pdf。has_double_extension 函数通过检测文件名是否包含多个扩展名,且最后一个扩展名为危险类型来识别此类欺诈。该检测不适用于最后两个扩展名都是危险类型的情况(如 script.exe.bat),因为这通常表示文件名被恶意截断而非用户欺骗。
Office VBA 宏检测
Office 文档中的 VBA 宏是恶意代码传播的常见载体。has_vba_macros 函数支持两种 Office 格式:
- OLE2 格式(旧版 Office:.doc/.xls/.ppt):魔数为 0xD0CF 0x11E0 0xA1B1 0x11AE,检测内容包括 "VBA"、"Macros"、"ThisDocument" 等关键字
- OOXML 格式(新版 Office:.docx/.xlsx/.pptx):实际上是 ZIP 格式,检测是否包含 vbaProject.bin 文件路径
ZIP 归档递归扫描
scan_archive_buffer 函数实现了纯内存的 ZIP 文件解析,无需将文件写入磁盘即可扫描归档内部。这是本模块最具深度的功能之一。
ZIP 文件由多个本地文件头(Local File Header)组成,每个本地文件头固定 30 字节,后跟文件名、额外字段和压缩数据。扫描器解析每个本地文件头,对每个条目执行以下检查:
- 文件数量限制:超过 100 个条目视为 ZIP bomb
- 解压大小累加:超过 50MB 视为 ZIP bomb
- 压缩比检测:单个条目压缩比 > 100:1 视为 ZIP bomb
- 危险扩展名检测:归档内文件名包含 .exe/.js 等
- 双扩展名检测:归档内文件名包含双扩展名
- 存储模式条目熵分析:对未压缩的条目计算 Shannon 熵
ZIP 炸弹防护
ZIP 炸弹是一种拒绝服务攻击,利用极小的压缩比生成巨大的解压数据。经典的 42.zip 仅 42KB 解压后可达 4.5PB。附件扫描器通过三重限制防护 ZIP bomb:
const MAX_ENTRIES: usize = 100; // 最多 100 个文件条目
const MAX_UNCOMPRESSED_SIZE: usize = 50 * 1024 * 1024; // 最多 50MB
const MAX_COMPRESSION_RATIO: usize = 100; // 最大 100:1 压缩比
当检测到 ZIP bomb 时,扫描器立即返回 High 级威胁并标记为 block。安全检查还包括文件名长度限制(> 1024 字节视为异常)、offset 合理性检查、异常增长检测。
Shannon 熵分析
Shannon 熵衡量数据的随机性,范围 0.0-8.0。正常情况下:纯文本 3.5-5.0、可执行文件 5.0-6.5、加壳可执行文件 > 7.0、加密内容接近 8.0。
判断规则:
- 熵 > 7.5 + 危险扩展名 = Critical(极可能是加壳恶意软件)
- 熵 > 7.0 + 已有威胁判定 = 升级为 High
pub fn calculate_entropy(data: &[u8]) -> f64 {
if data.is_empty() { return 0.0; }
let mut freq = [0u64; 256];
let len = data.len() as f64;
for &byte in data { freq[byte as usize] += 1; }
let mut entropy: f64 = 0.0;
for &count in &freq {
if count > 0 {
let p = count as f64 / len;
entropy -= p * p.log2();
}
}
entropy
}
恶意模式签名检测
check_malware_patterns 函数在两个维度进行恶意模式检测:
PE 文件检测(可执行文件):
- 可疑 API:WinExec、ShellExecute、URLDownloadToFile、VirtualAlloc、CreateRemoteThread、WriteProcessMemory、NtUnmapViewOfSection、VirtualAllocEx、NtCreateThreadEx
- 打包器签名:UPX(UPX0/UPX1)、ASPack(.aspack)、Nullsoft(.ndata)、PECompact(PECompact2)、EPack(!EPack)
- 脚本宿主引用:WScript.Shell、WScript.Network
PDF 文件检测:
- /Launch — PDF 可执行动作
- /SubmitForm — PDF 表单提交
- /JavaScript、/JS — 嵌入的 JavaScript 代码
- /OpenAction + /AA — 自动打开动作
FFI 层 C ABI 导出
附件扫描器通过 security_ffi.rs 暴露了 6 个 C ABI 函数,供 C/C++/Python 等语言调用:
// 创建扫描器实例
#[no_mangle]
pub extern "C" fn rust_scanner_create(deep_scan: i32) -> FfiScannerHandle;
// 扫描缓冲区(内存中的文件数据)
#[no_mangle]
pub extern "C" fn rust_scanner_scan_buffer(
handle: FfiScannerHandle,
data: *const u8,
data_len: usize,
filename: *const c_char,
) -> *mut FfiScanResult;
// 扫描文件路径
#[no_mangle]
pub extern "C" fn rust_scanner_scan_file(
handle: FfiScannerHandle,
filepath: *const c_char,
declared_filename: *const c_char,
) -> *mut FfiScanResult;
// 扫描 ZIP 归档
#[no_mangle]
pub extern "C" fn rust_scanner_scan_archive(
handle: FfiScannerHandle,
data: *const u8,
data_len: usize,
filename: *const c_char,
) -> *mut FfiScanResult;
// 计算 Shannon 熵
#[no_mangle]
pub extern "C" fn rust_scanner_calculate_entropy(
data: *const u8,
data_len: usize,
out_entropy: *mut f64,
) -> i32;
// 释放扫描结果
#[no_mangle]
pub extern "C" fn rust_scanner_free_result(result: *mut FfiScanResult);
测试覆盖
本模块共包含 18 个单元测试,覆盖以下场景:
- 基础检测:安全文件、危险扩展名、双扩展名检测(3 tests)
- 魔数识别:PE/PDF 魔数识别、类型不匹配检测(3 tests)
- 宏检测:OLE2 VBA 宏检测(1 test)
- 熵分析:均匀分布(8.0)、常量(0.0)、空数据、混合内容、英文文本(5 tests)
- 归档扫描:安全 ZIP、危险扩展名内嵌、双扩展名内嵌、非 ZIP 数据、ZIP 炸弹检测(5 tests)
- 恶意模式:PE 恶意 API、PE 打包器、PDF JavaScript、PDF /Launch 动作(4 tests)
- 集成测试:高熵加壳检测、深度扫描归档、纯文本不误报(3 tests)
设计取舍
在设计附件扫描器时,我们做出了以下关键设计决策:
- 纯内存操作:ZIP 归档扫描完全在内存中完成,不需要将文件写入磁盘,避免了磁盘 I/O 和临时文件泄露风险。代价是需要自行解析 ZIP 本地文件头,增加了代码复杂度。
- 启发式签名匹配:恶意模式检测使用字符串搜索而非完整正则表达式,牺牲了一定精确度,但大幅降低了误报率和性能开销。
- deep_scan 模式切换:通过一个布尔值控制是否启用深度扫描,让调用方在快速扫描和深度分析之间灵活选择。例如,IMAP 收件时可用 deep_scan=false 快速过滤明显威胁,用户打开附件时用 deep_scan=true 深度扫描归档内容。
- 威胁等级升级策略:当一个文件同时触发多个威胁指标时,扫描器自动取最高等级而非累加。这避免了"小问题堆积"导致的误报,同时确保严重威胁不被忽视。
源码结构总览
整个 attachment_scanner.rs 共 958 行,结构如下:
数据类型定义 (13行)
ThreatLevel enum (5级)
ThreatType enum (8种)
ScanResult struct (5字段)
AttachmentScanner 主结构 (400行)
new() / scan_file() / scan_buffer() / scan_archive_buffer()
静态辅助函数 (545行)
is_dangerous_extension() - 24种危险扩展名
has_double_extension() - 7种危险双扩展名
has_vba_macros() - OLE2/OOXML VBA检测
detect_real_type() - 9种魔数识别
is_type_mismatch() - 类型欺骗检测
calculate_entropy() - Shannon熵 (0.0-8.0)
check_malware_patterns() - PE/PDF恶意签名
测试模块 (380行)
18个单元测试 + 辅助函数 (create_minimal_zip / crc32_simple)

发表评论 取消回复