YARA is the pattern-matching language of malware analysis. Writing rules is easy; writing rules that survive the next sample variant is hard. This guide focuses on the second.
The Rule Structure
rule Example_Detection {
meta:
description = "Detects Example loader v3"
author = "CyberSecurity Elite"
date = "2026-04-14"
hash = "f3b1...c7e9"
reference = "https://example.com/report"
tlp = "white"
strings:
$s1 = "C2_BEACON_HEADER" ascii wide
$s2 = { 48 8b ?? ?? 48 89 ?? E8 [4] 85 C0 }
$s3 = /api\/v[0-9]\/(checkin|exec)/ ascii nocase
condition:
uint16(0) == 0x5A4D and 2 of them
}
Three string types — text, hex with wildcards/jumps, and regex. The condition is what makes the rule precise.
Anchoring: Why Most Rules Break
The first rule beginners write looks like:
strings:
$s = "Mozilla/5.0 (compatible; MyBotV1.0)"
condition:
$s
This catches the exact variant analyzed. The actor changes the UA string in v2 and your rule misses everything.
Better anchors:
- Code constructs that don’t change easily — function prologues, decryption loops, RC4 KSA initialization.
- Format features — PE imports, section names, RICH header values.
- Structural artifacts — XOR keys, callgraph fingerprints, mutex naming patterns.
rule RC4_KSA_Loop {
meta:
description = "Generic RC4 key-scheduling pattern"
strings:
// for (i=0;i<256;i++) S[i]=i; — classic init loop
$ksa = { 31 ?? 89 ?? 24 ?? FE ?? 80 FB FF 75 ?? }
condition:
$ksa
}
Hex with Wildcards
$pattern = { 48 8B ?? 48 89 ?? E8 [4-6] 85 C0 74 }
// ^^^^^^^ jump 4-6 bytes
// ^^ wildcard one nibble
// ^^ wildcard one byte
Use [X-Y] for variable-length gaps, ?? for any byte, single-nibble wildcards (?2) for specific instruction encodings.
The PE Module — Best Friend for Windows Malware
import "pe"
rule Suspicious_PE_Imports {
condition:
pe.imports("kernel32.dll", "VirtualAllocEx") and
pe.imports("kernel32.dll", "WriteProcessMemory") and
pe.imports("kernel32.dll", "CreateRemoteThread") and
pe.number_of_sections < 4 and
pe.entry_point_section.name != ".text"
}
PE-module conditions survive simple repackers because they target the structure, not strings.
Also useful:
pe.rich_signature.toolid(?, ?)— compiler fingerprint, often constant across an actor’s samplespe.section_index(".text").entropy > 7.0— strong indicator of packingpe.timestamp— reveals build-time patterns
Regex — Use Sparingly
YARA regex is slow. Use it only when you need true alternation or character classes that can’t be expressed in hex.
$url = /https?:\/\/[a-z0-9]{10,16}\.(top|xyz|club)\/[a-f0-9]{32}/ nocase
Performance
YARA scans terabytes at a time in SOCs. Slow rules are unusable rules.
- Short strings (<4 bytes) ASCII-only force a heavy scan. YARA warns; listen.
- Regex compiles to NFAs and is the slowest matcher. Combine with another condition (
pe.is_pe and $regex) so YARA short-circuits. forandofloops with large variable sets explode complexity.
yara --print-rules-stats profiles your ruleset.
False Positive Discipline
A noisy rule gets disabled. To stay precise:
- Test on goodware — Microsoft binaries, Python wheels, npm packages.
- Use VirusTotal Retrohunt before you publish — see what your rule actually matches across the global corpus.
condition: not pe.is_dllwhen a string is harmless in libraries.- Combine with imphash / sample-set conditions for actor-specific rules.
Rule Hygiene
Every rule should have:
- A meaningful name (
APT28_XAgent_Beacon_2024_03, notrule123) metawithauthor,date,reference,hash- A clear scope in the description — what variants it covers and what it intentionally won’t
- An explicit MIT or CC0 license header in shared repos
References
- YARA Documentation
- Florian Roth’s Signature Base — community gold standard
- YARA Rules Project