Neo23x0 - YARA-Performance-Guidelines

简单粗暴的一键机翻+纠正明显的机翻问题

性能的优化应该是在YARA规则本身成立条件和兼容性都保证的前提下考虑的。 如果加上工作交接和日常对规则准确度的情况,还需要考虑可读性,甚至一定的信息量。人类的高可读性在人力缺乏的情况下,应该优于性能

认同:短路逻辑,字符串原子来源的普遍性普通性 可读性比较差:正则

在为YARA创建规则时,请记住以下指导原则,以便从它们中获得最佳性能。本指南以Victor M.Alvarez和WXS的想法和建议为基础。

  • 修订版1.5(2021年2月),适用于所有高于3.7的YARA版本

    基础

    为了更好地掌握YARA性能可以优化的内容和位置,了解扫描过程是非常有用的。它基本上分为4个步骤,使用下面的示例规则对这些步骤进行非常简化的解释:

    1. import "math"
    2. rule example_php_webshell_rule
    3. {
    4. meta:
    5. description = "Just an example php webshell rule"
    6. date = "2021/02/16"
    7. strings:
    8. $php_tag = "<?php"
    9. $input1 = "GET"
    10. $input2 = "POST"
    11. $payload = /assert[\t ]{0,100}\(/
    12. condition:
    13. filesize < 20KB and
    14. $php_tag and
    15. $payload and
    16. any of ( $input* ) and
    17. math.entropy(500, filesize-500) >= 5
    18. }

    1.编写规则

    此步骤发生在实际扫描之前。YARA会寻找所谓的原子s在搜索字符串to feed the Aho-Corasick automaton,详细情况将在本章中解释。
    但是现在只需知道,原子最大长度为4字节,而YARA非常聪明地挑选它们以避免过多的匹配。在我们的示例中,YARA可能选择以下4个原子:

  • <?ph

  • GET
  • POST
  • sser (out of assert)

    2.AC自动机

    扫描开始了。步骤2.-4.将在所有文件上执行。YARA将在每个文件中查找上面定义的4个原子,前缀树称为AC自动机。任何匹配都会传递给字节码引擎。

    3.字节码引擎

    如果有匹配sser,YARA将检查它是否以a为前缀,并继续使用t,它将继续使用regex([\t ]{0,100}()。
    通过这种巧妙的方法,YARA避免了对完整文件使用缓慢的regex引擎,只需选择某些部分来查看。

    4.条件

    在完成所有模式匹配之后,将检查条件。YARA还有另一种优化机制,只对CPU进行密集处理。
    math.entropy从我们的示例规则中检查,它之前的4个条件是否满足。在本章的更多细节中进行了解释。条件与短路评估
    如果满足条件,则报告匹配。扫描继续进行步骤2中的下一个文件。

    原子

    YARA从字符串中提取最多4字节长的短子串,称为“原子”。这些原子可以从字符串中的任意位置提取,而YARA在扫描文件时搜索这些原子,如果它找到其中一个原子,那么它就验证字符串是否真的匹配。
    例如,考虑以下字符串:

    /abc.*cde/

    可能的原子是abc和cde,任何一个或另一个都可以使用abc原子目前是首选的,因为它们具有相同的质量,这是两者中的第一个。

    /(one|two)three/

    可能的原子是one,two,thre和hree,我们可以单独搜索thre(或hree),或两者兼而有之one和two。
    原子thre是首选的,因为这样会减少潜在的匹配。one和two(它们比较短)并且不包含双倍e(字母越独特越好)。
    YARA尽了最大的努力从每个字符串中选择最好的原子。例如:

    { 00 00 00 00 [1-4] 01 02 03 04 }

    这里YARA使用原子01 02 03 04,因为00 00 00 00太普通了

    { 01 02 [1-4] 01 02 03 04 }

    01 02 03 04优先于01 02因为它更长
    因此,重要的一点是字符串应该包含好的原子。这些字符串不是很好,因为它们包含的原子要么太短,要么太普通

    1. {00 00 00 00 [1-2] FF FF [1-2] 00 00 00 00}
    2. {AB [1-2] 03 21 [1-2] 01 02}
    3. /a.*b/
    4. /a(c|d)/

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

    1. /\w.*\d/
    2. /[0-9]+\n/

    此正则表达式不包含任何可用作原子的固定子字符串,因此必须在文件的每个偏移量处对其进行求值,以确定其是否匹配。

    太多循环迭代

    另一个很好的建议是避免环由于迭代次数太多,特别是循环中的语句太复杂,例如:

    1. strings:
    2. $a = {00 00}
    3. condition:
    4. for all i in (1..#a) : (@a[i] < 10000)

    这个规则有两个问题。第一种是字符串$a太常见,第二种是因为$a太常见,#a可能太高,可以被计算数千次。
    另一种情况也是低效的,因为迭代的次数取决于文件大小,而文件大小也可能非常高:

    1. for all i in (1..filesize) : ($a at i)

    Magic模块

    避免使用“Magic”模块它在Windows平台上不可用。使用“Magic”模块可以减慢扫描速度,但提供精确的匹配。
    自定义GIF魔数:

    1. rule gif_1
    2. {
    3. condition:
    4. (uint32be(0) == 0x47494638 and uint16be(4) == 0x3961) or
    5. (uint32be(0) == 0x47494638 and uint16be(4) == 0x3761)
    6. }

    避免使用“Magic”模块,(但是它真的好用好写):

    1. import "magic"
    2. rule gif_2
    3. {
    4. condition:
    5. magic.mime_type() == "image/gif"
    6. }

    字符串

    避免定义太短的字符串。
    任何小于4个字节的字符串都可能出现在许多文件中,或者作为XORed文件中的统一内容出现。

    同一内容

    有些字符串足够长,但由于不同的原因——一致性,不应该使用。
    以下是一些不应该使用的字符串示例,因为它们可能导致文件中的匹配过多:

    1. $s1 = "22222222222222222222222222222222222222222222222222222222222222"
    2. $s2 = "\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20\x00\x20" // wide formatted spaces

    错误消息如下所示:

    1. error scanning yara-killer.dat: string "$mz" in rule "shitty_mz" caused too many matches

    字符串指南

    尝试描述尽可能窄的字符串定义。如果可能的话,避免使用“nocase”属性,因为会生成和搜索许多原子(内存使用量更高,迭代次数更多)。请记住,在没有修饰符的情况下,默认假定为“ascii”。
    可能的组合是:

  • 低层-只有一个原子生成:

    1. $s1 = "cmd.exe" // (ascii only)
    2. $s2 = "cmd.exe" ascii // (ascii only, same as $s1)
    3. $s3 = "cmd.exe" wide // (UTF-16 only)
    4. $s4 = "cmd.exe" ascii wide // (both ascii and UTF-16) two atoms will be generated
    5. $s5 = { 63 6d 64 2e 65 78 65 } // ascii char code in hex
  • -YARA选择的4个字节的大小写字母的所有组合都将生成原子

    1. $s5 = "cmd.exe" nocase (all different cases, e.g. "Cmd.", "cMd.", "cmD." ..)

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

    1. $re = /[Pp]assword/

    在进行替换时要小心,例如:

    1. $re = /(a|b)cde/
    2. $hex = {C7 C3 00 (31 | 33)}

    这些字符串产生短原子,可以减慢扫描速度。
    在有少量变体的情况下,是否建议将字符串分别写入:

    1. $re1 = /acde/
    2. $re2 = /bcde/
    3. $hex1 = {C7 C3 00 31}
    4. $hex2 = {C7 C3 00 33}

    正则表达式

    只在必要时使用表达式。正则表达式计算值本身比普通字符串匹配速度慢,并且会使用大量内存。不要使用他们,如果十六进制字符串与跳跃和野生卡可以解决问题。
    如果你必须使用正则表达式,避免贪婪。*甚至是懒惰的量词。相反,使用确切的数字,如:.{1,30}或偶数.{1,3000}。另外,不要忘记上界(例如:{2,})。
    当我们使用量词时,可能会发生两种情况:

  • 如果正则表达式的开头锚定在一个位置上,并且唯一的后缀可以更改,YARA将匹配。最长的可能匹配。在某些情况下.*和.+或.{2,},这可能会导致大字符串和慢扫描问题。

  • 如果正则表达式有更多的开始,YARA将匹配所有的:
    1. $re1 = /Tom.{0,2}/ // will find Tomxx in "Tomxx"
    2. $re2 = /.{0,2}Tom/ // will find Tom, xTom, xxTom in "xxTom"
    较短的匹配数可以很容易地越过限制并创建“太多的匹配”错误。
    下面的示例是电子邮件地址的正则表达式。使用时[-a-z0-9._%+]使用量词,YARA将多次匹配一个地址,这并不理想。在这种情况下,建议找到一个相当小的地址子集,为分析提供足够的信息。
    使用:
    1. /[-a-z0-9._%+]@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
    2. OR
    3. /@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
    避免:
    1. /[-a-z0-9._%+]*@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
    2. /[-a-z0-9._%+]+@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
    3. /[-a-z0-9._%+]{x,y}@[-a-z0-9.]{2,10}\.[a-z]{2,4}/
    如果你想确定,那就是“exec”后面跟着“/bin/sh”,您可以使用@象征。这将是慢正则表达式:
    1. $ = /exec.*\/bin\/sh/
    这是一种更快的抵消方式:
    1. strings:
    2. $exec = "exec"
    3. $sh = "/bin/sh"
    4. conditions:
    5. $exec and $sh and
    6. @exec < @sh
    还尝试包含长字符串序列,这些字符串可以作为匹配过程中的锚。再说一遍,时间越长越好。
    坏的:
    1. $s1 = /http:\/\/[.]*\.hta/ // greedy [.]*
    更好:
    1. $s1 = /http:\/\/[a-z0-9\.\/]{3,70}\.hta/ // better, with an the upper bound
    最佳:
    1. $s1 = /mshta\.exe http:\/\/[a-z0-9\.\/]{3,70}\.hta/

    匹配量过大和扫描错误减缓

    太多的匹配错误是由于输入中出现的字符串太多而导致的,或者YARA多次匹配一个实例。
    缓慢的扫描是由产生太短原子的字符串引起的,或者根本就没有。因此,YARA使用了一种天真的模式匹配算法,这导致了速度的放缓。
    在某些情况下,这两个问题都可以通过以下步骤加以解决:
  1. 检查量词.和.+, .?
  2. 检查没有上限的量词,例如x{14,}
  3. 检查范围过大(例如,{1,300000})
  4. 检查十六进制字符串中的大跳转
  5. 检查外卡字符-它们是否可以更精确地指定,或者可以被字符串拆分为2,忽略了外卡字符?
  6. 检查替换:可以拆分成2个或更多字符串吗?
  7. 尝试添加单词匹配规范(FullWord,\b,.)

注:下一章条件与短路评估,这里提到了一些关于条件的提示。然而,它们的变化不会解决太多的匹配和减缓扫描错误。

条件与短路评估

尝试编写条件语句,其中最有可能是“false”的元素放在第一位
从左到右计算条件,引擎越早识别规则不满足,就越早跳过当前规则并计算下一个规则。
通过这种方式对条件语句进行排序所导致的速度改进取决于处理每个语句所需的CPU周期的不同。如果所有语句或多或少都是同样昂贵的,那么重新排序将不会带来明显的改进。如果其中一条语句处理得非常快,则建议将其放在第一条语句之前,以便在第一条语句为假的情况下跳过昂贵的语句计算。
更改以下语句中的顺序不会带来重大改进:

  1. $string1 and $string2 and uint16(0) == 0x5A4D

但是,如果语句的执行时间有很大不同,重新排序以触发短路将显著提高扫描速度:
慢:

  1. EXPENSIVE and CHEAP
  2. math.entropy(0, filesize) > 7.0 and uint16(0) == 0x5A4D

快:

  1. CHEAP and EXPENSIVE
  2. uint16(0) == 0x5A4D and math.entropy(0, filesize) > 7.0

短路评估被引入,以帮助优化昂贵的句子,特别是“for”的句子。有些人使用的条件如下所示:

  1. strings:
  2. $mz = "MZ"
  3. ...
  4. condition:
  5. $mz at 0 and for all i in (1..filesize) : ( whatever )

因为文件大小可能是一个很大的数字,所以“whatever”都可以执行很多次,从而减慢了执行速度。
现在,通过短路评估,只有在满足条件的第一部分时,才会执行“for”语句,因此,这条规则只适用于mz文件。
另一项改进是:

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

这样就设置了迭代次数的更高界。
在3.10版本中,整数范围循环也进行了优化:

  1. for all i in (0..100): (false)
  2. for any i in (0..100): (true)
  3. Both of these loops will stop iterating after the first time through.

正则表达式没有短路

遗憾的是,这并不适用于正则表达式,因为它们最初都被输入到字符串匹配引擎中。下面的示例将减缓对任何文件的搜索,而不仅仅是对文件大小小于200个字节的文件的搜索:

  1. strings:
  2. $expensive_regex = /\$[a-z0-9_]+\(/ nocase
  3. conditions:
  4. filesize < 200 and
  5. $expensive_regex

这个“短路”评估是从YARA版本3.4开始应用的。

元数据

元数据部分中的任何数据都由YARA读入RAM。(您可以通过在规则中插入100,000个哈希并检查YARA扫描前后的RAM使用情况来轻松测试这一点。)
当然,您不希望永久地从规则中删除元数据,但是如果缺少RAM,则可以在扫描之前直接删除工作流中一些不必要的部分。