引言:从"看懂样本"到"拦住家族"的最后一公里
在恶意软件分析的完整链路中,YARA 规则产出是把分析成果转化为可复用检测资产的关键一步 。样本静态分析给了你行为画像、动态分析给了你 IOC、逆向工程给了你内部机制------但这些成果如果只停留在内部报告,一旦下次样本换了哈希、换了壳、换了字符串,你的情报就"过期"了。YARA 规则的真正价值,是把"某个样本"的特征抽象成"某个家族"的模式------让一条规则在样本库、终端、网关、SIEM 里长期服役,哪怕攻击者重编译、换 C2、小改字符串,规则依然能命中。
但社区里大量流传的 YARA 教程存在一个共同的局限:要么只讲语法,要么只讲工具 。语法背得再熟,遇到真实样本依然不知道哪条字符串该放进规则、哪条该丢掉;工具用得再溜,生成的规则扔进 VT 扫一遍全是误报。真正决定一条规则能否服役于生产环境的,是三件朴素的事 :字符串选得准不准、条件平衡得好不好、规则跑得快不快。第一条规则是最难写的,写好它是把样本分析"变现"的过程;后面几十条只是复制这条曲线。
本文按"样本 → 字符串 → 条件 → 测试 → 迭代 → 沉淀 "的主线展开,分八个部分:方法论 → 核心语法 → PE 模块进阶 → 完整实战案例(三个规则从 0 到 1)→ 误报治理 → 自动化工具链 yarGen → 规则库管理 → 学习路径。读完之后,你应当能独立完成一次"从拿到样本到提交规则"的完整闭环,并知道什么时候写规则、什么时候不该写规则。
第一章:转化方法论------一条好规则的"四个层次"
在写第一行代码之前,必须先明确"什么样的 YARA 规则才叫好"。社区对"好规则"的定义在逐步收敛,核心是四个层次的能力:
| 层次 | 含义 | 典型反例 |
|---|---|---|
| 准确 | 匹配已知恶意样本,零误报 | 一条规则把 Chrome.exe 也扫出来 |
| 召回 | 匹配同家族变种,少量漏报 | 只匹配到一个特定 SHA256 才算赢------不如直接用哈希 |
| 性能 | 大规模扫描(数百万文件)时 CPU/时间可控 | 一条规则把整个磁盘扫了 4 小时 |
| 可读 | 三个月后你自己还能看懂这条规则为什么这么写 | $s1 = "abc" 没有任何注释,无法复盘 |
1.1 规则的两种"用途"决定编写策略
在动手之前,先问自己:**这条规则是用来"定性"还是用来"狩猎"?**两种用途对应完全不同的编写策略。
用途 A:狩猎型规则------目标是覆盖面广、误报可以接受,用于在大量样本里"扫一扫、看看到底有什么"。
- 策略 :条件宽松,字符串数量可以少,允许
any of them; - 典型输出:一批匹配结果,人工再过滤;
- 生命周期 :短(活动结束就废弃)。
用途 B:定性型规则 ------目标是精确匹配已知家族、零误报,用于生产环境(SIEM、EDR、网关)持续检测。 - 策略:条件严格,字符串要"独特+难改",必须有 filesize/PE 结构等"硬约束";
- 典型输出 :一条匹配即告警,不允许分析师再人工过滤;
- 生命周期 :长(可能服役数年)。
大量规则的失败,源于作者没有想清楚自己在写哪一种。一条狩猎型规则直接扔进生产环境,一定会触发误报风暴;一条定性型规则条件太松,新变种一来就漏。
1.2 什么样的样本"值得写规则"
不是所有样本都值得写规则。社区有共识:以下情况优先写:
- APT 样本:复杂、定向、家族特征独特,规则价值最高;
- 新型家族:VT 检出率低,公开规则库还没有,先发优势明显;
- 本地化定制样本:企业内网特有样本,外部规则库不会覆盖;
- 单一哈希失效的场景 :攻击者使用同 Builder 产出多变种,哈希防不住。
以下情况不建议写规则: - 已广泛流行的家族(Emotet、AgentTesla):社区规则库(Reversing Labs、Valhalla、Yara-Rules)已经有数百条优质规则,重写毫无意义;
- 单次攻击的"一次性"样本:没有家族延续,规则很快失效;
- 纯哈希匹配场景:变种极少,直接用 SHA256 + VT 看板即可。
1.3 规则的三种"来源"(家族 / 行为 / 工具)
YARA 规则不一定要针对"家族"------针对行为的规则往往比针对家族的更耐打。
| 规则类型 | 目标 | 典型字符串 | 稳定性 |
|---|---|---|---|
| 家族规则 | 特定恶意软件家族 | Mutex、PDB 路径、硬编码 C2、Builder 水印 | 中(攻击者可换 Builder) |
| 行为规则 | 特定恶意行为组合 | 勒索软件的 shadowcopy + 加密扩展名组合 |
高(行为比代码更难改) |
| 工具规则 | 特定攻击工具链 | Cobalt Strike、Mimikatz、Malleable Profile 特征 | 高 |
| 编写优先级 :家族规则 > 行为规则 > 工具规则------因为家族规则的"精度"最高,行为规则"召回"最好,工具规则便于对攻击基础设施做归因。 |
第二章:核心语法速通------你需要掌握的"最小集合"
YARA 语法酷似 C 语言,但完整语法有几百页文档。写生产规则,只需掌握"最小集合"------本节按"高频使用"排列,从必会到进阶。
2.1 规则的四段式骨架
yara
import "pe" // 可选:导入模块
import "math"
import "hash"
rule Rule_Identifier { // 规则名,≤128 字符,区分大小写
meta: // 元数据(可选,强烈建议写全)
author = "analyst_name"
date = "2026-09-03"
description = "Detects XXX family based on unique strings + PE structure"
family = "XXX"
reference = "https://..."
hash = "sha256_of_reference_sample"
license = "Apache-2.0"
strings: // 字符串定义(可选)
$s1 = "unique_string" ascii
$s2 = { 4D 5A 90 00 } // 十六进制
$s3 = /regex_pattern/i // 正则
condition: // 条件(必需)
uint16(0) == 0x5A4D and 2 of ($s*)
}
关键要点:
- 规则名:字符+下划线,不能以数字开头,≤128 字符,必须唯一;
- 元数据 :不参与匹配,但在规则命中时随报告返回,是规则可读性的唯一载体;
- 字符串定义区 :以
$开头的标识符 + 值; - 条件区:必须返回布尔值。
2.2 字符串的三种类型
文本字符串、十六进制字符串、正则表达式 ------三种各有用途,生产规则建议至少混用两种 。
文本字符串(最常用):
yara
$s1 = "This program cannot" // 默认 ASCII
$s2 = "注册表路径" wide // UTF-16LE 编码(Windows 宽字符)
$s3 = "CaSe insensitive" nocase // 忽略大小写
$s4 = "unique_phrase" fullword // 前后不能有字母/数字
$s5 = "hidden_data" xor // 自动尝试所有单字节 XOR (0x01-0xFF)
$s6 = "hidden_data" xor(0x01-0x3F) // 限定 XOR 范围,提速
$s7 = "base64encoded" base64 // 匹配 Base64 编码后的形态
$s8 = "base64encoded" base64wide // Base64 + UTF-16LE
十六进制字符串(精准匹配二进制模式):
yara
$r1 = { 4D 5A 90 00 } // 精确字节序列
$r2 = { E8 ?? ?? ?? ?? 68 ?? ?? ?? ?? } // 通配符(? = 任意字节)
$r3 = { 4D 5A [3-10] 50 45 00 00 } // 跳转 [n-m],匹配 3-10 个任意字节
$r4 = { 68 ( 61 | 62 | 63 ) 20 } // 或运算,匹配 61/62/63 任一
正则表达式(灵活但性能开销大):
yara
$re1 = /https?:\/\/[a-z0-9\-\.]+\.(xyz|top|buzz)\/[a-z]{8}/ // C2 URL 模式
$re2 = /[A-Za-z0-9+\/]{50,}={0,2}/ // Base64 长块
$re3 = /cmd\.exe\s+\/c\s+/i // 命令行模式
关键陷阱 :正则表达式必须有"锚点"(atom) 。YARA 引擎会从字符串中挑出 4 字节左右的"原子"喂给 Aho-Corasick 自动机做初筛------没有良好原子的正则(如 /w.*d/、/[0-9]+n/)会导致扫描性能崩塌。
2.3 条件表达式的"高频组合"
条件区是规则的"灵魂" ------它决定了"多少证据才算命中"。以下是 90% 规则用到的"高频组合":
基础逻辑:
yara
condition: $s1 and $s2 // 都匹配
condition: $s1 or $s2 // 至少一个
condition: not $s3 // 不匹配
condition: any of ($s*) // 任意 $s 开头的字符串
condition: all of ($s*) // 所有 $s 开头的字符串
condition: 2 of ($s1, $s2, $s3, $s4) // 至少 2 个
condition: 1 of them // 任意一个(匿名用)
位置限定(减少误报的利器):
yara
condition: $s1 at 0 // 字符串必须在偏移 0(如 MZ 头)
condition: $s1 in (0..1024) // 字符串必须在文件前 1KB 内
condition: #s1 > 3 // 字符串出现次数 > 3 次
condition: @s1[1] < 2048 // 字符串第一次出现在 2KB 内
文件属性:
yara
condition: filesize < 2MB // 文件大小限制
condition: uint16(0) == 0x5A4D // MZ 头(PE 文件)
condition: uint32(0) == 0x464C457F // ELF 头
condition: uint32(0) == 0xfeedface // Mach-O 头
for 循环(高级组合逻辑):
yara
// 所有 $a 的出现位置都在文件前 100 字节内
condition: for all i in (1..#a) : ( @a[i] < 100 )
// 至少有 2 个字符串出现在入口点附近
condition: for 2 of ($a*) : ( $ in (entrypoint..entrypoint+1024) )
// PE 模块:任意节区名为 ".text"
condition: for any section in pe.sections : ( section.name == ".text" )
引用其他规则(复合规则):
yara
rule base_filter {
condition: uint16(0) == 0x5A4D and filesize < 1MB
}
rule composite_rule {
condition: base_filter and $malicious_string
}
2.4 字符串修饰符的"选择原则"
修饰符不是"越多越好"------每一个修饰符都增加匹配范围,也增加误报概率。
| 修饰符 | 何时使用 | 何时避免 |
|---|---|---|
ascii |
字符串确定是 ASCII 编码 | 任何 Windows API 路径/URL(它们通常是 UTF-16LE) |
wide |
字符串是 Windows 宽字符(API 路径、Mutex 名) | 纯 ASCII 的 C2 URL(通常是窄字符) |
nocase |
字符串大小写可能变化(如用户输入) | 硬编码常量(如 PDB 路径、Builder 标识) |
fullword |
字符串是完整单词(如 API 名 CreateFile) |
字符串可能是更长文本的子串 |
xor |
样本用单字节 XOR 加密字符串 | 字符串未被加密(会大幅增加性能开销) |
base64 |
样本用 Base64 编码数据 | 数据未被编码(会爆炸性增加匹配数量) |
private |
字符串作为"内部辅助"不输出 | 字符串本身是关键指标,需要出现在报告中 |
一条经验法则 :每次加一个修饰符,都跑一遍"好样本库"(见第五章)------确保新加的修饰符不会让规则误报暴涨。
第三章:PE 模块进阶------超越字符串的"结构级"检测
字符串是最容易被绕过的 ------攻击者换一个 Mutex、改一个 URL,你的规则就废了。PE 模块让你能针对"结构"写规则------结构比字符串更稳定。
3.1 PE 模块的核心属性
yara
import "pe"
// 机器架构
condition: pe.machine == pe.MACHINE_AMD64 // 64位
condition: pe.machine == pe.MACHINE_I386 // 32位
// 子系统
condition: pe.subsystem == pe.SUBSYSTEM_WINDOWS_GUI // GUI 程序
condition: pe.subsystem == pe.SUBSYSTEM_WINDOWS_CUI // 控制台程序
// 编译时间戳
condition: pe.timestamp >= 1689206400 // 2023-07-13 之后编译
// PE 特性位图
condition: pe.characteristics & pe.DLL // 是 DLL
condition: pe.characteristics & pe.EXECUTABLE_IMAGE // 是可执行文件
// 节区
condition: pe.number_of_sections == 3 // 节区数
condition: pe.sections[0].name == ".text" // 第一个节区名
// 导入/导出
condition: pe.number_of_imports > 20
condition: pe.exports("CPlApplet") // 导出特定函数
// 版本信息
condition: pe.version_info["CompanyName"] contains "Microsoft"
3.2 Imphash:导入表的"家族指纹"
Imphash(导入表哈希)是 YARA 家族聚类最有效的工具之一 ------同一家族用同一 Builder 产出的样本,即使代码不同,导入表往往相同,Imphash 完全一致。
yara
import "pe"
import "hash"
// BlackBerry 发布的 Chaos/Yashma 勒索软件规则(实战案例)
import "pe"
rule Mal_Win32_ChaosRansomware_2022 {
meta:
author = "BlackBerry Threat Research"
date = "2022-05-10"
family = "Chaos/Yashma"
strings:
$x1 = "Encrypt" ascii wide
$x2 = "(?:[13]{1}[a-km-zA-HJ-NP-Z1-9]{26,33}|bc1[a-z0-9]{39,59})" ascii wide
$z0 = "deleteShadowCopies" ascii wide
$z1 = "shadowcopy" ascii wide
condition:
uint16(0) == 0x5a4d and
filesize < 35KB and
pe.imphash() == "f34d5f2d4577ed6d9ceec516c1f5a744" and
pe.number_of_sections == 3 and
all of ($x*) and 1 of ($z*)
}
Imphash 的使用策略:
- 组合 :
pe.imphash()单独使用太"具体"(变种改导入就漏),应与字符串组合; - 获取:pefile、PEStudio、VirusTotal、MalwareBazaar 都能查到样本的 imphash;
- 注意:攻击者改一个导入顺序就能改变 imphash,所以它是"聚类线索"而非"身份证明"。
3.3 Rich Header Hash:编译器指纹
Rich Header 是微软链接器插入的未公开结构,包含编译工具链信息------它的哈希极难伪造,能用来归因。
yara
import "pe"
import "hash"
rule APT42_CHAIRSMACK_PE_Metadata {
meta:
author = "your_name"
description = "Detects samples based on unique imphash + Rich Header hash"
reference = "https://mandiant.com/resources/blog/apt42-charms-cons-compromises"
condition:
pe.imphash() == "72f60d7f4ce22db4506547ad555ea0b1" or
hash.md5(pe.rich_signature.clear_data) == "c0de41e45352714500771d43f0d8c4c3"
}
3.4 节区与熵值的"加壳检测"
结构化的"加壳检测"规则,用于发现"可疑的加壳 PE":
yara
import "pe"
import "math"
rule Suspect_Packed_PE_High_Entropy {
meta:
author = "your_name"
description = "Flags small PE files with high-entropy sections"
condition:
pe.is_pe and
filesize < 500KB and
pe.number_of_sections <= 4 and
math.entropy(0, filesize) > 7.2
}
熵值判读 :正常代码节区熵值 5~6.5,>7.0 强烈疑似加壳 ,>7.5 几乎必然加密。
第四章:完整实战案例------从样本到规则的全过程
现在把所有知识点串起来,用三个递进式案例演示"从拿到样本到提交规则"的完整流程。
案例一:基于字符串+PE 结构的"家族规则"
背景 :拿到一个可疑样本 sample.exe(SHA256: a3f8c92b...),静态分析已确认属于 AgentTesla 家族变种,需要产出一条生产可用的定性规则。
Step 1:提取候选字符串
用 PEStudio、FLOSS、strings 提取全部字符串,按"独特性"和"稳定性"分类:
text
【高独特性 + 高稳定性】(优先候选)
- PDB 路径:C:\Users\dev\source\repos\NewAgent\obj\Release\NewAgent.pdb
- Mutex 名:Global\{4D51A0B5-...}
- 硬编码 SMTP 域:smtp.qiye.xxx.com:465
【中独特性 + 中稳定性】(候选)
- C2 URL:hxxp://mail.cdn-metrics.example.top/upload.php
- 加密密钥标识:AES_KEY_2026_XX
【低独特性】(丢弃)
- kernel32.dll、CreateFileW 等通用 API
- "This program cannot be run in DOS mode"
- "Mozilla/5.0 ..."(所有浏览器样本都有)
判断"独特性"的实用方法 :把字符串扔进 Google / VirusTotal / 良性样本库搜一下 ------搜出 10 个以上结果,这条字符串就没价值。
Step 2:从 PE 结构提取"硬约束"
用 PEStudio / pefile 提取:
- filesize: 248KB(限定 < 1MB)
- PE 头 :
uint16(0) == 0x5A4D - 节区数: 4
- imphash :
a1b2c3d4e5f6... - 编译时间 : 2026-08-24
Step 3:v1 规则(草稿)
yara
import "pe"
rule AgentTesla_Variant_2026_09 {
meta:
author = "your_name"
date = "2026-09-03"
description = "Detects AgentTesla variant based on PDB path + mutex + PE structure"
family = "AgentTesla"
hash = "a3f8c92b..."
strings:
$pdb = "NewAgent\\\\obj\\\\Release" ascii // PDB 路径片段
$mutex = "{4D51A0B5-" ascii wide // Mutex 前缀
$smtp = "smtp.qiye" ascii wide // SMTP 域名片段
$mz = { 4D 5A 90 00 } // PE 头
condition:
uint16(0) == 0x5A4D and
filesize < 1MB and
2 of ($pdb, $mutex, $smtp)
}
Step 4:v1 测试
bash
# 对已知恶意样本扫描(应命中)
yara rule.yar sample.exe
yara -s rule.yar sample.exe # 显示匹配的字符串
# 对"好样本库"扫描(不应命中)
yara -r rule.yar /opt/goodware/
测试结果假设:
- 恶意样本:命中 ✓
- 好样本库 10000 个文件:命中 3 个 (
NewAgent.pdb字段出现在某个学习资料里)------需要迭代 。
Step 5:v2 迭代(收紧条件)
发现$pdb字符串过于宽泛(obj\Release在任何 VS 项目的 PDB 路径都可能出现),需要更具体:
yara
import "pe"
rule AgentTesla_Variant_2026_09 {
meta:
author = "your_name"
date = "2026-09-03"
description = "Detects AgentTesla variant (2026-09 campaign)"
family = "AgentTesla"
version = "2.0"
hash = "a3f8c92b..."
strings:
$pdb = "NewAgent\\\\obj\\\\Release\\\\NewAgent.pdb" ascii // 完整路径
$mutex = "Global\\\\{4D51A0B5-" ascii wide // 完整 Mutex 前缀
$smtp = "smtp.qiye" ascii wide
$mz = { 4D 5A 90 00 }
condition:
uint16(0) == 0x5A4D and
filesize < 1MB and
pe.number_of_sections == 4 and
( $mutex and $smtp ) or
( $pdb and $smtp )
}
重新测试 :好样本库零命中,恶意样本库 10/10 命中。规则达标。
案例二:基于"行为组合"的"行为规则"
背景 :勒索软件变种众多,家族规则容易失效;写一条行为规则 ,专门匹配"勒索软件的典型行为组合"。
Step 1:定义"行为组合"
勒索软件的行为特征(可以独立于家族):
- 有加密相关 API 字符串(AES、RSA、ChaCha20)
- 有卷影删除字符串 (
vssadmin、shadowcopy) - 有勒索信相关字符串 (
.encrypted、README_FOR_DECRYPT) - 有加密文件扩展名
Step 2:编写行为规则
yara
import "pe"
rule Ransomware_Behavioral_Indicators {
meta:
author = "your_name"
date = "2026-09-03"
description = "Detects ransomware behavioral combination (shadow copy deletion + encryption artifacts)"
category = "ransomware"
reference = "MITRE ATT&CK T1486, T1490"
strings:
// 加密相关
$enc1 = "CryptEncrypt" ascii
$enc2 = "CryptAcquireContext" ascii
$enc3 = "ChaCha20" ascii nocase
// 卷影删除
$vss1 = "vssadmin.exe delete shadows" ascii
$vss2 = "DeleteShadowCopies" ascii wide
$vss3 = "SHADOWCOPY" ascii nocase
// 勒索信
$note1 = ".encrypted" ascii
$note2 = "HOW_TO_DECRYPT" ascii nocase
$note3 = "YOUR_FILES" ascii nocase
// 钱包地址
$btc = /[13][a-km-zA-HJ-NP-Z1-9]{27,34}/
$xmr = /4[0-9AB][1-9A-HJ-NP-Za-km-z]{93}/
// PE 头
$mz = { 4D 5A 90 00 }
condition:
uint16(0) == 0x5A4D and
filesize < 5MB and
// 行为组合:加密 + 卷影删除 + 勒索信(或加密 + 钱包地址)
(
(1 of ($enc*) and 1 of ($vss*) and 1 of ($note*)) or
(2 of ($enc*) and 1 of ($btc, $xmr))
)
}
这类规则的优势 :"行为"比"代码"更难伪造 ------攻击者可以换家族、换 Builder,但"勒索"这个行为本身不会变。缺点:误报率比家族规则高,需要配合 filesize 和 PE 结构收窄。
案例三:基于"工具指纹"的"工具规则"
背景 :Cobalt Strike Beacon 在野使用极广,写一条"工具指纹规则",用于狩猎 CS 流量。
yara
import "pe"
rule Cobalt_Strike_Beacon_Indicators {
meta:
author = "your_name"
date = "2026-09-03"
description = "Detects Cobalt Strike Beacon artifacts (config paths, mutex patterns)"
category = "tool"
reference = "MITRE ATT&CK S0154"
strings:
// Malleable Profile 默认特征
$c1 = "/submit.php" ascii
$c2 = "IEX (New-Object Net.Webclient)" ascii
$c3 = "%s (as %s\\\\%s)" ascii // beacons 输出格式
$c4 = "AppData\\\\Roaming\\\\Microsoft\\\\" ascii wide
// 配置解密标识
$c5 = { 00 01 00 01 00 02 00 } // CS 4.x config magic
// 无文件驻留相关
$c6 = "CreateRemoteThread" ascii
$c7 = "WriteProcessMemory" ascii
// PE 结构
$mz = { 4D 5A 90 00 }
condition:
uint16(0) == 0x5A4D and
filesize < 5MB and
(
2 of ($c1, $c2, $c3, $c5) or
( $c6 and $c7 and 1 of ($c1, $c4) )
)
}
工具规则的特殊价值 :同一个工具(如 CS)被多个 APT 组织使用------一条工具规则可以同时命中多个组织的攻击,是狩猎阶段的"高杠杆"资产。
第五章:误报治理------从"乱报警"到"零误报"的迭代循环
误报是 YARA 规则最大的"杀手" ------一条规则误报率高到 SOC 无法忍受,就会被管理员禁用;一旦被禁用,它就"死"了。据社区统计,68% 没有做过良性样本验证的规则,误报率超过 10%。
5.1 建立你的"好样本库"(Goodware Corpus)
没有好样本库,就无法证明"零误报"。建议逐步建立以下分层:
text
/opt/goodware/
├── windows_core/ # Windows 系统文件(C:\Windows\System32 完整拷贝)
├── common_apps/ # 常见应用(Chrome、Office、Adobe、微信、QQ)
├── dev_tools/ # 开发工具(VS、Python、Git、Node.js、JDK)
├── security_tools/ # 安全工具(Wireshark、Sysinternals、7-Zip)
├── compiler_artifacts/ # 各种编译器产出(MSVC、MinGW、PyInstaller 等)
└── your_company/ # 你所在公司的合法业务文件
关键要求 :数量级至少 10,000 个文件,覆盖所有"你会扫的文件类型"------比如你的规则只针对 PE,好样本库至少有 5,000 个 PE。
5.2 迭代循环:写规则 → 扫描 → 分析误报 → 修正
一条规则的诞生,通常经过 3~5 轮迭代:
text
【Round 1】写 v1 规则(基于已分析样本的字符串)
↓ 扫描好样本库 → 发现 30 个误报
【Round 2】分析误报,找出"字符串太通用"的原因
- 可能是 API 名(CreateFile)
- 可能是常见错误消息
- 可能是 PDB 路径的通用部分(obj\Release)
→ 收紧字符串或加位置限定
↓ 扫描好样本库 → 发现 5 个误报
【Round 3】继续收紧
- 加 filesize 限制
- 加 pe.number_of_sections 限制
- 加 imphash 限制
↓ 扫描好样本库 → 0 误报
【Round 4】扫恶意样本库(确认召回率)
- 如果召回率 < 90%,可能条件太严
- 放宽一些次要条件,重复 Round 2-3
【Round 5】终测 + 提交
- 跑一遍完整的恶意样本库 + 好样本库
- 记录"检出/误报"的数字进 meta
- 提交到规则库
5.3 性能优化:让规则跑得"快"
YARA 性能的核心是"条件求值顺序" ------条件从左到右求值,一旦某项为 False 就跳过后续 。
性能金字塔(从快到慢):
| 层次 | 检查项 | 开销 |
|---|---|---|
| 1(最快) | filesize |
几乎为 0 |
| 2 | uint16(0) == 0x5A4D / uint32(0) |
极低 |
| 3 | 短十六进制字符串 | 低 |
| 4 | 文本字符串(含 good atoms) | 中 |
| 5 | 长十六进制 + 通配符 | 中高 |
| 6(最慢) | 复杂正则、PE 模块、for 循环、熵值计算 | 高 |
| 优化前后对比: |
yara
// 慢:先算熵值再查 PE 头
condition: math.entropy(0, filesize) > 7.0 and uint16(0) == 0x5A4D
// 快:先查 PE 头(毫秒级),再算熵值
condition: uint16(0) == 0x5A4D and math.entropy(0, filesize) > 7.0
三条优化铁律:
yara
// 铁律 1:filesize 和 magic 放在最前
condition: uint16(0) == 0x5A4D and filesize < 2MB and ( ...复杂条件... )
// 铁律 2:避免通配符前缀(会让 atom 选择变差)
// 坏:{ ?? ?? ?? ?? 4D 5A 90 00 }
// 好:{ 4D 5A 90 00 ?? ?? ?? ?? }
// 铁律 3:避免无锚点正则
// 坏:/.*malware.*/ (无 atom,性能崩塌)
// 好:/malware_(\d{4})/ ("mala" 是 good atom)
5.4 避免"Pepper"(胡椒)问题的"三层验证"
社区经常提"avoid peppering" ------指的是过度使用通配符、宽泛条件导致规则对大量无关样本"撒胡椒面" 。避免 Pepper 的三层验证:
第一层:字符串层验证
bash
# 把每条字符串扔进 VT 搜索
# 如果某条字符串命中了 > 10 个无关样本,这条字符串就是"胡椒"
第二层:规则层验证
bash
# 对好样本库全量扫描
yara -r rule.yar /opt/goodware/
# 误报数 = 0 才能进入下一层
第三层:生态层验证
bash
# 用 YARA-CI(VirusTotal)或 yaraQA 检查规则的"语法+结构+误报"
# yaraQA 会对规则打分(100 分制,误报/性能/结构问题都会扣分)
5.5 常见"坑点"清单
text
【坑点 1】只匹配单个字符串(容易误报 + 容易被绕过)
坏:$s1 = "malware_string" ascii nocase
好:uint16(0) == 0x5A4D and $s1 and 2 of ($s*)
【坑点 2】没有 filesize 限制
影响:扫描大文件时性能极差,甚至扫描内存 dump 时溢出
【坑点 3】通配符滥用
坏:{ ?? ?? ?? ?? ?? ?? ?? ?? }
后果:匹配几乎所有文件
【坑点 4】modifiers 加得过多
$s = "hello" ascii wide nocase xor base64 fullword
后果:匹配范围爆炸,误报率飙升
【坑点 5】正则无锚点
坏:/.*password.*/
后果:性能极差,且容易误报
【坑点 6】规则名不具描述性
坏:rule rule1 { ... }
好:rule AgentTesla_Variant_2026_09 { ... }
【坑点 7】meta 不完整
后果:三个月后你自己都不知道这条规则是干嘛的
【坑点 8】硬编码的字符串没有注释
坏:$s1 = "Xyz_9Ab2" ← 为什么是它?
好:$s1 = "Xyz_9Ab2" ascii // unique mutex used by XXX family
第六章:自动化工具链------从"手工写"到"半自动"
手工写规则是对抗高级样本的"精工" ,但面对批量样本,自动化工具能显著提升效率。本节介绍 yarGen 系列工具。
6.1 yarGen:Florian Roth 的规则生成神器
yarGen 的核心原理 :从恶意样本中提取字符串,自动排除"良性软件库"中已有的字符串 ,剩下"独特字符串"用于生成规则。
安装与更新:
bash
# 克隆项目
git clone https://github.com/Neo23x0/yarGen
cd yarGen
# 安装依赖
pip3 install -r requirements.txt
# 首次使用前更新内置数据库(重要!会下载 goodware 字符串/opcode 库)
python3 yarGen.py --update
基础用法:
bash
# 从恶意样本目录生成规则(最简单)
python3 yarGen.py -m /path/to/malware/
# 排除良性字符串(重要!强烈推荐加)
python3 yarGen.py -m /path/to/malware/ --excludegood
# 指定输出
python3 yarGen.py -m /path/to/malware/ -o /tmp/rules.yar -a "your_name" -r "reference_url"
# 只取高分字符串(更严格)
python3 yarGen.py -m /path/to/malware/ -z 10 --score
# 排除 goodware 字符串 + 指定作者 + 输出
python3 yarGen.py -m /path/to/malware/ --excludegood -o output.yar \
-a "analyst_name" -r "internal_case_ref"
关键参数说明:
| 参数 | 说明 | 推荐值 |
|---|---|---|
-m |
恶意样本目录 | 必填 |
--excludegood |
排除良性样本中已有的字符串 | 必加 |
-z |
最低字符串分数 | 10(生产规则) |
-rc |
最多字符串数 | 20 |
-o |
输出文件 | 必填 |
-a |
作者 | 你的 ID |
-r |
参考 | 情报来源 |
--nomagic |
不加 magic 头检查 | 不推荐 |
--nofilesize |
不加 filesize 限制 | 不推荐 |
| yarGen 输出示例(生成的规则初稿): |
yara
rule CN_Actor_ToolKit_Sample_a3f8c {
meta:
author = "your_name"
description = "Auto-generated rule for file a3f8c92b..."
date = "2026-09-03"
hash = "a3f8c92b..."
strings:
$s1 = "Global\\\\{4D51A0B5-" ascii wide
$s2 = "smtp.qiye" ascii wide
$s3 = "NewAgent\\\\obj\\\\Release" ascii
$s4 = { 4D 5A 90 00 }
condition:
uint16(0) == 0x5A4D and 3 of them
}
yarGen 的局限 :它是"规则起点",不是"规则终点" 。生成的规则通常召回率不错但误报率中等 ,必须经过第五章的迭代循环才算生产可用。
6.2 yarAnalyzer:批量规则的质量分析
当你手头有几十条规则时,需要工具帮你"批量分析" 。yarAnalyzer 能分析规则质量、找出重叠、评估复杂度。
bash
python3 yarAnalyzer.py -p /path/to/rules/
6.3 其他工具
| 工具 | 用途 |
|---|---|
| Loki | 扫描器(基于 YARA 的 IOC 检测工具) |
| THOR | 商业版扫描器(Nextron 出品) |
| Valhalla | Nextron 的 YARA 规则订阅服务 |
| yaraQA | 规则质量评分工具 |
| yara-cop | Visual Studio Code 的 YARA 语法检查插件 |
| YARA-CI (VirusTotal) | 在线规则 CI(扫 NSRL 和 VT 良性库) |
| VarComparators | 在线规则对比与聚类工具 |
第七章:规则库管理与部署------从"单条规则"到"作战资产"
一条规则是"原子",一个规则库是"作战体系" 。规则管理的核心是可追溯、可回滚、可分发。
7.1 规则库的目录结构
text
/opt/yara_rules/
├── meta/
│ ├── CHANGELOG.md
│ └── README.md
├── rules/
│ ├── apt/ # APT 组织相关
│ │ ├── APT41_Sample1.yar
│ │ └── APT41_Sample2.yar
│ ├── malware/ # 已知恶意软件家族
│ │ ├── AgentTesla.yar
│ │ ├── CobaltStrike.yar
│ │ └── Ransomware_General.yar
│ ├── behavioral/ # 行为规则
│ │ ├── Packed_PE.yar
│ │ └── Suspicious_Strings.yar
│ ├── toolkit/ # 工具规则
│ │ ├── Mimikatz.yar
│ │ └── Metasploit.yar
│ ├── webshell/ # Webshell
│ │ ├── PHP_Webshell.yar
│ │ └── JSP_Memshell.yar
│ └── index.yar # 索引文件(include 其他)
├── tests/
│ ├── goodware/ # 好样本库
│ └── malware/ # 已知恶意样本库
└── scripts/
├── validate_rules.sh # 规则验证脚本
└── deploy_rules.sh # 部署脚本
index.yar 示例:
yara
include "rules/apt/APT41_Sample1.yar"
include "rules/malware/AgentTesla.yar"
include "rules/behavioral/Packed_PE.yar"
7.2 规则的版本管理与 Git 流程
规则必须进 Git------因为规则变更就是"检测策略变更",需要可追溯:
bash
# 1. 规则变更进 Git
git checkout -b new_rule_agenttesla
vim rules/malware/AgentTesla.yar
git add rules/malware/AgentTesla.yar
git commit -m "Add AgentTesla v2 rule (reduced FP from 3 to 0 on 10k goodware corpus)"
# 2. 跑自动化验证(CI/CD)
# - 语法检查
# - 好样本库扫描(零误报)
# - 恶意样本库扫描(召回率达标)
./scripts/validate_rules.sh
# 3. PR Review → Merge → 部署
git push origin new_rule_agenttesla
7.3 部署到终端与网关
规则部署的常见通道:
| 部署位置 | 工具 | 频率 |
|---|---|---|
| Linux 主机 | YARA CLI + cron | 每日 |
| Windows 终端 | Loki / THOR | 每日 |
| EDR | 商业 EDR API | 实时 |
| 网关/邮件 | YARA-HI 或邮件网关规则 | 实时 |
| SIEM | YARA 规则导入 | 每日 |
| VT / MISP | 上传规则共享 | 每周 |
| Python 调用 YARA 的进阶用法: |
python
import yara
import psutil
# 编译规则
rules = yara.compile(filepath='/opt/yara_rules/index.yar')
# 扫描单个文件
matches = rules.match('/tmp/sample.exe', timeout=30)
for m in matches:
print(f"[!] {m.rule} - meta: {m.meta}")
# 扫描运行中的进程(DFIR 场景)
for proc in psutil.process_iter(['pid', 'name']):
try:
matches = rules.match(pid=proc.info['pid'])
if matches:
print(f"[!] ALERT: PID {proc.info['pid']} ({proc.info['name']}) "
f"matches {[m.rule for m in matches]}")
except (psutil.AccessDenied, psutil.NoSuchProcess):
pass
7.4 规则的生命周期管理
规则不是"写完就完"------需要持续维护:
text
【规则生命周期】
出生:通过验证,部署到生产
↓
活跃:命中告警,定期召回测试
↓
老化:连续 90 天无命中,或家族特征已失效
↓
退役:从生产库移除,归档到 rules/retired/
↓
复活:如果该家族再次活跃,从 retired/ 恢复
定期巡检清单:
text
□ 每周:扫一次好样本库,确认零误报
□ 每月:扫一次恶意样本库,确认召回率
□ 每季度:审计规则 meta 完整性(作者、日期、参考)
□ 每半年:清理 6 个月无命中的规则(移入 retired/)
结语:规则是"分析成果的长期资产",不是"一次性代码"
回顾本篇,YARA 规则编写实战可以浓缩为三条核心原则:
原则一:字符串的"独特性"决定规则的"精度" 。把字符串扔进 Google/VT 搜一搜,搜出 10 个以上良性命中就丢掉------这是最朴素也最有效的"防胡椒"手段。规则的质量,取决于你愿意花多少时间在字符串筛选上 。
原则二:条件的"平衡"决定规则的"生命力" 。太严会漏,太松会误。filesize + PE 头 + 2-3 条独特字符串 是生产规则的"黄金组合";any of them 只能用于狩猎 ,all of them 容易被绕过 ------2 of ($s*) 这类"部分匹配"是大多数场景的最佳解 。
原则三:迭代是规则的"宿命" 。没有一条规则能一次写对 ------写规则、扫好样本、分析误报、修字符串、再扫、再修......直到零误报、召回率达标,才敢说"这是生产规则"。YARA-CI、yaraQA、好样本库是你这条迭代的"护栏",没有它们,你只能靠运气。
最后送给从业者一句话:YARA 规则是恶意软件分析的"最后一公里"------它把你的分析成果从"报告"转化为"资产",从"一次性的洞察"转化为"长期的防御能力" 。当你能在 24 小时内从拿到样本到提交一条零误报、高召回的生产规则,你拥有的就不仅是分析能力,而是整个组织的检测能力。
最后一句忠告 :规则的"个性"就是它的价值 ------每一条独特的 Mutex 名、每一条 PDB 路径、每一条 Malleable Profile 特征,都是你在对抗中"磨"出来的武器。不要把 YARA 规则写成模板化的"通用检测",要把它写成有故事的"威胁叙事"------三个月后读起来,你依然能从每条字符串里看到当时分析样本时的思考。