Detection Engineering

Writing YARA rules

Learn to write YARA rules that detect whole malware families: strings, conditions, modules, testing with yara/YARA-X and performance tips.

1 Introduction

YARA is a pattern-matching tool that lets you describe a family of files with a small, readable rule: "a Windows executable, smaller than 2 MB, that contains these two strings and this byte sequence". It was created by Victor M. Alvarez at VirusTotal and has become the lingua franca of malware researchers, incident responders and threat intelligence teams. People often call it "grep for malware", but it is much more expressive than grep.

Why not simply block a file hash? Because a hash changes completely as soon as the attacker recompiles or modifies one byte (see the Pyramid of Pain). A YARA rule targets what stays stable across variants: a mutex name, a configuration marker, a piece of code, a typo in a ransom note. One good rule can catch hundreds of samples that have never been seen before.

3 blocks

meta, strings and condition: that is the whole rule

1 hash

is what an IOC matches. A YARA rule matches a whole family

Everywhere

VirusTotal, EDRs, sandboxes, forensic tools and memory scanners speak YARA

Where YARA is used

Threat hunting

Sweep file shares, endpoints or a malware repository for files resembling a sample from a CTI report.

Incident response

Once one infected machine is found, write a rule from the sample and scan the whole fleet to find the others.

Malware triage

Classify incoming samples automatically: sandboxes run YARA on files and on memory dumps to name the family.

Sharing intelligence

Rules are plain text: they travel inside threat intel feeds, reports and STIX indicators.

2 Rule anatomy

Here is a complete rule for a fictional "MLAB demo loader". Every part is annotated; the sections below explain each keyword. Comments use // (single line) or /* */ (multi-line), exactly like in C.

demo_loader.yar

import "pe"                                  // optional: load a module (pe, elf, math, hash...)

rule MLAB_Demo_Loader : demo loader          // rule name + optional tags
{
    meta:                                    // free-form key/value context, never used for matching
        author      = "mlab Academy"
        description = "Detects the fictional MLAB demo loader (training sample)"
        date        = "2026-10-21"
        reference   = "https://academy.mlab.sh/page/yara-rules"
        hash        = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
        tlp         = "CLEAR"

    strings:                                 // what to look for
        $s1 = "mlab-demo-loader" ascii wide nocase    // text string + modifiers
        $s2 = "/gate.php?id=" ascii
        $s3 = "MLAB_MUTEX_7f3a" ascii wide fullword
        $h1 = { 4D 4C 41 42 ?? ?? 00 [2-4] FF }        // hex string: ?? = any byte, [2-4] = jump
        $r1 = /beacon_[0-9]{4}\.dat/                   // regular expression
        $x1 = "S3cr3tC0nf1g" xor(0x01-0xff)           // single-byte XOR, every key 1..255

    condition:                               // boolean logic deciding the match
        uint16(0) == 0x5A4D                  // file starts with "MZ" (Windows PE)
        and filesize < 2MB
        and pe.number_of_sections > 2
        and 2 of ($s*)                       // at least two of the $s strings
        and ($h1 or $r1 or $x1)
}

The header: name, tags, modifiers

A rule name is an identifier (letters, digits, underscore, max 128 characters, cannot start with a digit) and must be unique within its namespace. A common convention is TYPE_Family_Detail (e.g. MAL_ for malware, SUSP_ for suspicious, HKTL_ for hack tools). Optional tags after the colon let you filter output (yara -t loader). A rule can be prefixed with private (it never reports a match itself but can be used by other rules) or global (its condition is applied to every rule in the file).

meta: context for humans and tools

Key/value pairs (string, integer or boolean) that are never used for matching. Always fill in at least author, description, date and reference, plus the hash of a sample the rule was tested on. Six months later, the analyst triaging an alert will thank you.

strings: three kinds of patterns

Type Syntax Use it for
Text"mlab-demo-loader"Readable strings: URLs, mutexes, error messages, PDB paths. Escapes like \n \t \" \\ \x41 work.
Hex{ 4D 4C ?? [2-4] (41 | 42) }Raw bytes and code. ?? = any byte, 4? = nibble wildcard, [2-4] = jump of 2 to 4 bytes, ( A | B ) = alternatives.
Regex/beacon_[0-9]{4}\.dat/Variable patterns. Powerful but the slowest option: always anchor them on a few fixed characters.

String modifiers are written after the pattern and change how it is searched:

Modifier Effect
asciiPlain one-byte-per-character search. Implicit, unless wide is present.
wideCharacters interleaved with zero bytes (UTF-16LE), as stored by most Windows programs. Use ascii wide to search both.
nocaseCase-insensitive match.
fullwordOnly matches if delimited by non-alphanumeric characters: "mal" will not hit inside "normal".
xor / xor(1-255)Also searches every single-byte XOR encoding of the string, a trick malware uses to hide strings. Cannot be combined with nocase.
base64 / base64wideSearches the base64-encoded forms of the string (useful in scripts and documents).
privateThe string is matched but never printed in the output (handy for noisy helper strings).

condition: the decision

The condition is a boolean expression that may use and, or, not, comparisons and arithmetic. A string identifier alone ($a) is true when it is found. Every string you declare must be used in the condition, otherwise the rule does not compile.

Expression Meaning
2 of ($s*)At least two strings whose name starts with $s. Also: any of them, all of them, none of ($x*).
#url >= 3# = number of occurrences of the string.
$mz at 0The string is at exactly offset 0.
$cfg in (0..4096)The string appears within the first 4 KB.
@url[1]@ = offset of the n-th occurrence (1-based).
filesize < 2MBFile size, with KB / MB suffixes.
uint16(0) == 0x5A4DReads 2 bytes at offset 0 (little-endian): the MZ magic of a Windows executable. Also uint8, uint32, uint32be…
for all i in (1..#a) : ( ... )Loop over occurrences, integers or module arrays.
pe.number_of_sectionsModule data. pe, elf, macho, dotnet, math, hash… parse the file for you.

3 Worked examples

We build up from a one-line rule to a realistic one. All markers are invented for training: these rules will not make your antivirus flag this page, and you can safely reproduce them by creating test files with the same strings.

Step 1: the smallest useful rule

One string, one condition. The meta block is optional; condition is the only mandatory section.

demo_hello.yar

rule Demo_Hello_Marker
{
    strings:
        $marker = "MLAB-ACADEMY-TEST-MARKER"
    condition:
        $marker
}

Test it: printf 'xx MLAB-ACADEMY-TEST-MARKER yy' > sample.txt then yara -s demo_hello.yar sample.txt. You should see the rule name, the file name and the offset of the match.

Step 2: combining strings (suspicious PowerShell)

A single keyword like DownloadString appears in plenty of legitimate admin scripts. The trick is to require a combination: something that downloads and something that executes in memory, in a small text file that is not an executable.

ps_cradle.yar

rule SUSP_PS_Download_Cradle
{
    meta:
        author      = "mlab Academy"
        description = "PowerShell script that downloads content and executes it in memory"
        date        = "2026-10-21"
        score       = 60

    strings:
        $dl1 = "Net.WebClient" ascii wide nocase
        $dl2 = "DownloadString" ascii wide nocase
        $dl3 = "Invoke-WebRequest" ascii wide nocase
        $ex1 = "Invoke-Expression" ascii wide nocase
        $ex2 = "IEX(" ascii wide nocase
        $ex3 = "IEX (" ascii wide nocase
        $b64 = "FromBase64String" ascii wide nocase

    condition:
        filesize < 200KB
        and uint16(0) != 0x5A4D
        and 1 of ($ex*)
        and (1 of ($dl*) or $b64)
}
$ yara -s ps_cradle.yar invoice_update.ps1
SUSP_PS_Download_Cradle invoice_update.ps1
0x11:$dl1: Net.WebClient
0x28:$dl2: DownloadString
0x1f:$ex3: IEX (

Note the $dl* / $ex* naming: grouping strings by role with a prefix makes conditions such as 1 of ($ex*) short and readable. The score meta is a convention used by scanners such as THOR and LOKI to weight results.

Step 3: a family rule with file-type checks and a module

This is the annotated rule from section 2. It shows the classic structure of a malware-family rule: cheap structural checks first (MZ header, size, number of PE sections), then a threshold of "weak" strings (2 of ($s*)), then at least one "strong" indicator (a byte pattern, a regex or an XOR-hidden string). Requiring several independent clues is what keeps false positives low.

Step 4: private rules, counts, offsets and loops

A private rule acts as a reusable building block. Here Is_PE checks both the MZ header and the PE\0\0 signature found at the offset stored at 0x3C. The main rule then requires a configuration marker in the first 4 KB and at least three URLs that all appear after that marker.

demo_config.yar

private rule Is_PE
{
    condition:
        uint16(0) == 0x5A4D and uint32(uint32(0x3C)) == 0x00004550
}

rule MLAB_Demo_Config_Block
{
    meta:
        description = "Demo: string counts, offsets and loops"
    strings:
        $cfg = "[mlab-cfg]"
        $url = /https?:\/\/[a-z0-9.-]{4,64}\/[a-z]{3,10}\.php/ nocase
    condition:
        Is_PE
        and $cfg in (0..4096)
        and #url >= 3
        and for all i in (1..#url) : ( @url[i] > @cfg[1] )
}

Bonus: hashes and entropy without strings

A rule does not need a strings section. The hash module can match an exact SHA-256 (here the hash of an empty file, as a placeholder) and the math module can flag packed or encrypted content, whose entropy is close to 8. Hash checks in YARA are convenient but slow (the whole file is hashed): for plain hash IOCs, a lookup in your EDR or on mlab.sh is faster.

import "hash"
import "math"

rule MLAB_Demo_Exact_Hash_And_Entropy
{
    condition:
        filesize < 5MB
        and (
            hash.sha256(0, filesize) == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
            or math.entropy(0, filesize) >= 7.5
        )
}

4 Testing & tools

The yara command line

Install it with your package manager (apt install yara, brew install yara) or from the VirusTotal/yara GitHub releases. The essential commands:

# 1. Check that a rule file compiles (no output = OK)
$ yara -w demo_hello.yar /dev/null

# 2. Scan one file and print the matching strings (-s)
$ yara -s demo_hello.yar sample.txt
Demo_Hello_Marker sample.txt
0x3:$marker: MLAB-ACADEMY-TEST-MARKER

# 3. Scan a folder recursively (-r) and print the meta block (-m)
$ yara -r -m rules/ ./samples/

# 4. Compile once with yarac, then scan many times with -C
$ yarac rules.yar rules.yarc
$ yara -C rules.yarc ./samples/suspicious.bin

The compiler is your first reviewer. These are real messages it prints, and each one points at a classic mistake covered in section 5:

error: rule "bad" in bad.yar(1): unreferenced string "$b"
warning: rule "slow" in slow.yar(1): string "$a" may slow down scanning
warning: rule "slow" in slow.yar(1): $r contains .*, .+ or .{x,} consider using .{,N}, .{1,N} or {x,N} with a reasonable value for N

YARA-X, the next generation

YARA-X is a full rewrite of YARA in Rust by the same team, stable since its 1.0 release in 2025 and positioned as the future of the project (classic YARA 4.x now mostly receives fixes). It keeps the same rule language with very few incompatibilities, gives much clearer error messages and is faster on complex rules. Every rule on this page compiles with both YARA 4.5 and YARA-X.

# YARA-X: the Rust rewrite of YARA by VirusTotal (command: yr)
$ yr check rules/                    # compile-check every rule file
$ yr fmt demo_loader.yar             # normalise indentation and layout
$ yr scan demo_loader.yar ./samples/ # scan, same rule language

A testing routine that actually works

1. True positives

Keep the samples the rule was written for (in a password-protected archive, outside synced folders) and check that every one of them matches.

2. Goodware

Scan a large set of clean files: C:\Windows\System32, /usr/bin, popular installers, Office documents. Any hit is a false positive to fix before deployment.

3. Retro-hunt

Platforms such as VirusTotal Retrohunt, abuse.ch YARAify or MalwareBazaar run your rule against millions of past samples and show what else it would catch.

4. Version it

Store rules in Git, run yara or yr check in CI, and bump the date/modified meta each time you change one.

YARA-L is not YARA

Google Security Operations (formerly Chronicle) uses a language called YARA-L. Despite the name and a similar meta / events / condition layout, it matches log events in a SIEM, not bytes in files. If you want a portable way to write log detections, look at Sigma rules instead.

Start from a real sample on mlab.sh

Before writing a rule, analyze the suspicious file: hashes, strings and verdicts give you the stable markers your YARA rule should target.

Analyze a file on mlab.sh

5 Common pitfalls

Mistakes that cause false positives (or misses)

Matching on library or compiler strings

Strings like "This program cannot be run in DOS mode", OpenSSL error messages or Go runtime symbols are present in thousands of legitimate binaries. Pick strings that are specific to the author, not to the toolchain.

any of them on generic strings

One weak string is never enough. Combine several clues, add a file-type check (uint16(0) == 0x5A4D) and a filesize bound.

Forgetting wide

Windows programs often store strings in UTF-16. If you copied a string from a strings/sandbox report and the rule does not match, try ascii wide.

Scanning packed or compressed files

YARA sees raw bytes. Strings inside a ZIP, an Office document (which is a ZIP) or a UPX-packed executable are not visible until you unpack them, or scan the process memory instead.

A rule that triggers on its own threat report

Text-only rules (no file-type check) also match PDFs, web pages and rule files that quote the strings. Your antivirus quarantining your own rules repository is a classic.

Performance tips

YARA first extracts short atoms (up to 4 bytes) from each string and searches them all at once with the Aho-Corasick algorithm; only then does it verify full strings and evaluate conditions. Rules that give it poor atoms slow down every scan, not just their own.

Use strings of at least 4 distinctive bytes

Avoid { 00 00 00 00 }, { ?? ?? 00 } or "abc": the compiler warns "may slow down scanning" for a reason.

Bound your regular expressions

Never .* or .+; use .{1,64}. Start the regex with literal characters so YARA can extract an atom.

Put cheap checks first in the condition

Conditions short-circuit: filesize and uint16(0) before loops, math.entropy or hash calls avoids expensive work on non-matching files.

Prefer hex strings with fixed bytes at the start

Wildcards and jumps at the beginning of a hex string leave nothing to anchor on. Start and end with known bytes and keep jumps small.

Compile large rule sets

With thousands of rules, compiling once with yarac (or loading them once in your scanner) saves the parsing time on every run.

6 Going further

Community rule sets

Do not start from scratch: read good rules to learn the style, then reuse them. Always check the licence before using rules in a commercial product.

Neo23x0/signature-base

Florian Roth's large, well-maintained collection used by the LOKI and THOR scanners. Released under the Detection Rule License (DRL).

YARA-Forge

Aggregates, tests and de-duplicates dozens of public repositories into ready-to-use packages (core, extended, full) with consistent metadata.

elastic/protections-artifacts

The YARA rules Elastic Defend uses in production, a great reference for clean, low-false-positive style.

abuse.ch YARAify

Community platform to share rules and scan files against them, connected to MalwareBazaar.

How YARA fits with the rest of your detection

Tool Looks at Typical question
YARAFiles and memory"Is this file part of family X?"
SigmaLogs (SIEM)"Did a process behave like technique Y?"
Suricata / SnortNetwork traffic"Is a host talking to infrastructure Z?"

The three are complementary: a file caught by YARA gives you IOCs (C2 domains, hashes) for network and log rules, and tagging every rule with its MITRE ATT&CK technique in the meta block lets you measure your coverage. To understand what the families you detect actually do, see malware and threat families.

Related Modules