YARA Rules.
Written Right, Not Just Written.
A static reference for YARA syntax and judgment calls: what each section of a rule does, how string and condition types differ, and the habits that separate a rule that hunts from one that just makes noise. No rule engine here, no live testing, just the reference. A written rule can be run against a live memory image with the windows.vadyarascan plugin covered in Volatility3 Commands.
Rule Anatomy
Four sections, one of them required. Get the shape right and the rest of YARA is just vocabulary.
The Four Sections, In Order
1. import (optional) import "pe"; or import "math"; ├── Placed at the top of the file, outside any rule body └── Unlocks module functions like pe.imports() and math.entropy() for use in condition: // only needed if condition: actually calls a module function 2. meta: key = "value" pairs, free text ├── Never evaluated, documentation only, not matching logic ├── author, date, description are conventional └── reference should map to a sample hash, CVE, or ATT&CK ID // a rule with no reference is unmaintainable once someone else has to triage its hits 3. strings: $name = pattern, one per line ├── Text: $s1 = "powershell" nocase ├── Hex: $hex1 = { 46 72 6F 6D 42 61 73 65 36 34 } └── Regex: $r1 = /-enc(odedcommand)?/i // referenced by name ($s1, $hex1, ...) from inside condition: 4. condition: required The boolean expression that decides whether the rule fires ├── Can reference strings ($s1), counts (#s1), offsets (@s1), file properties, module functions └── The only section YARA actually requires // a rule with no strings: can still match on file checks alone, e.g. filesize and uint16(0)
String Types
A string's type decides how it searches and what it costs at scan time. Pick lazy and pay for it on every file.
Text Strings
nocase— case-insensitive matchwide— match UTF-16LE, needed for Windows API strings and PowerShell's in-memory textascii— match single-byte ASCII (the default; pair withwideto catch both encodings of the same string)fullword— match can't be flanked by another word character, cuts false positives on short stringsxor— matches the string XOR'd against a single byte, useful for lightly-obfuscated payloads$s2 = "cmd.exe" wide ascii fullword— catches both text encodings as a whole word, not a substring
Hex Strings
??— wildcard byte, matches anything at that position[0-4]— jump range, allows 0 to 4 unknown bytes between two fixed byte groups{ 46 72 6F 6D 42 61 73 65 36 34 }— matches the ASCII bytes for "FromBase64"{ E8 ?? ?? ?? ?? 5D C3 }— a CALL instruction with an unknown 4-byte offset, followed by a fixed epilogue
Regex Strings
$r = /pattern/ only when a text or hex string genuinely can't express what you're matching. An unanchored pattern with nested quantifiers can cause catastrophic backtracking on a large file, since a regex string is checked at every byte offset, not once. Anchor with ^/$ or bound the length where you can, before shipping a regex string into a rule that scans a whole file share.Condition Operators
condition: is the only line that decides a match. Everything else in the rule just gives it vocabulary to work with.
Boolean Logic
and/or/not— combine string matches and other conditions$s1 and ($s2 or $s3) and not $s4— parentheses group sub-conditions exactly like any other boolean expression
Counting
2 of ($s1,$s2,$s3)— at least 2 of the listed strings matchedany of them/all of them— shorthand over every$string defined in the rule#s1 > 3— string $s1 matched more than 3 times in the file
Positional
$a at 0— string $a matches at offset 0, the start of the file$a in (0..1024)— string $a matches somewhere in the first 1024 bytes
File Checks
filesize > 100KB— reject anything too small to be a real sample of what you're huntinguint16(0) == 0x5A4D— first two bytes read as little-endian uint16 equalMZ, the PE header magic
Module Functions
import at the top of the file; unlocks checks plain strings can't dope.imports("kernel32.dll", "CreateProcessW")— flags a specific imported API by DLL and function namemath.entropy(0, filesize) > 7.0— flags high-entropy content typical of packed or encrypted data
Best Practices
The difference between a rule that hunts and a rule that pages someone at 3am for nothing.
Fullword + Nocase
fullword and nocase to plaintext strings by default. Fullword stops a short string from matching as a substring of something longer; nocase stops case variations from slipping past a rule that only checked one spelling.Anchor On Structure
filesize, uint16(0), or a pe.* check so the rule tests the file's shape, not only its content. Two rules that share the same strings but differ on structure checks will have very different false-positive rates in production.Use Private Rules
private rule for a shared building block, like "looks like a PE" or "has a suspicious PowerShell flag," and reference it from other rules as a condition term. It never appears in scan output by itself, only through whatever rule calls it.One Rule, One Hypothesis
Test Against Clean Files
Pair Entropy With Strings
math.entropy(0, filesize) > 7.0 flags packed or encrypted content, but on its own it also flags routine zip archives, images, and installers. Pair it with a small, specific string set so the entropy check narrows a real hit instead of carrying the rule by itself.Common Pitfalls
Small mistakes that don't show up until the rule is already deployed, and already wrong.
Forgetting Wide
$s = "powershell" string will silently never match either one; add wide, or wide ascii to catch both encodings.Unanchored Regex
/.*a.*b.*c/ against a large file can trigger catastrophic backtracking well before it ever reaches a verdict. Anchor with ^/$, or switch to a text or hex string if the pattern doesn't actually need regex.Missing meta.reference
meta.reference, the next analyst triaging a hit has to reverse-engineer why the rule exists before deciding whether it still should.Full Example
Every clause from the sections above, in one rule, doing real work.
Suspicious_PowerShell_EncodedCommand
rule Suspicious_PowerShell_EncodedCommand { meta: author = "your_name" date = "2026-09-20" description = "Detects base64-encoded PowerShell execution" reference = "T1059.001" strings: $s1 = "powershell" nocase $s2 = "-EncodedCommand" nocase $s3 = "-enc" nocase $hex1 = { 46 72 6F 6D 42 61 73 65 36 34 } condition: $s1 and ($s2 or $s3) and $hex1 } // $hex1 decodes to the ASCII string "FromBase64" — pairing it with the plaintext flags means the // rule needs both the command-line indicator and the decode-side evidence before it fires. // meta.reference maps the hit straight to ATT&CK T1059.001 for triage.