Your first YARA rule: describe malware and hunt it
An antivirus signature isn't magic: it's a description of what a piece of malware looks like. YARA lets you write that description yourself. If grep finds a pattern in a file, YARA finds a set of patterns with logic across thousands of files at once — and it's the go-to tool for classifying samples, threat hunting and writing your own detection rules.
The anatomy of a rule
Every YARA rule has three blocks: meta (documentation), strings (the patterns to look for) and condition (when it counts as a match). Only condition is required.
rule APT_Downloader_Generic
{
meta:
author = "you"
description = "Downloader for family X"
reference = "internal report #1234"
strings:
$ua = "Mozilla/4.0 (compatible; MSIE 6.0)" // hardcoded UA
$mtx = "Global\\svchost_update" nocase // mutex
$code = { 6A 40 68 00 30 00 00 8D } // stub bytes
condition:
uint16(0) == 0x5A4D and 2 of them
}
Read it bottom-up: "if the file starts with MZ (0x5A4D, a Windows executable) and at least 2 of my patterns show up, it's a match". That uint16(0) == 0x5A4D is the same trick as magic bytes: anchor the rule to the real file type before looking at anything else.
The three kinds of string
- Text:
$a = "suspicious string". The most common: paths, domains, error messages, registry keys. - Hex:
$b = { 6A 40 68 ?? 30 [2-4] 8D }. For code bytes. Supports wildcards (??) and jumps ([2-4]= "between 2 and 4 arbitrary bytes"). - Regex:
$c = /https?:\/\/[a-z0-9.]{5,30}\/gate\.php/. Powerful, but the most expensive: use it only when text and hex aren't enough.
Modifiers that save you
$a = "SELECT" nocase // ignore case
$b = "explorer" wide // UTF-16 (typical on Windows)
$c = "cmd" wide ascii // search both encodings
$d = "admin" fullword // whole word, not "administrator"
wide is the most forgotten one: many strings in Windows binaries are UTF-16 ("e·x·p·l·o·r·e·r"), and without wide you'll never find them.
The condition is where the craft is
This is where you decide which combination counts as a match. Beyond and/or:
any of them // any of the $strings
all of them // all of them
2 of ($a, $b, $c) // at least 2 of those
$a and #b > 3 // $a appears AND $b appears more than 3 times
$a at 0 // $a is exactly at offset 0
$a in (0..1024) // $a within the first 1 KB
filesize < 200KB // bound by size (fast, cuts false positives)
Combining a structural condition (uint16(0), filesize) with "N of M strings" is the recipe for a robust rule: cheap to evaluate and hard to evade by changing a single string.
condition for you (any/all/N of them, with optional filesize). All in your browser.A good rule vs one that fights you
The classic mistake is overshooting in either direction:
- Too generic:
$a = "http"withany of them→ fires on half your disk. False positives nobody reviews = alerts everyone ignores. - Too specific: anchoring a hash or a string that changes on every build → you catch that sample and no variant. The signature ages in a day.
The sweet spot: pick uncommon but stable strings (a mutex, an odd user-agent, a custom error message) and require several at once in the condition. You describe the family, not the sample.
kernel32.dll or a legitimate installer generates noise that buries the real detections.Where it's used
The same rule works in many places: file scanning on an endpoint, retrohunt on VirusTotal to find related samples, triage during incident response, or wired into an analysis pipeline. It's the "per-file" complement to Sigma rules, which detect "per log". Between the two you cover the binary and its behavior.
Checklist
- ✅
metawith author, description and reference (future you will thank you). - ✅ Uncommon, stable strings;
widefor Windows binaries. - ✅ Anchor by structure (
uint16(0),filesize) before looking at strings. - ✅
conditionwith "N of M", not a carelessany of them. - ✅ Tested against legitimate software: zero (or almost) false positives.
A YARA rule is a hypothesis about what the malware looks like. Write the hypothesis carefully and you'll catch it again and again, even when it changes its name.