mcpField
W
writing-yara-rules

writing-yara-rules

github.com/trilwu安全17750 次安装12 周前更新
代码仓库

关于 writing-yara-rules

面向恶意软件分析与检测人员的 YARA 规则编写指南,涵盖耐久特征选择、条件设计及验证,侧重降低误报并兼顾扫描性能。

主要功能

  1. 01选择稳定的家族特征
  2. 02组合文本、十六进制与正则字符串
  3. 03使用 PE、ELF 等模块
  4. 04控制误报与扫描性能
  5. 05测试文件和内存规则
  6. 06组织并共享规则集

适用场景

  1. 01为恶意软件家族编写检测规则
  2. 02将样本或 IOC 转为可测试签名
  3. 03在样本库中搜寻相关恶意软件
  4. 04审查规则的误报与性能

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/trilwu/secskills --skill writing-yara-rules

安装后在 Claude Code 中输入/writing-yara-rules确认已加载。

详细介绍

编写 YARA 规则

好的 YARA 规则匹配的是恶意软件的本质——它的代码、结构和不可避免的常量,而不是它的伪装,例如文件名或下次构建时就会变化的可变字符串。匹配本质,规则就能经受重新编译并捕获整个家族;匹配伪装,你就只能捕获某个样本一次。整个过程的关键在于选择字符串和条件:既要足够具体以避免误报,也要足够持久以便泛化。

适用场景

  • 编写 YARA 规则以检测恶意软件家族、工具或能力
  • 为已知样本中的文件或内存伪影创建特征码
  • 将 IOC 或捕获的样本转化为可移植、可测试的检测规则
  • 在语料库中搜寻某个家族(VirusTotal Retrohunt、LOKI/THOR、主机扫描)
  • 根据共享代码、常量或结构对相关样本进行聚类
  • 审查或调整现有规则,以减少误报并提升扫描性能

不适用场景

  • 检测目标属于日志或 SIEM 遥测,而非文件或内存——请使用 writing-sigma-rules
  • 选择检测目标及其应归属的位置,属于高于规则语法的策略问题——请使用 engineering-detections
  • 你还不够了解样本,无法选择持久的锚点——请先使用 analyzing-malware
  • 执行威胁搜寻,而非编写特征码——请使用 hunting-threats
  • 整理 IOC、归因信息和完整报告——请使用 producing-threat-intelligence

规则结构

包含三个部分:meta(文档信息,不参与匹配)、strings(模式)和 condition(决定是否命中的布尔条件)。

import "pe"

rule Family_Loader_v1
{
    meta:
        author      = "analyst"
        date        = "2026-07-26"
        description = "ExampleLoader stage-1, config-decode stub + family constants"
        hash        = "a1b2c3...<sha256 of the analyzed sample>"
        reference   = "https://internal/case/1234"
        version     = "1"
        tlp         = "amber"
    strings:
        $decode = { 8A 04 0? 32 0? 88 0? 4? 3B ?? 72 }
        $marker = "cfg::begin" ascii
        $mutex  = "Global\\ExL-%08x" ascii
    condition:
        uint16(0) == 0x5A4D and filesize < 2MB and 2 of them
}

重要的元数据字段。 author 和 date 用于记录来源;hash 记录规则所基于样本的确切哈希,以便未来的分析人员重新验证;reference 链接到能佐证规则的案例或报告;description 说明检测对象和原因。元数据是自由格式文本——YARA 不会对其进行求值——因此应将其视为规则的变更日志。添加工具会读取的 version/tlp/score 字段。

字符串与修饰符

文本字符串是默认类型。十六进制字符串匹配原始字节,并支持通配符和跳转。正则表达式写在斜杠之间。

strings:
    $text  = "Mozilla/5.0 (Custom-UA)" ascii wide
    // Hex: ?? = any byte, 0? = any low nibble, [4-6] = 4 to 6 arbitrary bytes
    $hex   = { 55 8B EC 83 EC ?? 53 56 57 [4-6] E8 ?? ?? ?? ?? }
    $re    = /\/gate\/[a-f0-9]{16}\.php/ nocase

修饰符会改变字符串的匹配方式。可以组合使用能够共存的修饰符。

修饰符效果
nocase不区分大小写地匹配文本/正则表达式
wide匹配 UTF-16LE(每个字符两个字节)——适用于大多数 Windows 字符串
ascii匹配单字节形式;与 wide 配合使用可匹配两种形式
fullword仅在两侧为非字母数字字节时匹配
xor匹配全部 256 种单字节 XOR 编码变体(密钥为 0x00-0xFF)
xor(0x01-0xff)在限定的密钥范围内进行 XOR 匹配
base64 / base64wide匹配三种 base64 编码对齐方式
private匹配结果不会显示在输出中(-s/回调);仍会计入 of them

xor 无需知道密钥即可查找由单字节密钥隐藏的字符串;base64 可捕获嵌入编码数据块中的配置值。两者都会增加扫描耗时——只有在有证据表明存在相应编码时才使用,不要默认启用。

构建持久的条件

条件决定了规则的具体程度。除了 all of them / N of them 之外,还可以使用:

condition:
    // Anchor on file type before touching strings — cheap and decisive
    uint16(0) == 0x5A4D and              // MZ (PE)
    uint32(0) == 0x464C457F and          // 0x7F 'E' 'L' 'F' (ELF)

    // Offset constraints
    $magic at 0 and                      // string must sit at offset 0
    $tag in (0..1024) and                // within the first 1 KB
    #beacon >= 3 and                     // the $beacon string appears 3+ times

    // Bound the search — a 40-byte dropper is not a 50 MB installer
    filesize < 500KB and

    // Quorum across a candidate set, resilient to any one string changing
    3 of ($cfg*) and

    // for..of: k of a set, with a per-match predicate
    for any i in (1..#url) : ( @url[i] < 0x2000 )

#string 表示匹配次数,@string[i] 表示第 i 次匹配的偏移量,!string[i] 表示其长度。for..of 可表达“这些字符串中任意/所有/N 个各自满足某个条件”,适用于编写“已知桩代码 100 字节范围内存在 RWX 标记”之类的规则。先使用 uint* 魔数进行锚定,可让引擎在扫描字符串前跳过不匹配的文件,从而同时提高准确性和速度。

特异性与持久性

每条规则都处于一条光谱上:太宽泛会产生误报,太狭窄则只能捕获一个构建版本。目标是一组特定于家族的常量,加上一个结构锚点。

不好——单个常见字符串,会匹配一半的良性软件:

rule Bad_TooBroad {
    strings:
        $ = "GetProcAddress"
    condition:
        $
}

不好——文件名只是伪装的定义;该家族会在每次活动中更改它:

rule Bad_Filename {
    strings:
        $ = "svch0st_updater.exe" nocase
    condition:
        $
}

好——多个作者不会轻易更改的常量,并通过文件类型和大小进行限制,同时要求达到一定数量的匹配项,避免一个被修改的字符串导致规则失效:

rule Good_FamilyCluster {
    meta:
        description = "ExampleRAT: config key schedule + mutex fmt + UA"
    strings:
        $keysched = { 66 0F 6F 05 ?? ?? ?? ?? 66 0F EF C8 66 0F 7F 4? }
        $mutexfmt = "ExR-{%04x-%04x}" ascii
        $ua       = "X-ExR-Build:" ascii wide
        $err      = "cfg decrypt failed @stage%d" ascii
    condition:
        uint16(0) == 0x5A4D and filesize < 3MB and 3 of them
}

好的规则之所以能泛化,是因为它的锚点是恶意软件必须具备的东西——它自己的密钥调度、自有互斥体格式、自有诊断字符串——而不是操作人员会在不同部署之间切换的内容。

使用模块

模块会公开解析后的结构。在文件顶部使用 import。

PE——对 Windows 样本最有价值的模块:

import "pe"
rule PE_Structural {
    condition:
        pe.is_pe and
        pe.imphash() == "d2a6...<import hash>" and       // API import fingerprint
        pe.number_of_sections == 5 and
        pe.imports("wininet.dll", "InternetOpenA") and
        pe.exports("PluginInit") and
        for any s in pe.sections : ( s.name == ".xdata" and s.raw_data_size == 0 ) and
        pe.rich_signature.length > 0 and                 // Rich header (compiler fingerprint)
        pe.timestamp > 1577836800
}

imphash 会将使用相同导入表构建的样本聚类;Rich 标头可为构建工具链生成指纹,且很难伪造;pe.imports/pe.exports 锚定的是能力,而非偶然出现的字节。

ELF 为 Linux 提供与 PE 类似的功能(elf.number_of_sections、elf.type、elf.symtab)。math 可为区域评分——标记经过压缩或加密的数据块:

import "math"
rule Packed_HighEntropy {
    condition:
        uint16(0) == 0x5A4D and
        math.entropy(0x1000, filesize - 0x1000) >= 7.2 and   // near-random tail
        math.mean(0, filesize) > 100
}

hash 可在条件中计算摘要——适合固定特定的嵌入式资源,而非整个文件(整个文件的哈希无需使用 YARA):

import "hash"
rule EmbeddedResource {
    condition:
        hash.sha256(0x400, 0x200) == "e3b0c4...<digest of the fixed config blob>"
}

定位与位置无关的特征

锚定会随代码移动的特征,而非随活动变化的特征。按持久性大致排序:加密常量(S 盒、MD5/SHA 初始值、自定义密钥调度)→ 该家族自己的互斥体/管道/注册表名称模板 → 配置标记和分隔符 → C2 URI 模板(/gate/、/panel/upload.php)→ 开发者留下的独特错误和调试字符串 → 使用 floss 反混淆后得到的栈字符串。文件名、编译时间戳、活动使用的 C2 IP,以及单个常见的 Win32 API 名称都不持久——它们只是伪装。

消除误报

规则命中样本并不代表完成;不命中良性软件才算完成。以下三个手段要结合使用:

  • 匹配数量——要求匹配 N of M 个字符串,绝不要只依赖一个常见字符串。任何单个字符串都可能出现在良性软件中;三个家族常量同时出现则不会。
  • 文件类型锚点——在条件开头使用 uint16(0) == 0x5A4D 或 ELF 魔数,这样规则就不会扫描无关文件类型。
  • 大小限制——filesize < N。20 KB 的加载程序不是 200 MB 的游戏客户端;大小限制可以轻松排除整类偶然匹配。

测试与验证

验证不是可选项。未经测试的规则迟早会引发误报风暴。

yara -w rule.yar sample                 # -w suppresses warnings; does it fire?
yara -s -w rule.yar sample              # -s prints which strings matched, and where
yara -w -r rule.yar ./samples/family/   # -r recurse: must hit every known-true sample
yara -w -r rule.yar ./corpus/goodware/  # must produce ZERO hits — the load-bearing step
yara -C rules.yarc ...                  # compile once, scan many with the compiled form

原子及 4+ 字节锚点为何重要。 YARA 会通过提取每个字符串中的短定长字节子串——原子——来进行扫描,并先搜索这些子串;原子匹配后,才会对整个字符串进行完整验证。如果一个字符串唯一的定长片段只有 1–2 个字节(通配符过多的十六进制模式、两个字母的文本字符串),就会产生较弱的原子,导致其频繁匹配,几乎对每个文件都要进行验证,从而使性能大幅下降。确保每个字符串至少包含一段连续的 4+ 个非通配符字节。YARA 会在编译时针对速度较慢的字符串和较短的原子发出警告——去掉 -w(该选项会抑制这些警告),并将这些警告视为错误。避免使用无界和灾难性正则表达式(.*、嵌套量词);优先使用有界字符类和跳转。规则集运行缓慢时,可使用 yara -S(--print-stats,按规则统计信息)进行性能分析。

内存与进程扫描

文件规则和内存规则不可互换。以 uint16(0) == 0x5A4D 和 PE 节为锚点的规则,不会在载荷已解包、已重定位且没有磁盘头的进程上触发。

yara -w rule.yar mem.dmp                # scan a memory dump / minidump
yara -w rule.yar 4242                   # scan a live process — the PID is the final argument
yara -w -s rule.yar 4242                # -s prints which strings matched in the process, and where

对于内存规则,应去掉文件魔数锚点和 filesize 限制(进程没有有意义的文件大小);改为以仅在解包后才存在的解码后字符串和代码为锚点——明文配置、C2 模板、运行时拼接的栈字符串。将文件扫描规则和内存扫描规则分开,或者为仅适用于文件的条件设置门控,让同一条规则可以用于两者。

YARA-X:现代引擎

YARA-X 是 Rust 重写版本,也是发展方向:速度更快、更安全、诊断更完善,并且在很大程度上与源代码兼容。条件允许时,编写可在两者下运行的新规则。需要了解以下差异:

  • CLI 为 yr(例如,使用 yr scan rule.yar sample 扫描、使用 yr compile 编译、使用 yr fmt 格式化)。
  • 更严格的解析会拒绝一些有歧义的旧规则;负索引和少数已弃用的结构已被移除;include 和模块语义也有所收紧。
  • 正则表达式引擎有所改进,性能警告也更清晰;yr fmt 和语言服务器已内置。
  • 经典 YARA 的部分第三方模块尚未全部移植——依赖冷门模块前请先确认。

如果部署环境中混用不同引擎,请使用两种引擎测试同一条规则(LOKI/THOR 和较旧的扫描器运行经典 YARA;较新的流水线运行 YARA-X)。

组织和共享规则

  • 规则集 — 每个家族或活动使用一个文件,通过 include "base.yar" 共享辅助规则;将整个目录编译成单个 .yarc 以供分发。
  • 私有规则 — private rule 不会单独报告;在公共规则中引用它,将其用作可复用的谓词(例如共享的“是否为 Windows PE”门控、共享的加壳器检测器)。这样可以让条件保持 DRY 且易于阅读。
  • 标签 — rule Foo : loader family_x { ... };扫描时使用 yara --tag loader 筛选。按角色和家族添加标签,便于在搜寻时选择子集。
  • 外部变量 — yara -d ext=value(或 -x module=path);在条件中引用 ext,让同一条规则可根据不同环境采用不同表现(例如由扫描主机传入的文件名/文件路径)。
  • 元数据规范 — 统一的 author/date/reference/hash/score 字段,可让下游工具(THOR、VT、你的 CI)进行排序、评分和归属。
private rule is_pe {
    condition: uint16(0) == 0x5A4D and filesize < 10MB
}

rule Family_X_Loader : loader family_x {
    meta:
        author = "analyst"
        score  = 75
    strings:
        $a = "ExL-%08x" ascii
        $b = { 8A 04 0? 32 0? 88 0? 4? 3B ?? 72 }
    condition:
        is_pe and all of them
}

应当摒弃的借口

  • “我对样本做了哈希,这就是一条规则。” 哈希只能精确匹配一个文件,除此之外什么也匹配不到。下一个构建版本的哈希就会不同。这是一个 IOC,不是检测规则。
  • “它匹配了样本,可以发布了。” 未经良性文件语料库测试的规则,会引发一场尚未爆发的误报风暴。扫描样本并不能证明它不会匹配其他内容。
  • “有一个好字符串就够了。” 一个字符串只需经过一次变异就会失效,而且常见字符串无处不在。应要求达到家族特征常量的多数门槛。
  • “为求稳妥,我会给整个函数加通配符。” 过度使用通配符的十六进制规则不会留下 4 字节原子,因此规则只能依赖薄弱锚点匹配,严重拖慢扫描性能。保留真实的字节序列。
  • “文件名很有辨识度,就用它吧。” 文件名只是伪装——操作者每次活动都可以零成本更名。应以代码和结构为锚点。
  • “它在文件上有效,所以在内存中也有效。” 解包后的进程映像中不存在文件魔数和节锚点。内存扫描需要自己的规则。
  • “先写宽泛规则,以后再调优。” 生产环境中的每次嘈杂命中都会损害分析师的信任,并掩盖真正的匹配。应在部署前调优,而不是部署后再调。

阅读外部来源

以 Markdown 格式获取公开公告、规范和供应商报告:

curl -sL "https://defuddle.md/<url>"      # scheme in the path is optional

这会剥除页面中的冗余内容——对于以文字为主的页面,令令牌数减少约 78%——并返回全文而非摘要,因此你可以对其进行 grep,并相信未找到结果的判断。

以下三种情况不适用。获取 JSON 和 API 响应时应使用原始内容,因为可读性提取会破坏结构化数据。获取需要身份验证或由 JavaScript 渲染的页面时,应直接访问,因为它会以匿名方式检索页面。切勿通过它访问对手的基础设施(钓鱼链接、C2、恶意软件托管站点)、客户端拥有的主机或交战 URL——请求会离开你的机器并发送给第三方;对于正在运作的对手基础设施,这也会向运营者暴露你的行动。

有些网站会阻止提取器,并返回错误内容而非页面——例如 freedesktop.org 返回的 {"error":"Failed to fetch: 418 I'm a teapot"}。这表示获取请求遭到拒绝,并不表示来源称该内容不存在。在据此得出任何结论之前,请直接重新获取该 URL。

参考资料

  • writing-sigma-rules — 将同样的技巧用于日志和 SIEM 遥测数据
  • engineering-detections — 选择检测目标及其适用位置
  • analyzing-malware — 了解构建持久规则所依据的样本
  • hunting-threats — 执行这些签名所驱动的威胁狩猎
  • producing-threat-intelligence — 整理 IOC、归因和报告
  • reporting-security-findings — 编写检测结果报告
  • YARA(VirusTotal)和 YARA-X — 检测引擎及其文档
  • yarGen — 根据样本自动生成候选规则和字符串(仅作起点,绝非可直接使用的成品规则)
  • yara-validator / yr fmt — 在 CI 中检查并规范化规则
  • LOKI 和 THOR(Nextron)— 用于主机端狩猎的 IOC 和 YARA 扫描器
  • VirusTotal Retrohunt — 在 VT 的样本库中运行规则,以查找相关样本

评论

支持 Markdown