Skip to main content

Conditions

YARA-X is a Rust reimplementation of YARA, the pattern-matching tool used to identify and classify malware. Every rule ends with a condition section, and it is the only body section that is always required. The condition is a single boolean expression: when it evaluates to true, the scanned data matches the rule.

Most conditions reference the patterns declared in the strings section. In a condition, a pattern identifier such as $a acts as a boolean variable that is true when the pattern was found in the scanned data.

rule Example {
strings:
$a = "text1"
$b = "text2"
$c = "text3"
$d = "text4"
condition:
($a or $b) and ($c or $d)
}
note

The condition is a boolean expression, but the values it combines do not have to be patterns. They can be integers, comparisons, function calls, or data exposed by modules. The only requirement is that the whole expression resolves to true or false.

Boolean and arithmetic operators

YARA-X supports the usual families of operators inside conditions.

CategoryOperators
Booleanand, or, not
Comparison==, !=, <, <=, >, >=
Arithmetic+, -, *, \, %
Bitwise&, |, ^, ~, <<, >>

These combine freely. For example, you can compare an arithmetic expression against a counter, or AND together several pattern checks.

Counting matches with #

A pattern can match more than once. Prefixing the identifier with # instead of $ gives the number of matches as an integer.

rule CountExample {
strings:
$a = "dummy1"
$b = "dummy2"
condition:
#a == 6 and #b > 10
}

You can also restrict the count to a region of the file by combining # with the in operator and an offset range:

rule CountInRange {
strings:
$a = "dummy1"
condition:
#a in (filesize - 500..filesize) == 2
}

Match offsets with @

The @ prefix yields the offset where a pattern matched. Because a pattern can match multiple times, @a is indexed, and the index is one-based: @a[1] is the first match, @a[2] the second, and so on.

rule AtExample {
strings:
$a = "dummy1"
condition:
$a at 100 and @a[1] == 100
}

Two related forms make offset checks more readable:

  • $a at <offset> is true when $a matches at exactly that offset.
  • $a in (<start>..<end>) is true when $a matches anywhere within the inclusive offset range.
rule InExample {
strings:
$a = "dummy1"
$b = "dummy2"
condition:
$a in (0..100) and $b in (100..filesize)
}

Match lengths with !

The ! prefix returns the length of a match in bytes. Like @, it is indexed one-based, so !a[1] is the length of the first match. This is mainly useful with regular expressions and hex patterns containing jumps, where different matches can have different lengths.

tip

!a without an index is shorthand for the first match — it is equivalent to !a[1]. The same shorthand applies to @a and to the anonymous match placeholders used inside loops, described below.

The of operator and quantifiers

Often you want "at least N of these patterns" rather than naming a specific boolean combination. The of operator expresses that: <quantifier> of <pattern_set>.

rule OfExample {
strings:
$a = "dummy1"
$b = "dummy2"
$c = "dummy3"
condition:
2 of ($a, $b, $c)
}

A pattern set can use a wildcard to match identifiers by prefix, and the keyword them stands for every pattern in the rule.

ExpressionMeaning
all of themEvery pattern in the rule must match
any of themAt least one pattern must match
none of themNo pattern may match
2 of ($a, $b, $c)At least two of the listed patterns
any of ($a, $b, $c)At least one of the listed patterns
none of ($b*)No pattern whose identifier starts with $b
2 of ($foo*)At least two patterns starting with $foo

The quantifier can be an integer (or any expression returning a numeric value) or one of the keywords all, any, and none. You can also confine the whole check to an offset range with in:

rule OfInRange {
strings:
$a1 = "dummy1"
$a2 = "dummy2"
condition:
all of ($a*) in (filesize - 500..filesize)
}

for..of loops

When you need to evaluate the same expression against several patterns, use a for loop over a pattern set. Inside the loop body, the anonymous identifiers $, #, @, and ! refer to the pattern currently being evaluated.

rule ForOfExample {
strings:
$a = "dummy1"
$b = "dummy2"
$c = "dummy3"
condition:
for any of ($a, $b, $c) : ( # > 3 )
}

The quantifier before of says how many patterns in the set must satisfy the body. For example, for all of them : ( @ > 1000 ) requires that every pattern's first match occurs past offset 1000.

for..in loops

The second loop form iterates a variable over a range or collection and requires that a quantifier of the iterations satisfy the body.

rule ForInExample {
strings:
$a = "dummy1"
$b = "dummy2"
condition:
for all i in (1..#a) : ( @a[i] < 100 )
}

Here i walks from 1 to the number of matches of $a, and the rule matches only if every match of $a is found before offset 100. The range bounds can themselves be expressions, such as (1..#a) above.

info

for..of iterates over patterns and exposes the anonymous $, #, @, ! placeholders. for..in iterates over values (a range or collection) and binds a named loop variable you then use to index, e.g. @a[i]. Reach for for..of when the same test applies to many patterns, and for..in when you need to relate individual matches to each other.

Special variables and data accessors

A condition can inspect the raw bytes and size of the scanned data without declaring any pattern.

filesize

filesize is the size of the scanned data in bytes. It accepts the postfixes KB and MB, which multiply by 1024 and 1024 × 1024 respectively.

rule FileSizeExample {
condition:
filesize > 200KB
}

Reading integers and floats at an offset

A family of functions reads a value of a given type at a byte offset. The name encodes the size, sign, and endianness; for example uint16 reads an unsigned 16-bit little-endian integer, while int32be reads a signed 32-bit big-endian integer.

FamilyExamples
Signed integersint8, int16, int32, with be variants like int16be
Unsigned integersuint8, uint16, uint32, with be variants like uint32be
Floatsfloat32, float64, with be variants

A classic use is detecting a Windows PE file by reading the MZ and PE signatures:

rule IsPE {
condition:
// MZ signature at offset 0
uint16(0) == 0x5A4D and
// PE signature at the offset stored at 0x3C
uint32(uint32(0x3C)) == 0x00004550
}
Illustrative example

The IsPE rule and the small snippets on this page are illustrative — the string and byte values are drawn from the upstream documentation to demonstrate syntax. Treat them as learning material, not as production detection logic.

Putting it together

A realistic condition usually layers several of these features: presence, counts, location, and file size combined with boolean logic.

rule CombinedExample {
strings:
$hdr = { 4D 5A }
$tag1 = "config_a"
$tag2 = "config_b"
$tag3 = "config_c"
condition:
$hdr at 0 and
filesize < 2MB and
2 of ($tag*) and
#tag1 in (0..1024) > 0
}

This rule matches a file that starts with the MZ header, is smaller than 2 MB, contains at least two of the three config_* patterns, and has at least one $tag1 match within the first kilobyte.

Sources