Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
YARA-Performance-Guidelines — 关于如何编写快速且内存友好的 YARA 规则的指南 | Kitploit
工具/GitHubGitHub/neo23x0/yara-performance-guidelines
静态分析恶意软件分析学习与教育
GitHubneo23x0/yara-performance-guidelines

YARA-Performance-Guidelines

关于如何编写快速且内存友好的 YARA 规则的指南

查看仓库
174231年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

YARA 性能优化指南

编写高效的 YARA 规则对于保持快速、准确的扫描性能至关重要。本指南提供关键原则和最佳实践,帮助你优化规则、减少不必要的计算并避免常见陷阱。它融合了行业专家的见解,包括 Victor M. Alvarez、WXS 以及 YARA 社区的贡献。

  • 修订版 1.6,2025 年 2 月,适用于 YARA 3.7 以上的所有版本

核心要点

本节提供 YARA 性能最佳实践的简明摘要。有关详细说明和示例,请参阅下面的完整指南。

理解 YARA 的扫描过程

"把 YARA 想象成一个两步过程:首先,搜索 strings 中列出的所有模式;其次,评估条件。你无法用精心构造的条件来弥补字符串选择不当的问题。"
— Wesley Shields

YARA 在扫描文件时遵循四个主要步骤:

  1. 编译规则:从定义的字符串中提取原子(4 字节子串)。
  2. Aho-Corasick 搜索:在文件中扫描这些原子。
  3. 字节码引擎:验证完整的字符串匹配。
  4. 条件评估:检查额外的规则逻辑。

字符串选择——最重要的因素

YARA 首先搜索字符串,这使字符串选择成为影响规则效率的最重要因素。

✅ 字符串的最佳实践:

  • 避免过短的字符串(少于 4 字节)——它们会产生太多误报。
  • 使用独特的 4 字节原子——YARA 依赖它们进行快速扫描。
  • 尽量减少十六进制字符串中的通配符——至少保留一个长的具体片段。
  • 谨慎使用正则表达式——如有必要,加入一个固定的 4 字节锚点以提高效率。
  • 避免单字节重复模式——例如,\x00\x00\x00\x00 出现得过于频繁。
  • 谨慎使用 nocase——它会生成呈指数级增长的搜索变体。

优化条件与短路求值

YARA 按顺序评估条件,并在第一次失败时停止。

✅ 条件的最佳实践:

  • 将快速检查放在前面(例如,filesize < X),然后再放开销大的条件。
  • 避免对大数据进行循环(for all i in (1..filesize) 效率低下)。
  • 使用直接偏移量(@)而不是正则表达式来进行顺序检查。

⚠ 注意: 正则表达式条件不会短路,并且总是最后才被评估。

模块——谨慎使用

像 pe、elf 或 magic 这样的模块在评估之前必须解析整个文件,这会增加扫描时间。

✅ 替代方案:

  • 不要使用 pe.is_pe,改用 uint16(0) == 0x5A4D 来识别 PE 文件。
  • 除非需要进行深入的文件检查,否则避免使用模块。

处理匹配过多与扫描缓慢

过多的匹配会减慢扫描速度,并可能触发 “too many matches”(匹配过多)错误。

✅ 修复低效匹配:

  • 检查正则表达式量词——避免没有上界的 .*、.+ 或 {x,}。
  • 减少十六进制字符串中的通配符。
  • 尽可能将交替分支拆分为单独的字符串。

视频教程

@herrcore 制作了一个非常实用的视频教程,涵盖了本性能指南中讨论的主题。

YARA 入门——编写高效的 YARA 规则

基础

为了更好地把握 YARA 性能可以在哪些方面、哪些环节进行优化,了解扫描过程会很有帮助。扫描过程基本上分为 4 个步骤,下面将使用这个示例规则进行非常简化的说明:

root@kitploit:~
import "math"
rule example_php_webshell_rule
{
    meta:
        description = "Just an example php webshell rule"
        date = "2021/02/16"
    strings:
        $php_tag = "<?php"
        $input1   = "GET"
        $input2   = "POST"
        $payload = /assert[\t ]{0,100}\(/
    condition:
        filesize < 20KB and
        $php_tag and
        $payload and
        any of ( $input* ) and
        math.entropy(500, filesize-500) >= 5
}

1. 编译规则

这一步发生在实际扫描之前。YARA 会在搜索字符串中查找所谓的 atoms(原子),以提供给 Aho-Corasick 自动机。详细信息在原子章节中说明,但现在只需知道它们最长为 4 字节,并且 YARA 会相当巧妙地选择它们以避免过多匹配。在我们的示例中,YARA 可能会选择以下 4 个原子:

  • <?ph
  • GET
  • POST
  • sser(来自 assert)

2. Aho-Corasick 自动机

到这里,扫描已经开始了。步骤 2 到 4 将对所有文件执行。YARA 会借助一种称为 Aho-Corasick 自动机的前缀树,在每个文件中查找上面定义的 4 个原子。任何匹配都会被交给字节码引擎。

3. 字节码引擎

例如,如果匹配到 sser,YARA 会检查它是否以 a 为前缀,并且后面是否跟着 t。如果成立,它将接着匹配正则表达式 [\t ]{0,100}\(。通过这种巧妙的方法,YARA 避免了使用缓慢的正则表达式引擎遍历整个文件,而只是挑选特定部分进行更仔细的检查。

4. 条件

所有模式匹配完成后,就开始检查条件。YARA 还有另一种优化机制:只有当示例规则中它前面的 4 个条件都满足时,才执行 CPU 密集型的 math.entropy 检查。更详细的说明见条件与短路求值章节。

如果条件满足,就会报告一个匹配。然后扫描进入步骤 2,继续处理下一个文件。

原子

YARA 会从字符串中提取最长 4 字节的短子串,这些子串被称为 “atoms”(原子)。这些原子可以从字符串中的任何位置提取。扫描文件时,YARA 会搜索这些原子,如果找到其中一个,则会验证字符串是否真正匹配。

例如,考虑以下字符串:

root@kitploit:~
/abc.*cde/

=> 可能的原子是 abc 和 cde,可以使用两者中的任意一个。目前优先选择 abc 原子,因为它们的质量相同,而它是两者中的第一个。

root@kitploit:~
/(one|two)three/

=> 可能的原子是 one、two、thre 和 hree,我们可以单独搜索 thre(或 hree),也可以同时搜索 one 和 two。优先选择原子 thre,因为它比 one 和 two(这两个更短)产生的潜在匹配更少,而且它不包含双写字母 e(字母越独特越好)。

YARA 会尽最大努力为每个字符串选择最佳原子,例如:

root@kitploit:~
{ 00 00 00 00 [1-4] 01 02 03 04 }

=> 这里 YARA 使用原子 01 02 03 04,因为 00 00 00 00 太常见了

root@kitploit:~
{ 01 02 [1-4] 01 02 03 04 }

=> 优先选择 01 02 03 04 而不是 01 02,因为它更长

因此,重要的一点是字符串应包含好的原子。以下是不好的字符串,因为它们包含的原子要么太短,要么太常见:

root@kitploit:~
{00 00 00 00 [1-2] FF FF [1-2] 00 00 00 00}
{AB  [1-2] 03 21 [1-2] 01 02}
/a.*b/
/a(c|d)/

最糟糕的字符串是那些根本不包含任何原子的字符串,例如:

root@kitploit:~
/\w.*\d/
/[0-9]+\n/

这些正则表达式不包含任何可以用作原子的固定子串,因此必须在文件的每个偏移位置进行求值,以查看它们是否匹配。

循环迭代过多

另一个重要的建议是避免使用迭代次数过多的循环,尤其是当循环内的语句过于复杂时,例如:

root@kitploit:~
strings:
	$a = {00 00}
condition:
	for all i in (1..#a) : (@a[i] < 10000)

这条规则有两个问题。第一,字符串 $a 太常见;第二,正因为 $a 太常见,#a 可能非常大,从而导致条件被评估成千上万次。

另一个条件也同样低效:迭代次数取决于文件大小,而文件大小也可能非常大:

root@kitploit:~
for all i in (1..filesize) : ($a at i)

Magic 模块

避免使用在 Windows 平台上不可用的 “magic” 模块。使用 “magic” 模块会减慢扫描速度,但能提供精确匹配。

自定义 GIF 魔数头定义:

root@kitploit:~
rule gif_1 {
  condition:
    (uint32be(0) == 0x47494638 and uint16be(4) == 0x3961) or
    (uint32be(0) == 0x47494638 and uint16be(4) == 0x3761)
}

使用 “magic” 模块:

root@kitploit:~
import "magic"
rule gif_2 {
  condition:
    magic.mime_type() == "image/gif"
}

过短的字符串

避免定义过短的字符串。任何少于 4 字节的字符串都很可能出现在大量文件中,或者作为异或(XOR)处理过的文件中的统一内容出现。

内容单一

有些字符串长度足够,但出于另一个原因——内容单一——不应使用。以下是一些不应使用的字符串示例,因为它们可能导致文件中出现过多匹配。

root@kitploit:~
$s1 = "22222222222222222222222222222222222222222222222222222222222222"
$s2 = "\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20"  // wide formatted spaces

错误消息看起来像这样:

root@kitploit:~
error scanning yara-killer.dat: string "$mz" in rule "shitty_mz" caused too many matches

字符串建议

尽量将字符串定义描述得尽可能精确。如有可能,尽量避免使用 “nocase” 属性,因为会生成并搜索大量原子(内存占用更高,迭代次数更多)。请记住,在没有修饰符的情况下,默认采用 “ascii”。可能的组合如下:

LOW(低) - 只生成一个原子

root@kitploit:~
$s1 = "cmd.exe"		       // (ascii only)
$s2 = "cmd.exe" ascii          // (ascii only, same as $s1)
$s3 = "cmd.exe" wide           // (UTF-16 only)
$s4 = "cmd.exe" ascii wide     // (both ascii and UTF-16) two atoms will be generated 
$s5 = { 63 6d 64 2e 65 78 65 } // ascii char code in hex

HIGH(高) - YARA 选定的 4 个字节的所有大小写字母组合都将生成为原子

root@kitploit:~
$s5 = "cmd.exe" nocase      (all different cases, e.g. "Cmd.", "cMd.", "cmD." ..)

如果你想匹配脚本命令,请在使用 nocase 之前确认该语言是否完全不区分大小写(例如 php、Windows 批处理)。如果只需要一两个字母的大小写变体,最好使用正则表达式,例如:

root@kitploit:~
$re = /[Pp]assword/

使用交替(alternation)时要小心,例如:

root@kitploit:~
$re = /(a|b)cde/
$hex = {C7 C3 00 (31 | 33)}

这些字符串会生成较短的原子,从而减慢扫描速度。当变体数量较少时,建议将字符串分开编写:

root@kitploit:~
$re1 = /acde/
$re2 = /bcde/
$hex1 = {C7 C3 00 31}
$hex2 = {C7 C3 00 33}

正则表达式

只在必要时使用正则表达式。正则表达式的求值本质上比普通字符串匹配慢,并且会消耗大量内存。如果带跳转和通配符的十六进制字符串就能解决问题,就不要使用它们。

如果必须使用正则表达式,请避免贪婪量词 .*,甚至也要避免懒惰量词 .*?。改用精确的数字,例如 .{1,30},甚至 .{1,3000}。另外,不要忘记上界(例如避免 .{2,})。

使用量词时,可能会发生两种情况:

如果正则表达式的开头锚定在一个位置,只有后缀可以变化,YARA 将匹配尽可能长的结果。对于 .*、.+ 或 .{2,} 这类情况,这可能导致很长的字符串,进而拖慢扫描速度。

如果正则表达式有更多可能的开头,YARA 将全部匹配。

root@kitploit:~
$re1 = /Tom.{0,2}/		// will find Tomxx in "Tomxx"
$re2 = /.{0,2}Tom/      // will find Tom, xTom, xxTom in "xxTom"

较短匹配的数量很容易超过限制,从而产生 “too many matches”(匹配过多)错误。

下面的示例是电子邮箱地址的正则表达式。当 [-a-z0-9._%+] 与量词一起使用时,YARA 会对同一个地址进行多次匹配,这并不理想。在这种情况下,建议找到一个小而合理的地址子集,以提供足够的信息供分析使用。

推荐

root@kitploit:~
/[-a-z0-9._%+]@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
OR
/@[-a-z0-9.]{2,10}\.[a-z]{2,4}/ 

避免

root@kitploit:~
/[-a-z0-9._%+]*@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
/[-a-z0-9._%+]+@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
/[-a-z0-9._%+]{x,y}@[-a-z0-9.]{2,10}\.[a-z]{2,4}/

如果你想确保 exec 后面跟着 /bin/sh,可以使用 @ 符号提供的偏移量。这是较慢的正则表达式版本:

root@kitploit:~
$ = /exec.*\/bin\/sh/

这是更快的偏移量方式:

root@kitploit:~
strings:
  $exec = "exec" 
  $sh   = "/bin/sh"
conditions:
  $exec and $sh and
  @exec < @sh

另外,尽量在匹配过程中加入可作为锚点的长字符串序列。再说一次,越长越好。

差

root@kitploit:~
$s1 = /http:\/\/[.]*\.hta/	// greedy [.]*

更好

root@kitploit:~
$s1 = /http:\/\/[a-z0-9\.\/]{3,70}\.hta/ 	// better, with an the upper bound

最佳

root@kitploit:~
$s1 = /mshta\.exe http:\/\/[a-z0-9\.\/]{3,70}\.hta/

匹配过多与扫描变慢错误

“too many matches”(匹配过多)错误是由过于通用的字符串引起的,这些字符串在输入中出现得过于频繁,或者 YARA 对同一个实例进行了多次匹配。

扫描变慢(slowing down scanning)是由生成过短原子或根本不生成原子的字符串引起的。其结果是 YARA 退而使用朴素的模式匹配算法,从而导致扫描变慢。

在某些情况下,这两个问题都可以通过以下步骤修复:

  1. 检查量词 .*、.+、.*?
  2. 检查没有上界的量词,例如 x{14,}
  3. 检查过大的范围(例如 x{1,300000})
  4. 检查十六进制字符串中的大跳转
  5. 检查通配符——能否更精确地指定它们,或者能否将字符串拆分为 2 个从而省略通配符?
  6. 检查交替分支——能否拆分为 2 个或更多字符串?
  7. 尝试为单词匹配添加限定(fullword、\b 等)

请注意,在下一章条件与短路求值中,会提到一些关于条件的技巧。然而,修改这些技巧并不能解决 “too many matches” 和 “slowing down scanning” 错误。

条件与短路求值

在编写条件语句时,尽量把最可能为 “False” 的元素放在最前面。条件从左到右进行求值。引擎越早确定规则不满足,就能越早跳过当前规则并评估下一条规则。这种排序方式带来的速度提升取决于处理每条语句所需的 CPU 周期差异。如果所有语句的开销大致相同,重新排序不会带来明显的改善。如果某条语句可以非常快地处理完,建议把它放在最前面,以便在第一条语句为 FALSE 时跳过开销大的语句评估。

改变下面这条语句中的顺序不会带来显著的改善:

root@kitploit:~
$string1 and $string2 and uint16(0) == 0x5A4D

然而,如果各语句的执行时间差异很大,通过重新排序来触发短路将显著提高扫描速度:

慢(SLOW)

root@kitploit:~
// EXPENSIVE and CHEAP
math.entropy(0, filesize) > 7.0 and uint16(0) == 0x5A4D

快(FAST)

root@kitploit:~
// CHEAP and EXPENSIVE
uint16(0) == 0x5A4D and math.entropy(0, filesize) > 7.0

短路求值的引入是为了帮助优化开销大的语句,尤其是 “for” 语句。有些人曾使用过如下示例中的条件:

root@kitploit:~
strings:
	$mz = "MZ"
	...
condition:
	$mz at 0 and for all i in (1..filesize) : ( whatever )

由于 filesize 可能是一个非常大的数字,“whatever” 可能会被执行很多次,从而拖慢执行速度。现在,借助短路求值,“for” 语句只会在条件的第一部分满足时执行,因此这条规则只会在 MZ 文件上变慢。还可以做进一步的改进:

root@kitploit:~
$mz at 0 and filesize < 100KB and for all i in (1..filesize) : ( whatever )

这样就为迭代次数设置了一个更高的上限。

从版本 3.10 开始,整数范围循环也得到了优化:

root@kitploit:~
for all i in (0..100): (false)
for any i in (0..100): (true)

Both of these loops will stop iterating after the first time through.

正则表达式不支持短路

遗憾的是,这对正则表达式并不适用,因为所有正则表达式最初都会被送入字符串匹配引擎。下面的示例会拖慢对所有文件的搜索速度,而不仅仅是那些文件大小小于 200 字节的文件:

root@kitploit:~
strings:
  $expensive_regex = /\$[a-z0-9_]+\(/ nocase
conditions:
  filesize < 200 and
  $expensive_regex

这种 “短路” 求值自 YARA 3.4 版本起开始应用。

元数据

YARA 会将元数据部分中的任何数据读入内存。(你可以很容易地测试这一点:向一条规则中插入 100,000 个哈希,然后检查 YARA 扫描前后的内存使用情况。)当然,你不会想永久性地从规则中删除元数据,但如果内存紧张,可以在工作流程中紧接扫描之前删除其中一些不需要的部分。

下载工具