YARA Rules for Beginners: Writing Your First Malware Detection Signature
TL;DR: YARA is a pattern-matching engine used by malware analysts, incident responders, and SOC teams to identify malicious files based on textual or binary signatures. In this tutorial, you’ll learn YARA’s syntax, write your first rule against a real-world sample (the EICAR test file and a WannaCry-style indicator), test it with the yara CLI, and avoid common pitfalls like false positives and performance bottlenecks. By the end, you’ll have a working detection signature and a mental model for iterating on it.
What Is YARA and Why Should You Care?
YARA is a free, open-source tool originally developed by Victor Alvarez at VirusTotal. It lets you describe malware families in a readable, rule-based syntax and then scan files, memory, or processes to see if they match. Think of it as grep for binary data — but with boolean logic, wildcards, regex, and a scoring system.
It’s used everywhere:
- VirusTotal uses YARA to tag samples.
- MalwareBazaar and MISP distribute YARA rules alongside IOCs.
- EDR and AV vendors embed YARA-style logic (or an equivalent) in their engines.
- Incident responders drop YARA into Velociraptor, Loki, or THOR to sweep endpoints for known threats.
If you work in threat intel or DFIR, YARA is a baseline skill. For a broader overview of how YARA fits alongside Sigma and Suricata in a detection stack, this YARA rules guide covers the detection-triangle concept well.
Let’s write a rule.
Anatomy of a YARA Rule
Every YARA rule has three parts (plus optional metadata):
rule RuleName
{
meta:
author = "you"
description = "what it detects"
date = "2024-01-01"
strings:
$a = "malicious string"
$b = { 4D 5A 90 00 } // hex
$c = /regex[0-9]{3}/i // regex
condition:
$a and $b
}
Enter fullscreen mode Exit fullscreen mode
Key rules:
- Rule names must start with a letter or underscore and contain only alphanumerics and underscores.
-
Strings start with
$. You can have up to 10,000 of them per rule (but don’t). - Condition is a boolean expression returning true or false.
-
Modifiers like
nocase,wide,ascii,fullwordchange how strings match.
String types at a glance
Type Example Notes Text$a = "cmd.exe"
Case-sensitive by default
Text + nocase
$a = "cmd.exe" nocase
Case-insensitive
Wide
$a = "cmd.exe" wide
UTF-16LE — common in Windows binaries
Hex
$a = { 4D 5A }
Raw bytes, supports wildcards ??
Regex
$a = /https?:\/\/[a-z0-9.]+/i
Slower than plain strings
Condition operators
- Logical:
and,or,not - Comparison:
==,!=,<,>,<=,>= - Counting:
#a > 5(string$aappears more than 5 times) - Size:
filesize < 1MB - Location:
$a at 0(string is at offset 0) - Sets:
any of them,all of ($a*),2 of ($b*)
Writing Your First Rule: The EICAR Test File
EICAR is a harmless 68-byte string that AV engines intentionally flag as malware. It’s perfect for a first rule because the signature is unambiguous.
The EICAR string is:
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
Enter fullscreen mode Exit fullscreen mode
Create eicar.yar:
rule EICAR_Test_File
{
meta:
author = "dev.to reader"
description = "Detects the EICAR antivirus test file"
reference = "https://www.eicar.org/download-anti-malware-testfile/"
date = "2024-11-01"
strings:
$eicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
condition:
$eicar
}
Enter fullscreen mode Exit fullscreen mode
Note the escaped backslashes: because YARA strings interpret \ as an escape character, a literal backslash must be \\.
Save the EICAR payload:
printf 'X5O!P%%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > sample.txt
Enter fullscreen mode Exit fullscreen mode
Install YARA (Ubuntu/Debian):
sudo apt install yara
Enter fullscreen mode Exit fullscreen mode
Run it:
yara eicar.yar sample.txt
Enter fullscreen mode Exit fullscreen mode
Output:
EICAR_Test_File sample.txt
Enter fullscreen mode Exit fullscreen mode
You just wrote and executed a detection rule. If it doesn’t match, verify the file contents with xxd sample.txt — off-by-one characters are the usual culprit.
A More Realistic Example: Detecting WannaCry-Style Indicators
The EICAR rule is trivial. Real malware families require layered logic — anchoring on file structure, unique strings, and behavioral telltales.
WannaCry (2017) spread via EternalBlue (MS17-010), encrypted files with AES-128, and demanded Bitcoin payment. Its binary contains several distinctive artifacts. Even though AV coverage is universal now, it’s a great teaching sample because its indicators are well-documented.
Here’s a simplified rule inspired by public WannaCry detections:
rule WannaCry_Ransomware_Indicators
{
meta:
author = "dev.to reader"
description = "Detects WannaCry-style dropper artifacts"
reference = "https://www.cisa.gov/news-events/alerts/2017/05/12/wannacrypt-ransomware"
date = "2024-11-01"
tlp = "white"
strings:
// PE header
$mz = { 4D 5A }
// WannaCry kill switch domains (historical)
$killswitch = "iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com" nocase
$killswitch2 = "ifferfsodp9ifjaposdfjhgosurijfaewrwergwea.com" nocase
// Ransom note filename
$note = "@[email protected]" nocase
// Bitcoin wallet strings used in the original campaign
$btc1 = "13AM4VW2dhxYgXeQepoHkHSQuy6NgaEb94"
$btc2 = "12t9YDPgwueZ9NyMgw519p7AA8isjr6SMw"
// Tor client artifacts
$tor = "taskdl.exe" nocase
$tor2 = "taskse.exe" nocase
// Encryption-related imports
$crypto = "CryptEncrypt" ascii
$crypto_w = "CryptEncrypt" wide
condition:
$mz at 0 and
(2 of ($killswitch*) or
$note or
any of ($btc*) or
(2 of ($tor*) and 1 of ($crypto*)))
}
Enter fullscreen mode Exit fullscreen mode
Let’s unpack the design choices:
-
$mz at 0anchors the file as a PE. Without it, a text file quoting the BTC address would match — a textbook false positive. -
nocaseon domains catches case variations that survive string obfuscation. -
any of ($btc*)uses wildcarded set matching, so adding more wallets later is cheap. - The condition is an OR of independent clusters. Malware mutates; requiring all indicators would miss variants. Requiring some combination balances recall and precision.
-
$cryptoand$crypto_whandle both ASCII and UTF-16LE import tables; Windows API strings often appear in either form.
This rule is intentionally illustrative — production rules would also factor in entropy, PE section anomalies, and signed-certificate checks. For a broader writeup on building detection logic from threat intel feeds, the team at Riskitera’s blog publishes hands-on analyses worth following if you want to see how rule authors source their indicators.
Test it against any WannaCry sample in a safe sandbox (never on a production host):
yara -r wannacry.yar /path/to/samples/
Enter fullscreen mode Exit fullscreen mode
Flags you’ll use often:
-
-r— recurse directories -
-s— print matching strings (essential for tuning) -
-w— disable warnings -
-p N— parallel scan with N threads -
-d name=value— define external variables
Example with string output:
yara -s wannacry.yar suspicious.exe
Enter fullscreen mode Exit fullscreen mode
You’ll see exactly which strings fired, which tells you whether the match is real or a coincidental string in an unrelated binary.
Best Practices for YARA Rule Quality
Writing a rule that matches once is easy. Writing one that survives in production for years is hard. These habits matter:
1. Anchor on file structure
Always include uint16(0) == 0x5A4D (MZ header) when targeting Windows executables. For scripts, anchor on shebangs. For documents, anchor on magic bytes.
condition:
uint16(0) == 0x5A4D and
filesize < 5MB and
$unique_string
Enter fullscreen mode Exit fullscreen mode
2. Prefer multiple short strings over one long one
Long strings break on trivial mutations. Four independent 8–15 byte strings joined by 3 of them are more robust.
3. Use fullword and nocase deliberately
fullword prevents cmd.exe from matching inside xcmd.exe. nocase triples performance cost in some engines — use when case obfuscation is realistic (domain names, CLI arguments), not for every string.
4. Beware of common strings
Strings like "This program cannot be run in DOS mode" and "kernel32.dll" appear in millions of benign files. Never condition on them alone. Combine with a filesize bound or a rare companion string.
5. Add metadata and TLP
Future-you will thank present-you. Include author, description, date, reference, hash (of the reference sample), and tlp. Most sharing platforms (MISP, OpenCTI, ThreatFox) parse these fields.
6. Test for false positives before publishing
Run your rule against a clean corpus — /usr/bin on Linux, C:\Windows\System32 on Windows — and confirm zero hits. A quick smoke test:
yara -r myrule.yar /usr/bin 2>/dev/null | head
Enter fullscreen mode Exit fullscreen mode
If anything matches, inspect it with -s and tighten the condition.
7. Version your rules
Track changes in Git. Use semantic versioning on rule sets. A rule you tweak to catch variant B might silently stop catching variant A.
8. Use include and modules
YARA supports modules like pe, math, hash, and elf that expose parsed file metadata:
import "pe"
import "hash"
rule Suspicious_Packed_PE
{
condition:
pe.is_pe and
pe.number_of_sections > 0 and
for any section in pe.sections : (
section.characteristics & pe.SECTION_MEM_EXECUTE and
math.entropy(section.offset, section.size) > 7.0
) and
hash.md5(0, filesize) != "d41d8cd98f00b204e9800998ecf8427e"
}
Enter fullscreen mode Exit fullscreen mode
That’s a real, generic heuristic: packed executables often exhibit high entropy in executable sections, which is a common sign of runtime unpacking.
Common Pitfalls and How to Avoid Them
Forgetting to escape backslashes. "C:\Windows" in YARA is a literal backslash followed by W. You want "C:\\Windows". Regex strings use different escaping.
Using $a without context in condition. YARA treats $a as “string defined AND found.” If you write not $a, it returns true when $a is undefined — probably not what you meant. Use explicit #a == 0 for clarity.
Overusing regex. Regex is slow and error-prone. Prefer hex wildcards { 4D 5A ?? ?? 00 } for binary signatures and nocase strings for text.
Ignoring filesize. Limiting to filesize < 10MB often eliminates a huge class of false positives (archives, media files) without losing real matches.
Scanning the filesystem blindly. On production endpoints, scan only file types you can act on (executables, scripts, office docs) and exclude caches. Use -N to skip .git, node_modules, and the like.
Copying rules without attribution. Public rules from vendors and researchers carry licenses and TLP markings. Respect them.
Conclusion
YARA is a deceptively simple language that scales from one-line text matches to complex PE-aware heuristics. The path to competence is:
- Write a trivial rule (EICAR) to learn syntax.
- Model a real family (WannaCry) to learn layered conditions.
- Test against clean corpora to build intuition for false positives.
-
Iterate with
-soutput to see why something matched. - Version and document so your rules survive the next analyst.
The tooling is straightforward: apt install yara, drop a .yar file, run yara rule.yar target. The craft is in the condition blocks. Start small, anchor on structure, and treat every rule as a hypothesis you have to falsify against a clean system before trusting it on an infected one.
Once you’re comfortable, look into YARA-X (the Rust rewrite from VirusTotal), integrate rules into your SIEM or EDR, and pair them with behavioral detections. Static signatures catch known families; behavioral logic catches their mutations. You want both.