YARA performance: atoms, fast strings and conditions
Why some YARA rules are slow and how to fix them: how atoms and the prefilter work, good and bad strings, hex with fixed bytes, regex anchors and cheap guards.
A single slow rule can make a scan of thousands of files crawl, because every rule in a compiled set shares the same scanning pass. Understanding how YARA searches makes the difference between a rule that costs microseconds and one that dominates the scan.
How scanning works
Scanning happens in two phases:
- Pattern search. At compile time, YARA picks from every pattern a short fixed sequence of up to four bytes, the atom. All atoms of all rules are put into one Aho-Corasick automaton, and the data is scanned once. Wherever an atom occurs, YARA verifies the full pattern at that position.
- Condition evaluation. For each rule, the condition is evaluated using the matches found, plus file properties and module data.
The cost of phase 1 depends on how often atoms occur in the data. An atom like E8 45 FF 0A is rare; an atom like 00 00 occurs millions of times in any binary, and each occurrence triggers a verification.
Good and bad atoms
| Pattern | Atom quality | Why |
|---|---|---|
"EvilCorpUpdater" | Excellent | Long, rare text |
{ 8B 45 FC 33 D2 F7 75 F8 } | Good | Fixed code bytes |
"abc" | Poor | Three bytes: too short, frequent |
{ 00 00 00 00 ?? ?? 00 00 } | Very poor | Only zero bytes are fixed |
{ ?? ?? ?? ?? [2-8] 4D } | Very poor | One fixed byte |
/[a-z]{8}\.dll/ | Poor | Only .dll is literal |
/.*password=.*/ | Poor | Leading .* and short literal |
YARA-X warns at compile time about some patterns that are likely to be slow; the editor displays those warnings, and the linter adds heuristics for the rest.
Writing fast strings
- At least four fixed bytes. For text, prefer distinctive strings of eight characters or more. For hex, keep at least one run of four consecutive fixed bytes, ideally at the start.
- Avoid common bytes. Runs of
00,FF,90(NOP),CC(int3) or spaces are everywhere. - Wildcard only what varies. In code patterns, wildcard relative addresses and immediates that change between builds, not the opcodes.
- Use
nocase,xorandbase64on long strings only. Each expands one pattern into many atoms. - Anchor regexes with literals.
/evil[0-9]{2}\.exe/gives the atomevil; a class-only regex gives nothing.
rule Fast_Code_Pattern
{
strings:
// Four fixed bytes up front, wildcards only on the rel32 operand.
$decrypt = { 8A 04 0E 34 ?? 88 04 0F 41 81 F9 ?? ?? ?? ?? 72 }
$cfg = "cfg_version=" ascii
condition:
uint16(0) == 0x5A4D and filesize < 3MB and all of them
}
Cheap guards first
Conditions short-circuit from left to right. Put the checks that reject most files, cheaply, at the front:
import "pe"
import "math"
rule Ordered_Condition
{
strings:
$a = "ReflectiveLoader" ascii
condition:
uint16(0) == 0x5A4D and // one integer read
filesize < 4MB and // no I/O at all
$a and // already computed by the scan
pe.number_of_sections > 3 and // module data
math.entropy(0, 0x1000) < 7.0 // bounded computation
}
A filesize limit also protects you from huge files that happen to contain a matching atom.
Loops and module functions
- Loop over matches (
for any i in (1..#a)), never over every offset of the file (for any i in (0..filesize)). math.entropy,hash.*and other functions over(0, filesize)read the whole file; limit the range or guard it.pe.imports()and similar functions are cheap once the module has parsed the file, but parsing itself is skipped for files that fail an earlieruint16(0) == 0x5A4Dcheck.
Measuring
Measure rather than guess:
- Time
yr scan rules.yar corpus/on a representative corpus before and after a change. - Compare the whole rule set with and without a suspicious rule.
- Tools such as yaraQA check rule sets for known performance and logic issues.
- The rule pages of this library show a YARA Forge quality score that already reflects automated QA.
Checklist
- Every pattern has four or more consecutive fixed bytes.
- No pattern is made mostly of
00,FF,90,CCor whitespace. nocase,xorandbase64only on strings of eight characters or more.- Regexes contain a literal of three characters or more and no leading
.*. - The condition starts with magic bytes and a
filesizebound. - Loops iterate over matches; module functions use bounded ranges.
Paste your rule into the linter to check points 1–6 automatically, then test it in the editor. For how to test rules systematically, continue with testing and CI.
FAQ
- What is an atom in YARA?
- An atom is a short fixed byte sequence, up to four bytes, that YARA extracts from each pattern. All atoms are searched at once; the full pattern is only verified where its atom occurs.
- Why is my YARA rule slow?
- Usually because a pattern has no good atom: a string shorter than four bytes, a hex pattern made mostly of wildcards, very common bytes such as 00 00 00 00, or a regex without a literal part.
- Does the order of conditions matter?
- Yes. Conditions are evaluated left to right with short-circuiting, so cheap checks like filesize and magic bytes should come before module calls, loops and entropy or hash computations.