$npx -y skills add killvxk/cybersecurity-skills-zh --skill analyzing-macro-malware-in-office-documents分析嵌入在 Microsoft Office 文档(Word、Excel、PowerPoint)中的恶意 VBA 宏, 识别下载摇篮、载荷执行、持久化机制和反分析技术。 使用 olevba、oledump 和 VBA 去混淆提取攻击链。 适用于 Office 宏分析、VBA 恶意软件调查、恶意文档分析或基于文档的威胁检查相关请求。
| 1 | # 分析 Office 文档中的宏恶意软件 |
| 2 | |
| 3 | ## 适用场景 |
| 4 | |
| 5 | - 一个可疑的 Office 文档(.doc、.docm、.xls、.xlsm、.ppt)被电子邮件安全系统标记 |
| 6 | - 调查投递武器化 Office 文档的钓鱼攻击活动 |
| 7 | - 提取 VBA 宏代码以识别载荷下载 URL 和执行方法 |
| 8 | - 分析混淆的 VBA 代码以了解完整的攻击链 |
| 9 | - 确定文档是否使用 DDE、ActiveX 或远程模板注入而非宏 |
| 10 | |
| 11 | **不适用于**分析非宏 Office 威胁(DDE、远程模板注入);虽然本技能涵盖这些检测,但可能需要专门分析。 |
| 12 | |
| 13 | ## 前置条件 |
| 14 | |
| 15 | - Python 3.8+,安装 oletools(`pip install oletools`) |
| 16 | - Didier Stevens 的 oledump.py |
| 17 | - 未安装 Microsoft Office 的隔离分析虚拟机(防止意外执行) |
| 18 | - XLMDeobfuscator 用于 Excel 4.0 宏分析(pip install xlmdeobfuscator) |
| 19 | - LibreOffice 用于安全文档渲染(默认不执行 VBA 宏) |
| 20 | |
| 21 | ## 工作流程 |
| 22 | |
| 23 | ### 步骤 1:文档初始分类 |
| 24 | |
| 25 | 确定文档是否包含宏或其他活动内容: |
| 26 | |
| 27 | ```bash |
| 28 | # 使用 olevba 快速分类 |
| 29 | olevba suspect.docm |
| 30 | |
| 31 | # 检查 OLE 流和宏 |
| 32 | oleid suspect.docm |
| 33 | |
| 34 | # 输出指标: |
| 35 | # VBA 宏: True/False |
| 36 | # XLM 宏: True/False |
| 37 | # 外部关系: True/False(远程模板) |
| 38 | # ObjectPool: True/False(嵌入对象) |
| 39 | # Flash: True/False(SWF 对象) |
| 40 | |
| 41 | # 综合 OLE 分析 |
| 42 | oledump.py suspect.docm |
| 43 | |
| 44 | # 列出所有带宏指示符的 OLE 流 |
| 45 | # 标记为 'M' 的流包含 VBA 宏 |
| 46 | # 标记为 'm' 的流包含宏属性 |
| 47 | ``` |
| 48 | |
| 49 | ### 步骤 2:提取和分析 VBA 代码 |
| 50 | |
| 51 | 提取完整的 VBA 宏源代码: |
| 52 | |
| 53 | ```bash |
| 54 | # 使用完整去混淆提取 VBA |
| 55 | olevba --decode --deobf suspect.docm |
| 56 | |
| 57 | # 仅提取 VBA 源代码 |
| 58 | olevba --code suspect.docm > extracted_vba.txt |
| 59 | |
| 60 | # 使用 oledump 详细提取 |
| 61 | oledump.py -s 8 -v suspect.docm # 流 8(根据流列表调整) |
| 62 | |
| 63 | # 提取所有宏流 |
| 64 | oledump.py -p plugin_vba_dco suspect.docm |
| 65 | ``` |
| 66 | |
| 67 | ``` |
| 68 | 需要识别的关键 VBA 元素: |
| 69 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
| 70 | 自动执行触发器: |
| 71 | - Auto_Open / AutoOpen(Word) |
| 72 | - Auto_Close / AutoClose |
| 73 | - Document_Open / Document_Close |
| 74 | - Workbook_Open(Excel) |
| 75 | - AutoExec |
| 76 | |
| 77 | 可疑函数: |
| 78 | - Shell() / Shell.Application |
| 79 | - WScript.Shell.Run / Exec |
| 80 | - CreateObject("WScript.Shell") |
| 81 | - PowerShell 执行 |
| 82 | - URLDownloadToFile |
| 83 | - MSXML2.XMLHTTP(HTTP 请求) |
| 84 | - ADODB.Stream(文件写入) |
| 85 | - Environ()(环境变量) |
| 86 | - CallByName(间接方法调用) |
| 87 | ``` |
| 88 | |
| 89 | ### 步骤 3:VBA 代码去混淆 |
| 90 | |
| 91 | 去除混淆层以揭示载荷: |
| 92 | |
| 93 | ```python |
| 94 | # VBA 去混淆技术 |
| 95 | import re |
| 96 | |
| 97 | def deobfuscate_vba(code): |
| 98 | # 1. 解析 Chr() 调用:Chr(104) & Chr(116) -> "ht" |
| 99 | def resolve_chr(match): |
| 100 | try: |
| 101 | return chr(int(match.group(1))) |
| 102 | except: |
| 103 | return match.group(0) |
| 104 | code = re.sub(r'Chr\$?\((\d+)\)', resolve_chr, code) |
| 105 | |
| 106 | # 2. 去除字符串拼接:"htt" & "p://" -> "http://" |
| 107 | code = re.sub(r'"\s*&\s*"', '', code) |
| 108 | |
| 109 | # 3. 解析 ChrW 调用:ChrW(104) |
| 110 | code = re.sub(r'ChrW\$?\((\d+)\)', resolve_chr, code) |
| 111 | |
| 112 | # 4. 解析 StrReverse:StrReverse("exe.daolnwod") -> "download.exe" |
| 113 | def resolve_reverse(match): |
| 114 | return '"' + match.group(1)[::-1] + '"' |
| 115 | code = re.sub(r'StrReverse\("([^"]+)"\)', resolve_reverse, code) |
| 116 | |
| 117 | # 5. 去除 Mid$/Left$/Right$ 混淆(复杂,标记为需要手动审查) |
| 118 | |
| 119 | # 6. 解析 Replace():Replace("Powxershxell", "x", "") |
| 120 | def resolve_replace(match): |
| 121 | original = match.group(1) |
| 122 | find = match.group(2) |
| 123 | replace_with = match.group(3) |
| 124 | return '"' + original.replace(find, replace_with) + '"' |
| 125 | code = re.sub(r'Replace\("([^"]+)",\s*"([^"]+)",\s*"([^"]*)"\)', resolve_replace, code) |
| 126 | |
| 127 | return code |
| 128 | |
| 129 | with open("extracted_vba.txt") as f: |
| 130 | vba_code = f.read() |
| 131 | |
| 132 | deobfuscated = deobfuscate_vba(vba_code) |
| 133 | print(deobfuscated) |
| 134 | ``` |
| 135 | |
| 136 | ### 步骤 4:分析 Excel 4.0(XLM)宏 |
| 137 | |
| 138 | 处理绕过 VBA 检测的旧版 Excel 宏: |
| 139 | |
| 140 | ```bash |
| 141 | # 检测 XLM 宏 |
| 142 | olevba --xlm suspect.xlsm |
| 143 | |
| 144 | # 去混淆 XLM 宏 |
| 145 | xlmdeobfuscator -f suspect.xlsm |
| 146 | |
| 147 | # 使用 oledump 手动 XLM 分析 |
| 148 | oledump.py suspect.xlsm -p plugin_biff.py |
| 149 | |
| 150 | # 需要关注的 XLM(Excel 4.0)宏函数: |
| 151 | # EXEC() - 执行 shell 命令 |
| 152 | # CALL() - 调用 DLL 函数 |
| 153 | # REGISTER() - 注册 DLL 函数 |
| 154 | # URLDownloadToFileA - 下载文件 |
| 155 | # ALERT() - 显示消息(社会工程学) |
| 156 | # HALT() - 停止执行 |
| 157 | # GOTO() - 控制流 |
| 158 | # IF() - 条件执行 |
| 159 | ``` |
| 160 | |
| 161 | ### 步骤 5:检查非宏攻击向量 |
| 162 | |
| 163 | 检查文档中的 DDE、远程模板和嵌入对象: |
| 164 | |
| 165 | ```bash |
| 166 | # 检查 DDE(动态数据交换) |
| 167 | python3 -c " |
| 168 | import zipfile |
| 169 | import xml.etree.ElementTree as ET |
| 170 | import re |
| 171 | |
| 172 | z = zipfile.ZipFile('suspect.docx') |
| 173 | for name in z.namelist(): |
| 174 | if name.endswith('.xml') or name.endswith('.rels'): |
| 175 | content = z.read(name).decode('utf-8', errors='ignore') |
| 176 | # DDE 字段代码 |
| 177 | if 'DDEAUTO' in content or 'DDE ' in content: |
| 178 | print(f'[!] 在 {name} 中发现 DDE') |
| 179 | dde_match = re.findall(r'DDEAUTO[^\"]*\"([^\"]+)\"', content) |
| 180 | for m in dde_match: |
| 181 | print(f' 命令:{m}') |
| 182 | # 远程模板 |
| 183 | if 'attachedTemplate' in content or 'Target=' in content: |
| 184 | urls = re.findall(r'Target=\"(https?://[^\"]+)\"', content) |
| 185 | for url in urls: |
| 186 | print(f'[!] 远程模板 URL:{url}') |
| 187 | " |
| 188 | |
| 189 | # 检查嵌入的 OLE 对象 |
| 190 | oledump.py -p plugin_msg.py suspect.docm |
| 191 | |
| 192 | # 检查外部引用关系 |
| 193 | python3 -c " |
| 194 | import zipfile |
| 195 | z = zipfile.ZipFile('suspect.docx') |
| 196 | for name in z.namelist(): |
| 197 | if '.rels' in name: |
| 198 | content = z.read(name).decode('utf-8', errors='ignore') |
| 199 | if 'http' in content.lower() or 'ftp' in content.lower(): |
| 200 | print(f'{name} 中的外部引用:') |
| 201 | import re |
| 202 | urls = re.findall(r'Target=\"([^\"]+)\"', content) |
| 203 | for url in urls: |