安全与风控
编写高质量 YARA-X 恶意软件检测规则(含字符串选择…
在编写、审查或优化 YARA-X 恶意软件检测规则时,用系统化方法选择字符串、设计条件、验证误报并迁移旧版规则。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
编写高质量 YARA-X 恶意软件检测规通用
解决思路
- 安装 YARA-X:使用 `brew install yara-x` 或 `cargo install yara-x`,CLI 命令为 `yr`。
- 确保字符串能生成良好的 atom:YARA 提取 4 字节子序列用于快速匹配;避免重复字节、常见序列或少于 4 字节的字符串,否则会强制对过多文件进行慢速字节码验证。
- 针对特定家族而非类别:写“检测 LockBit 3.0 配置提取例程”,而不是“检测勒索软件”。
- 部署前用正常软件测试:在 VirusTotal 正常软件语料库或自己的干净文件集上验证规则。
- 用廉价检查短路:先 `filesize`(即时),再 magic bytes(近乎即时),再字符串(廉价),最后模块(昂贵)。
- 编写元数据作为文档:包含 `description`(以“Detects”开头)、`author`、`reference`、`date`。
- 按平台适配模式:Windows PE 用 `uint16(0) == 0x5A4D`,macOS Mach-O 用 `uint32(0) == 0xFEEDFACE`(32 位)、`0xFEEDFACF`(64 位)、`uint32be(0) == 0xCAFEBABE`(通用),Office 文档用 `uint32(0) == 0x04034B50`,Chrome 扩展用 `crx` 模块,Android 应用用 `dex` 模块。
- 注意 `uintNN()` 读取小端序:将常量写成字节反转形式,或使用 `uintNNbe()` 按文件顺序写;ZIP/OOXML 文件以字节 `50 4B 03 04` 开头,所以应写 `uint32(0) == 0x04034B50`,而不是 `uint32(0) == 0x504B0304`;Mach-O 通用二进制在磁盘上是 `CA FE BA BE`,应写 `uint32be(0) == 0xCAFEBABE` 或 `uint32(0) == 0xBEBAFECA`;用 `yr scan` 对已知良好样本验证 magic-byte 检查。
- macOS 恶意软件检测:使用 magic bytes 加字符串模式,关注键盘记录器痕迹(`CGEventTapCreate`、`kCGEventKeyDown`)、SSH 隧道字符串(`ssh -D`、`tunnel`、`socks`)、持久化路径(`~/Library/LaunchAgents`、`/Library/LaunchDaemons`)、凭据窃取(`security find-generic-password`、`keychain`)。
- JavaScript 检测:npm 包用 `package.json` 模式、postinstall/preinstall 钩子、外泄组合(fetch + 环境访问 + 凭据路径);Chrome 扩展用 `crx` 模块;独立 JS 用混淆标记(eval+atob、fromCharCode 链)、唯一函数/变量名、打包载荷;压缩/webpack 包用能在打包后存活的唯一字符串(URL、magic 值),避免函数名。
- JavaScript 好字符串:以太坊函数选择器 `{ a9 05 9c bb }`(`transfer(address,uint256)`)、`{ 70 a0 82 31 }`(`balanceOf(address)`);零宽字符隐写 `{ E2 80 8B E2 80 8C }`;混淆器签名 `_0x`、`var _0x`;特定 C2 域名和 webhook URL。
- JavaScript 坏字符串:`require`、`fetch`、`axios`(太常见);`Buffer`、`crypto`(合法用途广泛);单独的 `process.env`(需要特定环境变量名)。
- 字符串价值排序:互斥体名是金,C2 路径是银,错误消息是铜;栈字符串几乎总是唯一的;如果需要超过 6 个字符串,说明过拟合。
- 拒绝以下候选字符串:少于 4 字节(无 atom);重复字节如 `0000`、`9090`(弱 atom);API 名如 `VirtualAlloc`、`CreateRemoteThread`(每个打包器和安装程序都调用);出现在 Windows 系统文件中(必然误报);常见路径如 `C:\Windows\`、`cmd.exe`(无处不在);出现在其他恶意软件家族中(不能识别本家族)。
- 选择字符串类型:精确 ASCII/Unicode 文本用 `$s = "MutexName" ascii wide`;特定字节序列用 `$h = { 4D 5A 90 00 }`;带变化的字节序列用十六进制通配符 `{ 4D 5A ?? ?? 50 45 }`;带结构的模式(URL、路径)用有界正则 `/https:\/\/[a-z]{5,20}\.onion/`;未知编码(XOR、base64)用修饰符 `$s = "config" xor(0x00-0xFF)`。
- 修饰符纪律:绝不推测性使用 `nocase` 或 `wide`,只在有确认证据表明样本间大小写或编码变化时使用;`nocase` 使 atom 生成翻倍,`wide` 使字符串匹配翻倍。
- 条件设计按短路顺序:`filesize <`、magic bytes、字符串、模块;如果条件超过 5 行,拆分为多个规则。
- 选择 `all of` 与 `any of`:字符串各自唯一时用 `any of them`;字符串常见但组合可疑时用 `all of them`;字符串置信度不同时分组 `all of ($core_*) and any of ($variant_*)`;出现误报时收紧 `any` → `all` 并添加更多必需字符串。
- 按置信度分组:用前缀表达分级要求,如 `any of ($a*) and any of ($b*)` 要求来自两个类别的证据。
- 模块与字节检查:imphash、rich header、authenticode 用 PE 模块;magic bytes 或简单偏移用 `uint16`/`uint32`(更快,无模块开销);节名/大小用 PE 模块但先放 magic-byte 过滤器;Chrome 扩展权限用 `crx` 模块;LNK 目标路径用 `lnk` 模块;如果 `uint32()` 能做,就不要加载模块。
- 性能:正则必须锚定到 4+ 字节字面量,否则会在每个文件偏移处求值;写 `/mshta\.exe http:\/\/.../` 而不是 `/http:\/\/.../`;无法锚定时用带通配符的十六进制模式。
- 性能:约束每个正则量词,用 `.{0,30}` 而非 `.*`;无界正则会同时导致性能灾难和内存爆炸。
- 性能:用 filesize 约束循环,如 `filesize < 100KB and for all i in (1..#a) : ...`;无界 `#a` 在大文件中可达数千。
- 性能:字节固定时优先用十六进制而非正则。
- 写规则前判断样本是否加壳:熵 > 7.0 可能加壳,先找解壳层;可读字符串很少或没有可能加壳,用熵、PE 结构或加壳器签名;检测到 UPX/MPRESS/自定义加壳器时,针对解壳载荷或检测加壳器本身;有可读字符串时继续基于字符串检测;不要针对加壳层写规则。
- 字符串失败时转向结构:高熵节用 `math.entropy()`;异常导入模式用 `pe.imphash()` 做导入哈希聚类;PE 结构异常用节名、大小、特征;元数据用版本信息、时间戳、资源;没有唯一特征时该样本可能无法仅用 YARA 检测。
- 调试误报:用 `yr scan -s rule.yar false_positive.exe` 查看哪个字符串匹配;在合法库中时添加 `not $fp_vendor_string` 排除;常见开发模式时替换为更具体的字符串;多个通用字符串一起匹配时收紧为要求全部加唯一标记;恶意软件使用常见技术时针对其具体实现细节而非技术本身。
- 放弃方法的时机:提取只返回 API 名和路径时转向结构;找不到 3 个唯一字符串时可能加壳,针对解壳版本或检测加壳器;规则匹配正常软件时 1-2 个匹配调查并收紧,3-5 个找不同指标,6+ 个重新开始;优化后性能仍很差时是架构问题,拆分为聚焦规则或添加严格预过滤器;描述难以编写时规则太模糊。
- 拒绝合理化:唯一于一个样本 ≠ 唯一于整个生态,用正常软件测试;yarGen 只是建议,需手动验证每个字符串,预期丢弃 80%;10 个样本 ≠ 生产环境,用正常软件语料库;一个规则捕获所有变体会导致误报泛滥,针对特定家族;一开始就写紧规则,弱规则部署就是损害;狩猎规则会变成检测规则,同样质量标准;API 名不使软件恶意,需要行为上下文;常见字符串 + `any` = 误报泛滥,只对各自唯一的字符串用 `any of`;`/fetch.*token/` 匹配所有认证代码,添加外泄目标要求;无界正则 = 性能灾难加内存爆炸,用 `.{0,30}`;攻击者会向合法代码注入,检查 eval+decode 链;一个慢规则拖慢整个规则集,优化 atom;`--relaxed-re-syntax` 掩盖真实 bug,修复正则;PEiD 规则已过时。
- 掌握五个工具:`yr` CLI(`yr check` 验证、`yr fmt` 格式化、`yr scan -s` 扫描并显示字符串、`yr dump -m pe` 检查结构);yarGen(`yarGen.py -m samples/ --excludegood` 提取候选字符串);FLOSS(`floss sample.exe` 提取混淆/栈字符串);signature-base(学习高质量示例);YARA-CI(部署前正常软件语料库测试)。
- 开发周期:`yr check rule.yar` 检查语法和精确行号;`yr fmt -w rule.yar` 标准化格式;`yr dump -m pe sample.exe --output-format yaml` 检查结构;`time yr scan -s rule.yar corpus/` 带计时扫描;用 `yr dump` 调查模块字段、调试模块条件不匹配、探索新模块。
- 版本门控功能:`private $helper = "pattern"` 匹配但不输出(v1.3.0+);`// suppress: slow_pattern` 内联静默特定警告(v1.4.0+);`filesize < 10_000_000` 数字下划线(v1.5.0+);`$_unused` 抑制未使用字符串警告。
- Chrome 扩展分析(crx 模块,需 YARA-X v1.5.0+,`permhash()` 需 v1.11.0+):关键 API 为 `crx.is_crx`、`crx.permissions`、`crx.permhash()`;红旗包括 `nativeMessaging` + `downloads`、`debugger` 权限、`<all_urls>` 上的内容脚本。
- Android DEX 分析(dex 模块,需 YARA-X v1.11.0+,与旧版 YARA 的 dex 模块不兼容):关键 API 为 `dex.is_dex`、`dex.contains_class()`、`dex.contains_method()`、`dex.contains_string()`;红旗包括单字母类名(混淆)、`DexClassLoader` 反射、加密资产。
- 从旧版 YARA 迁移:用 `yr check --relaxed-re-syntax rules/` 识别问题,逐个修复后不用 relaxed 模式验证 `yr check rules/`;修复字面量 `{`(`/{/` → `/\{/`)、无效转义(`\R` → `\\R` 或 `R`)、base64 字符串(任意长度 → 至少 3 字符)、负索引(`@a[-1]` → `@a[#a - 1]`)、重复修饰符(移除);`--relaxed-re-syntax` 是诊断而非终点。
- 命名:`{CATEGORY}_{PLATFORM}_{FAMILY}_{VARIANT}_{DATE}`,如 `MAL_Win_Emotet_Loader_Jan25`;类别有 `MAL_`、`HKTL_`、`WEBSHELL_`、`EXPL_`、`SUSP_`、`GEN_`;平台有 `Win_`、`Lnx_`、`Mac_`、`Android_`、`CRX_`。
- 工作流:收集多个样本(单样本规则脆弱);用 `yarGen -m samples/ --excludegood` 提取候选;应用字符串选择测试验证质量,预期丢弃 80% 的 yarGen 输出;写规则时包含正确元数据并先放廉价检查;用 `yr check`、`yr fmt`、linter 脚本做 lint 和测试;用 VirusTotal 语料库或本地干净文件做正常软件验证;部署完整元数据后监控误报;匹配不到 50% 已知变体的规则太窄,匹配正常软件的太宽。
- 审查他人规则:先运行 `uv run {baseDir}/scripts/yara_lint.py suspect.yar` 和 `uv run {baseDir}/scripts/atom_analyzer.py suspect.yar`,按代码(`E002`、`W009`)报告发现,让作者查阅 style-guide.md;机械故障由脚本捕获,判断性决策(字符串是否识别本家族、条件是否仅凭通用字符串就能触发)由人做。
- 避免常见错误:不用 API 名作指标,用调用点十六进制模式加唯一互斥体;不用无界正则 `/https?:\/\/.*/`,用 `/https?:\/\/[a-z0-9]{8,12}\.onion/`;不先放 `pe.imports(...)`,先放 `uint16(0) == 0x5A4D and filesize < 10MB`;不用短字符串 `"abc"`,用 `"abcdef"`(4+ 字节);不写未转义大括号 `/config{key}/`,写 `/config\{key\}/`;不写错误端序 magic bytes `uint32(0) == 0xCAFEBABE`,写 `uint32be(0) == 0xCAFEBABE`。
- 部署前质量检查清单:名称遵循 `{CATEGORY}_{PLATFORM}_{FAMILY}_{VARIANT}_{DATE}`;描述以“Detects”开头并解释检测内容和方式;所有必需元数据齐全;字符串唯一(非 API 名、常见路径或格式字符串);所有字符串 4+ 字节且 atom 潜力好;base64 修饰符仅用于 3+ 字符的字符串;正则有界、锚定到字面量、`{` 已转义;条件以廉价检查开头;magic-byte 常量对已知良好样本验证;规则匹配所有目标样本;规则在正常软件语料库上零匹配;`yr check` 和 `yr fmt --check` 通过;linter 无错误通过;完成同行评审。
- 运行脚本:`uv run {baseDir}/scripts/yara_lint.py rule.yar` 验证风格/元数据;`uv run {baseDir}/scripts/atom_analyzer.py rule.yar` 检查字符串质量。
- 阅读 `examples/` 目录中的真实规则:MAL_Win_Remcos_Jan25.yar(PE 恶意软件:分级字符串计数、每家族多规则)、MAL_Mac_ProtonRAT_Jan25.yar(macOS:Mach-O magic bytes、多类别分组)、MAL_NPM_SupplyChain_Jan25.yar(npm 供应链:真实攻击模式、ERC-20 选择器)、SUSP_JS_Obfuscation_Jan25.yar(JavaScript:混淆器检测、基于密度的匹配)、SUSP_CRX_SuspiciousPermissions.yar(Chrome 扩展:crx 模块、权限)。
- 学习规则仓库:Neo23x0/signature-base(17,000+ 生产规则)、elastic/protections-artifacts(端点测试)、imp0rtp3/js-yara-rules(JavaScript)、InQuest/awesome-yara(精选索引);指南:YARA Style Guide、YARA Performance Guidelines、YARA-X 文档;macOS 特定:Apple 生产规则位于 `/System/Library/CoreServices/XProtect.bundle/`,objective-see 发布 macOS 恶意软件研究和样本。
适用边界 · 注意事项
- 需要反汇编的静态分析 → 使用 Ghidra/IDA 技能
- 动态恶意软件分析 → 使用沙箱分析技能
- 基于网络的检测 → 使用 Suricata/Snort 技能
- 使用 Volatility 的内存取证 → 使用内存取证技能
- 简单的基于哈希的检测 → 直接使用哈希列表