适用对象:安全运营(SOC)、恶意样本分析(MA)、邮件网关/终端防护研发。本文兼顾可读性与落地性,提供可直接运行的检测思路和脚本。
Microsoft Office(Word、Excel、PowerPoint)及其兼容格式(WPS、OpenOffice)是企业办公的默认入口,天然具备以下攻击优势:
.docx/.xlsx,安全软件对办公文档的拦截阈值较高。因此,Office 文档是 APT、勒索软件、钓鱼邮件的第一载荷选择之一。
| 技术 | 说明 | 常见场景 |
|---|---|---|
| VBA 宏 | 文档打开或关闭时自动运行 AutoOpen、Document_Open 等过程 | Emotet、TrickBot、Qakbot 历史分发 |
| Excel 4.0 (XLM) 宏 | 旧版宏语言,无需 VBA 环境,混淆后检测难度大 | 近五年金融恶意邮件高频出现 |
| DDE / OLE 自动更新 | Word 字段 DDEAUTO 或 Excel 公式执行外部程序 | 无需启用宏即可启动程序 |
| OLE 嵌入对象 | 在文档内嵌入可执行文件、脚本、LNK、Package 对象 | 点击图标后执行 |
| 远程模板加载 (.dotm/.xlam) | 主文档本身干净,打开时加载远程恶意模板 | 绕过静态文件查杀 |
| CVE 漏洞利用 | CVE-2017-11882(EQNEDT32)、CVE-2017-8570、CVE-2018-0802 等 | 无需交互,打开即触发 |
| OOXML 结构异常 | 重打包 zip、隐藏工作表、异常 XML 关系、加密的 VBA 工程 | 反取证、反静态分析 |
| PowerPoint 动作/超链接 | 幻灯片切换动作或 mouseover 触发下载 | 会议投屏钓鱼 |
建议把 Office 文件检测拆成 三层漏斗,逐层提高成本,降低误报:
.docx 伪装成 .pdf、重命名过的 .rtf 等。CreateObject、Shell、WScript.Shell、Run、DownloadString、Environ、注册表操作等;| 工具/库 | 用途 | 推荐场景 |
|---|---|---|
| oletools | Python 套件,含 oleid、olemeta、olevba、mraptor、rtfobj、oleobj、olefile | 宏提取、OLE 对象分析、结构检查 |
| olefile | 读写 OLE 文件 | 自定义解析 |
| python-docx / openpyxl | 解析 OOXML Word/Excel | 自定义提取隐藏内容 |
| YARA | 规则匹配 | IOC、家族特征、混淆模式 |
| VirusTotal / MalwareBazaar | 云端查杀与情报 | 哈希查询、多引擎检测 |
| Cuckoo / ANY.RUN | 沙箱动态分析 | 行为确认 |
| xlrd / xlrd2 | 读取 Excel 二进制/XML | XLM 宏分析 |
| pcodedmp | 反编译 VBA P-code | 源码被删除或加密时 |
以下脚本演示如何批量检查 Office 文档中的宏、危险 API、OLE 对象和外部关系。依赖 oletools、olefile、python-docx、openpyxl。
#!/usr/bin/env python3
# office_scan.py
import os
import re
import sys
import zipfile
from pathlib import Path
from oletools.olevba import VBA_Parser, TYPE_OLE, TYPE_OpenXML, TYPE_Word2003_XML
from oletools.oleobj import OleObject
from oletools.oleid import OleID
from oletools.rtfobj import RtfObjParser
DANGEROUS_KEYWORDS = [
r"Shell\s*\(", r"CreateObject\s*\(", r"WScript\.Shell",
r"DownloadString", r"Invoke-Expression", r"IEX",
r"powershell", r"cmd\.exe", r"mshta", r"regsvr32",
r"rundll32", r"Environ\s*\(", r"CreateProcess",
r"VirtualAlloc", r"WriteProcessMemory", r"Net\.WebClient",
r"Scripting\.FileSystemObject", r"ADODB\.Stream",
]
RE_SUSPICIOUS = re.compile("|".join(DANGEROUS_KEYWORDS), re.IGNORECASE)
def check_ole(file_path):
"""对 OLE/OOXML 文档做 VBA 和 OLE 对象检查"""
results = []
try:
vba = VBA_Parser(file_path)
if vba.detect_vba_macros():
results.append("[WARNING] 检测到 VBA/XLM 宏")
for (subfilename, stream_path, vba_filename, vba_code) in vba.extract_macros():
# 危险 API 检测
matches = RE_SUSPICIOUS.findall(vba_code)
if matches:
results.append(f" [DANGER] 宏中危险函数: {set(matches)}")
# 疑似下载/执行
if any(k in vba_code.lower() for k in ["http", "https", "ftp", "\\", "cmd"]):
results.append(" [WARNING] 宏中出现网络/命令相关字符串")
vba.close()
except Exception as e:
results.append(f"[ERROR] VBA 解析失败: {e}")
try:
oid = OleID(file_path)
indicators = oid.check()
for ind in indicators:
if ind.value and ind.risk == "HIGH":
results.append(f"[HIGH] {ind.name}: {ind.description}")
except Exception as e:
results.append(f"[ERROR] OleID 失败: {e}")
return results
def check_ooxml_relationships(file_path):
"""检查 OOXML 中的外部关系(远程模板、图片、链接)"""
results = []
if not zipfile.is_zipfile(file_path):
return results
try:
with zipfile.ZipFile(file_path) as zf:
for name in zf.namelist():
if name.endswith(".rels"):
content = zf.read(name).decode("utf-8", errors="ignore")
# TargetMode="External" 表示外部关系
external = re.findall(r'TargetMode="External"[^>]*Target="([^"]+)"', content)
for url in external:
if url.startswith("http"):
results.append(f"[WARNING] 外部关系: {url}")
except Exception as e:
results.append(f"[ERROR] OOXML 关系解析失败: {e}")
return results
def check_rtf(file_path):
"""对 RTF 文件做 OLE 对象检查"""
results = []
try:
with open(file_path, "rb") as f:
parser = RtfObjParser(f)
parser.parse()
for obj in parser.objects:
if obj.is_ole:
results.append(f"[WARNING] RTF 内嵌 OLE 对象: {obj.oleformat_id}")
except Exception as e:
results.append(f"[ERROR] RTF 解析失败: {e}")
return results
def scan_file(file_path):
print(f"\n=== 扫描: {file_path} ===")
ext = Path(file_path).suffix.lower()
all_results = []
if ext in {".doc", ".xls", ".ppt", ".docx", ".xlsx", ".pptx", ".docm", ".xlsm", ".pptm"}:
all_results += check_ole(file_path)
all_results += check_ooxml_relationships(file_path)
elif ext == ".rtf":
all_results += check_rtf(file_path)
else:
print("[SKIP] 非 Office 文件")
return
if all_results:
for r in all_results:
print(r)
else:
print("[OK] 未检出明显恶意特征")
if __name__ == "__main__":
for path in sys.argv[1:]:
if os.path.isfile(path):
scan_file(path)
使用示例:
python office_scan.py 发票.docx 工资表.xlsm
xlrd2 或 xlsm2 解析宏表,检查 FORMULA、GOTO、HALT 等函数。Equation Native 流且伴有异常字体记录。pcodedmp 还原。oletools 的 msoffcrypto 插件可检测并尝试弱密码爆破。YARA 适合作为第二层/第三层的规则补充。例如:
rule Office_Macro_Download_Execute {
strings:
$a = "WScript.Shell" nocase
$b = "Microsoft.XMLHTTP" nocase
$c = "ADODB.Stream" nocase
$d = "Shell(" nocase
condition:
uint32(0) == 0xE011CFD0 or // OLE2
filesize < 10MB and
2 of them
}
rule Office_XLM_Exec {
strings:
$a = "=EXEC(" nocase
$b = "=RUN(" nocase
$c = "=REGISTER(" nocase
condition:
any of them
}
powershell、cmd、mshta、wscript)的行为建模,实时告警。.exe 重命名为 .docx,或把 OLE 文档伪装成 PDF。必须魔数校验。Office 文件恶意检测的核心不是“有没有宏”,而是“文件结构和代码语义是否有异常执行意图”。建议采用:
哈希信誉 → 文件结构解析 → 宏/对象静态分析 → 危险函数/IOC 匹配 → 沙箱动态行为确认
这五层漏斗,配合 YARA 规则与 EDR 行为监控,能覆盖大多数 Office 文档类攻击。
参考资源