条件只不过是所有编程语言中都可以找到的布尔表达式,例如 if 语句,它们可以包含典型的布尔运算符 and、or 和 not 以及关系运算符 >=、<=、<、>、== 和 !=。此外,算术运算符 (+、-、*、\、%) 和按位运算符 (&、|、<<、>>、~、^) 可以是用于数字表达式。
整数始终是 64 位长,即使 uint8、uint16 和 uint32 等函数的结果也会提升为 64 位。这是必须考虑的事情,特别是在使用按位运算符时 (例如,~0x01 不是 0xFE,而是 0xFFFFFFFFFFFFFFFE)。
下表列出了所有运算符的优先级和结合性,该表按优先级降序排序,这意味着列表中较高行中列出的运算符将被分组到其下方行中列出的前面的运算符中。同一行中的运算符具有相同的优先级,如果它们一起出现在表达式中,则关联性决定它们的分组方式。
| 优先级 | 操作符 | 描述 | 关联性 |
|---|---|---|---|
| 1 | []. | 数组下标 结构成员访问 | 从左到右 |
| 2 | -~ | 负 按位非 | 从右到左 |
| 3 | *\% | 乘 除 余 | 从左到右 |
| 4 | +- | 加 减 | 从左到右 |
| 5 | <<>> | 按位左移 按位右移 | 从左到右 |
| 6 | & | 按位与 | 从左到右 |
| 7 | ^ | 按位异或 | 从左到右 |
| 8 | | | 按位或 | 从左到右 |
| 9 | <<=>>= | 小于 小于或等于 大于 大于或等于 | 从左到右 |
| 10 | ==!=containsicontainsstartwithistartwithendwithiendswithiequalsmatches | 等于 不等于 包含子字符串 跟上面相似但不区分大小写 以子符串开头 与上面相似但不区分大小写 以子符串结尾 跟上面相似但不区分大小写 不区分大小写的字符串比较 字符串与正则表达式匹配 | 从左到右 |
| 11 | not defined | 逻辑 NOT 检查表达式是否已定义 | 从右到左 |
| 12 | and | 逻辑与 | 从左到右 |
| 13 | or | 逻辑或 | 从左到右 |
字符串标识符也可以在条件中使用,充当布尔变量,其值取决于文件中关联字符串是否存在。
rule Example
{
strings:
$a = "text1"
$b = "text2"
$c = "text3"
$d = "text4"
condition:
($a or $b) and ($c or $d)
}计算字符串
有时我们不仅需要知道某个字符串是否存在,还需要知道该字符串在文件或进程内存中出现了多少次。每个字符串出现的次数由一个变量表示,该变量的名称是字符串标识符,但用 # 字符代替 $ 字符,例如:
rule CountExample
{
strings:
$a = "dummy1"
$b = "dummy2"
condition:
#a == 6 and #b > 10
}此规则匹配包含字符串 $a 的文件或进程正好六次以及字符串 $b 的出现次数超过十次。
从 Yara 4.2.0 开始,可以表达整数范围内字符串的计数,如下所示:
#a in (filesize-500..filesize) == 2在此示例中,文件最后 500 个字节中 "a" 字符串的数量必须恰好等于 2。
字符串偏移或虚拟地址
在大多数情况下,当在条件中使用字符串标识符时,我们愿意知道关联的字符串是否位于文件或进程内存中的位置,但有时需要知道该字符串是否位于文件或进程内存中的某个特定偏移处,文件或进程地址空间内的某个虚拟地址,在这种情况下,需要运算符 at,该运算符的用法如下例所示:
rule AtExample
{
strings:
$a = "dummy1"
$b = "dummy2"
condition:
$a at 100 and $b at 200
}仅当在文件内的偏移量 100 处 (或者如果应用于正在运行的进程,则在虚拟地址 100 处) 找到字符串 $a 时,上例中的表达式 $a at 100 才为 true。字符串 $b 应该出现在偏移量 200 处。请注意,两个偏移量都是十进制的,但是可以像 C 语言一样通过在数字前添加前缀 0x 来编写十六进制数字,这在编写虚拟地址时非常方便。另请注意运算符 at 的优先级高于 and。
at 运算符允许在文件中某个固定偏移量或进程内存空间中的虚拟地址处搜索字符串,而 in 运算符允许在一定范围内搜索字符串偏移量或地址。
rule InExample
{
strings:
$a = "dummy1"
$b = "dummy2"
condition:
$a in (0..100) and $b in (100..filesize)
}在上面的示例中,字符串 $a 必须位于 0 到 100 之间的偏移量处,而字符串 $b 必须位于 100 到文件末尾之间的偏移量处,同样,默认情况下数字是十进制的。
还可以使用 @a[i] 获取字符串 $a 第 i 次出现的偏移量或虚拟地址。索引是基于 1 的,因此第一次出现是 @a[1],第二次出现是 @a[2],依此类推。如果提供的索引大于字符串出现的次数,则结果将为 NaN 值。
匹配长度
对于许多包含跳转的正则表达式和十六进制字符串,匹配的长度是可变的。如果有正则表达式 /fo*/,则字符串 "fo"、"foo" 和 "fooo" 可以匹配,它们都具有不同的长度。
可以使用字符 ! 来将匹配的长度作为条件的一部分,在字符串标识符前面,以类似的方式使用 @ 字符作为偏移量。!a[1] 是 $a 第一个匹配的长度,!a[2] 是第二个匹配的长度,依此类推。 !a 是 !a[1] 的缩写形式。
文件大小
字符串标识符并不是唯一可以出现在条件中的变量 (事实上可以在没有任何字符串定义的情况下定义规则),还可以使用其他特殊变量。这些特殊变量之一是 filesize,正如其名称所示,它保存正在扫描的文件的大小,大小以字节表示。
rule FileSizeExample
{
condition:
filesize > 200KB
}前面的示例还演示了 KB 后缀的使用,当该后缀附加到数字常量时,会自动将该常量的值乘以 1024。MB 后缀可用于将该值乘以 2 ^ 20,两个后缀只能与十进制常量一起使用。
仅当规则应用于文件时,使用 filesize 才有意义。如果规则应用于正在运行的进程,它将永远不会匹配,因为 filesize 在此上下文中没有意义。
入口点
可以在规则中使用的另一个特殊变量是 entrypoint,如果文件是可移植可执行文件 (PE) 或可执行可链接格式 (ELF),则此变量保存可执行文件入口点的原始偏移量。如果我们正在扫描正在运行的进程,入口点将保存可执行文件入口点的虚拟地址,此变量的典型用途是在入口点查找某种模式以检测加壳程序或简单文件感染程序。
rule EntryPointExample1
{
strings:
$a = { E8 00 00 00 00 }
condition:
$a at entrypoint
}
rule EntryPointExample2
{
strings:
$a = { 9C 50 66 A1 ?? ?? ?? 00 66 A9 ?? ?? 58 0F 85 }
condition:
$a in (entrypoint..entrypoint + 10)
}规则中 entrypoint 变量的存在意味着只有 PE 或 ELF 文件可以满足该规则,如果文件不是 PE 或 ELF,则任何使用此变量的规则都会计算为 false。
DANGER
entrypoint 变量已弃用,应该使用 PE 模块中的 pe.entry_point 来代替。从 Yara 3.0 开始,如果使用 entrypoint 将收到警告,并且将在未来版本中完全删除。
访问给定位置的数据
在很多情况下,可能需要编写依赖于存储在某个文件偏移或虚拟内存地址的数据的条件,具体取决于是否正在扫描文件或正在运行的进程。在这些情况下可以使用以下函数之一从给定偏移处的文件中读取数据:
int8(<offset or virtual address>)
int16(<offset or virtual address>)
int32(<offset or virtual address>)
uint8(<offset or virtual address>)
uint16(<offset or virtual address>)
uint32(<offset or virtual address>)
int8be(<offset or virtual address>)
int16be(<offset or virtual address>)
int32be(<offset or virtual address>)
uint8be(<offset or virtual address>)
uint16be(<offset or virtual address>)
uint32be(<offset or virtual address>)intXX 函数从偏移或虚拟地址读取 8、16 和 32 位有符号整数,而函数 uintXX 读取无符号整数。16 位和 32 位整数均被视为小端整数。如果想读取大端整数,请使用以 be 结尾的相应函数。参数可以是返回无符号整数的任何表达式,包括 uintXX 函数本身的返回值。作为例子,看一下区分 PE 文件的规则:
rule IsPE
{
condition:
// MZ signature at offset 0 and ...
uint16(0) == 0x5A4D and
// ... PE signature at offset stored in MZ header at 0x3C
uint32(uint32(0x3C)) == 0x00004550
}字符串集合
在某些情况下,有必要表示文件应包含给定集合中的某些数字字符串。集合中的任何字符串都不需要存在,但至少其中一些应该存在。在这些情况下可以使用 of 运算符。
rule OfExample1
{
strings:
$a = "dummy1"
$b = "dummy2"
$c = "dummy3"
condition:
2 of ($a,$b,$c)
}此规则要求文件中必须存在集合 ($a,$b,$c) 中的至少两个字符串,但哪两个并不重要。当然,使用该运算符时,of 关键字之前的数量必须小于或等于集合中字符串的数量。
该集合的元素可以像前面的示例一样显式枚举,也可以使用通配符指定。例如:
rule OfExample2
{
strings:
$foo1 = "foo1"
$foo2 = "foo2"
$foo3 = "foo3"
condition:
2 of ($foo*) // equivalent to 2 of ($foo1,$foo2,$foo3)
}
rule OfExample3
{
strings:
$foo1 = "foo1"
$foo2 = "foo2"
$bar1 = "bar1"
$bar2 = "bar2"
condition:
3 of ($foo*,$bar1,$bar2)
}甚至可以使用 ($*) 来引用规则中的所有字符串,或者编写等效的关键字 them 以提高易读性。
rule OfExample4
{
strings:
$a = "dummy1"
$b = "dummy2"
$c = "dummy3"
condition:
1 of them // equivalent to 1 of ($*)
}在上面的所有示例中,字符串的数量均由数值常量指定,但可以使用任何返回数值的表达式。也可以使用关键字 any、all 和 none。
all of them // all strings in the rule
any of them // any string in the rule
all of ($a*) // all strings whose identifier starts by $a
any of ($a,$b,$c) // any of $a, $b or $c
1 of ($*) // same that "any of them"
none of ($b*) // zero of the set of strings that start with "$b"DANGER
由于 Yara 内部的工作方式,"0 of them" 是语言中含糊不清的部分,应避免使用,取而代之的是 "none of them"。要理解这一点,请考虑 "2 of them" 的含义,如果 2 个或更多字符串匹配,则为 true。从过去上看,"0 of them" 遵循这一原则,如果至少有一个字符串匹配,则结果为 true。在 Yara 4.3.0 中,如果恰好有 0 个字符串匹配,则通过使 "0 of them" 计算为 true 来解决这种歧义。为了改善这种情况并明确意图,鼓励使用 "none" 代替 0,这样可以更容易地推理出 "none of them" 的含义。
从 Yara 4.2.0 开始,可以在整数范围内表达一组字符串,如下所示:
all of ($a*) in (filesize-500..filesize)
any of ($a*, $b*) in (1000..2000)从 Yara 4.3.0 开始,可以在特定偏移处表达一组字符串,如下所示:
any of ($a*) at 0对许多字符串应用相同匹配条件
还有另一个运算符与 of 非常相似,但功能更强大,即 for..of 运算符,语法是:
for expression of string_set : ( boolean_expression )其含义是:string_set 中的字符串中至少有 expression 必须满足 boolean_expression。
换句话说:对 string_set 中的每个字符串进行 boolean_expression 计算,并且必须至少有 expression 返回 True。
当然,boolean_expression 可以是规则的条件部分接受的任何布尔表达式,除了一个重要的细节:这里可以使用美元符号 ($) 作为占位符正在给字符串作判断,看看下面的表达式:
for any of ($a,$b,$c) : ( $ at pe.entry_point )布尔表达式中的 $ 符号不绑定到任何特定字符串,在表达式的三个连续计算中,它将是 $a,然后是 $b,然后是 $c。
也许你已经意识到 of 运算符是 for..of 的特殊情况,以下表达式是相同的:
any of ($a,$b,$c)
for any of ($a,$b,$c) : ( $ )还可以使用符号 #、@ 和 ! 分别代表每个字符串的出现次数、第一个偏移量和长度。
for all of them : ( # > 3 )
for all of ($a*) : ( @ > @b )从 Yara 4.3.0 开始,可以通过文本字符串表达条件,如下所示:
for any s in ("71b36345516e076a0663e0bea97759e4", "1e7f7edeb06de02f2c2a9319de99e033") : ( pe.imphash() == s )这里值得记住的是,规则中引用的两个哈希是普通文本字符串,与规则的字符串部分无关。在循环条件内,pe.impash() 函数的结果与每个文本字符串进行比较,从而产生更简洁的规则。
将匿名字符串与 of 和 for..of 一起使用
当使用 of 和 for..of 运算符后跟 them 时,分配给规则的每个字符串的标识符通常是多余的。由于没有单独引用任何字符串,因此不需要为每个字符串提供唯一的标识符。在这些情况下,可以声明带有仅由 $ 字符组成的标识符的匿名字符串,如下例所示:
rule AnonymousStrings
{
strings:
$ = "dummy1"
$ = "dummy2"
condition:
1 of them
}迭代字符串出现的次数
如字符串偏移量或虚拟地址中所示,给定字符串出现在文件或进程地址空间中的偏移量或虚拟地址可以使用语法 @a[i] 进行访问,其中 i 是字符串出现的索引,表示你所指的字符串 $a 的哪一个出现 (@a[1],@a[2],...)。
有时需要迭代其中一些偏移量并保证它们满足给定条件,在这种情况下可以使用 for..in,例如:
rule Occurrences
{
strings:
$a = "dummy1"
$b = "dummy2"
condition:
for all i in (1,2,3) : ( @a[i] + 10 == @b[i] )
}前面的规则规定,$b 的第一次出现应该在 $a 第一次出现之后 10 个字节,并且两个字符串的第二次和第三次出现也应该发生同样的情况。
相同的条件也可以写成:
for all i in (1..3) : ( @a[i] + 10 == @b[i] )请注意,使用范围 (1..3),而不是枚举索引值 (1,2,3)。当然不必强制使用常量来指定范围边界,也可以使用表达式,如下例所示:
for all i in (1..#a) : ( @a[i] < 100 )在本例中,迭代 $a 的每次出现 (请记住 #a 代表 $a 出现的次数),此规则指定 $a 的每次出现都应位于文件的前 100 个字节内。
如果想表达只有某些字符串出现才能满足条件,则 for..of 运算符中的相同逻辑适用于此处:
for any i in (1..#a) : ( @a[i] < 100 )
for 2 i in (1..#a) : ( @a[i] < 100 )for..in 运算符与 for..of 类似,但后者迭代一组字符串,而前者迭代范围、枚举、数组和字典。
迭代器
在 Yara 4.0 中,for..in 运算符得到了改进,现在它不仅可以用于迭代整数枚举和范围 (例如:1,2,3,4 和 1..4),还可以用于迭代任何一种可迭代的数据类型,如 Yara 模块定义的数组和字典。例如,以下表达式在 Yara 4.0 中有效:
for any section in pe.sections : ( section.name == ".text" )相当于
for any i in (0..pe.number_of_sections-1) : ( pe.sections[i].name == ".text" )新语法更加自然且易于理解,是在较新版本的 Yara 中表达此类条件的推荐方式。
迭代字典时,必须提供两个变量名称来保存字典中每个条目的键和值,例如:
for any k,v in some_dict : ( k == "foo" and v == "bar" )一般来说,for..in 运算符的形式如下:
for <quantifier> <variables> in <iterable> : ( <some condition using the loop variables> )其中 是任意、全部或一个表达式,其计算结果为迭代器中必须满足条件的项目数, 是一个以逗号分隔的变量名称列表,用于保存当前项目的值(变量的数量取决于 的类型),并且 是可以迭代的东西。
其中 quantifier 是任意、全部或计算为迭代器中必须满足条件的项数的表达式,variables 是一个逗号分隔的变量名列表,其中包含当前项的值 (变量数取决于 iterable 的类型),iteraable 是可迭代的。
引用其它规则
在编写规则的条件时,还可以以类似于传统编程语言的函数调用的方式引用先前定义的规则。通过这种方式,可以创建依赖于其他规则的规则,来看一个例子:
rule Rule1
{
strings:
$a = "dummy1"
condition:
$a
}
rule Rule2
{
strings:
$a = "dummy2"
condition:
$a and Rule1
}从示例中可以看出,只有当文件包含字符串 "dummy2" 并满足 Rule1 时,它才会满足 Rule2。请注意,必须在调用之前定义好要调用的规则。
4.2.0 中引入了另一种引用其他规则的方法,即规则集,其操作方式与字符串集类似 (请参阅字符串集合),例如:
rule Rule1
{
strings:
$a = "dummy1"
condition:
$a
}
rule Rule2
{
strings:
$a = "dummy2"
condition:
$a
}
rule MainRule
{
strings:
$a = "dummy2"
condition:
any of (Rule*)
}此示例演示如何使用规则集以随规则自动增长的方式描述高阶逻辑,如果在 MainRule 之前定义另一个名为 Rule3 的规则,那么它将自动包含在 MainRule 条件中 Rule* 的扩展中。
要使用规则集,包含的所有规则都必须在使用规则集之前存在。例如,以下内容将产生编译器错误,因为 a2 是在 x 中使用规则集之后定义的:
rule a1 { condition: true }
rule x { condition: 1 of (a*) }
rule a2 { condition: true }