Writing Your First YARA Rule

Write a working YARA rule from nothing. The four parts of a rule, how to write strings and a condition, and how to test it before you save it.

A YARA rule has four parts: a name, optional metadata, a list of named patterns called strings, and a condition that decides which combination of those patterns counts as a match. This page walks through a complete rule line by line, then shows you how to test it against files you already know the answer for, before you save it and turn it loose on your history. You do not need to have written one before.

Open Rules in the sidebar and select Create YARA rule. The editor opens with a skeleton rule, your name and today's date already filled into the metadata, and it checks what you type as you type it.

What goes in a YARA rule?

Four sections, in this order.

PartWhat it doesRequired
rule <Name>Names the rule. This is the name that appears on every match, so it is the sentence the next analyst reads firstYes
meta:Free-form key and value pairs: author, date, description, family, a reference link, reference hashes. Has no effect on whether a file matchesNo
strings:The patterns to look for, each given a name beginning with $Only if the condition refers to one
condition:One logical expression that decides the matchYes

Stairwell saves one rule at a time. If you paste a file containing several rules into the editor, save them one at a time, or bulk-load the file with the command line tool described in Manage YARA Rules.

What does a complete rule look like?

Here is a whole rule. It is deliberately small, and every line of it is explained underneath.

rule Example_ExDl_Downloader_Strings
{
    meta:
        author = "[email protected]"
        date = "2026-08-26"
        description = "Config strings and mutex used by the ExDl downloader"
        family = "ExDl"
        hash = "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f"

    strings:
        $ua    = "Mozilla/4.0 (compatible; ExDl/2.1)"
        $mutex = "Global\\exdl_singleton_9f3a"
        $stub  = { 8B 45 FC 33 D2 F7 75 F8 89 55 F4 }

    condition:
        uint16(0) == 0x5A4D and filesize < 2MB and 2 of them
}

rule Example_ExDl_Downloader_Strings names the rule. Rule names cannot contain spaces. A name that states the family and what the rule keys on saves the next reader from opening the body at all, which is why naming conventions get their own section in YARA Rule Best Practices.

The meta: block is documentation. None of it affects matching. Two entries earn their keep beyond documentation:

  • description is what someone reads when your rule fires at 2am and they have never seen it before.
  • hash records a file you know the rule should match, and it does more work for you than documentation. See What do the hashes in the metadata do? below.

$ua and $mutex are text strings. A text string in double quotes matches those exact bytes. Backslashes are escaped, which is why the mutex name is written Global\\exdl_singleton_9f3a for a value that reads Global\exdl_singleton_9f3a in the file.

$stub is a hex string. The bytes between braces match that exact byte sequence, and they let you key on machine code, an encryption constant, or a structure that has no printable form.

The condition is where the rule earns its keep, so read it as three separate requirements joined by and:

  • uint16(0) == 0x5A4D reads the first two bytes of the file and requires them to be MZ, the start of a Windows executable. This keeps the rule out of text files, logs, and archives that happen to contain the same bytes somewhere inside.
  • filesize < 2MB bounds the file size. A downloader is small; an installer that embeds one is not.
  • 2 of them requires at least two of the three named strings, so the rule survives a build in which one of the three changed.

Together they make a narrow, checkable claim: a Windows executable, under 2 MB, carrying at least two of these three specific artifacts.

How do I write the strings?

Three kinds cover nearly everything you will write at first.

Text. $s = "CreateRemoteThread" matches those bytes exactly, including case. Prefer long strings. A short string matches by accident: a four-character string turns up inside compressed data and base64 blobs on files that have nothing to do with your target, while an eight or twelve character string specific to the malware almost never does.

Hex. $h = { 48 8B 05 ?? ?? ?? ?? FF D0 } matches raw bytes. ?? is a wildcard for any single byte, which is how you write a code pattern whose embedded addresses change between builds. Hex is what you reach for when the interesting thing is not text.

Wide. Windows programs frequently store strings as two bytes per character, so ExDlService sits on disk as E, null, x, null, D, null, and so on. A plain text string will not match that. Add the wide modifier and YARA looks for the two-byte form instead:

strings:
    $svc_narrow = "ExDlService"
    $svc_both   = "ExDlService" ascii wide

wide on its own looks only for the two-byte form. ascii wide looks for both, which is usually what you want when you do not know how the target stores it.

Other modifiers exist, including nocase for case-insensitive matching. nocase is the one to be careful with, and YARA Rule Best Practices explains why with a worked example.

How does the condition decide a match?

The condition is one expression that is either true or false for a given file. Everything above it is raw material; this is where precision comes from, because the same three strings can produce a rule that returns nothing useful or a rule you would wake someone for, depending only on how you combine them.

The four shapes you need first:

ConditionMeansUse it when
any of themAt least one named string is present. Equivalent to $a or $b or $cEach string is distinctive enough to stand alone. Rare, and the most common source of a noisy rule
all of themEvery named string is presentYou are confident every build carries all of them. Precise, and brittle if one string changes
2 of themAt least two, any twoThe usual answer. Tolerates one artifact changing without opening the rule to a single accidental hit
#ua > 3The string named $ua appears more than three timesRepetition is the signal, such as a template written once per configuration block

Note the difference between the third row and the fourth. 2 of them counts how many distinct strings appeared. #ua > 3 counts how many times one string appeared. They answer different questions and are easy to confuse when you are reading someone else's rule.

Combine these with and, or, and not, and add tests about the file itself:

  • filesize < 500KB and filesize > 4KB bound the size.
  • uint16(0) == 0x5A4D requires a Windows executable, uint32(0) == 0x464C457F an ELF binary.
  • $stub at 0 requires a string at a specific offset rather than anywhere in the file.
  • pe.imports("wininet.dll", "InternetOpenUrlA") and math.entropy(0, filesize) > 7.0 test structured properties through YARA's modules, which are available to you without any setup. See the next section.

A habit worth adopting on day one: lead the condition with the unambiguous file-type and size guards, then the string requirements. The rule then reads as a claim about a kind of file rather than a claim about bytes floating in a void, and the next person can see what it is for.

Do I need to import anything?

No. Do not write import lines. Stairwell gives every rule the same eight modules automatically:

pe, elf, cuckoo, hash, math, dotnet, time, magic

So pe.is_pe, elf.number_of_sections, math.entropy(0, filesize), and hash.md5(0, filesize) all work in a condition with nothing above the rule line. Your rules are shorter and easier to read for it.

The other half of that sentence matters more, because it is the single most common reason a rule copied from a blog post or a GitHub repository does not work here: modules outside those eight are not supported. If the rule you pasted in starts with an import of something else, that import line is the thing to delete. If the rule's condition genuinely depends on that module, the rule will not run in Stairwell and needs rewriting against what is available rather than patching.

An import "pe" line for a module that is already there is harmless and accepted, so an inherited rule that imports one of the eight will still work. Leave it out of anything you write yourself.

Can one rule reference another?

No. In plain YARA you can write a condition that names another rule and matches when that rule matched. In Stairwell every rule stands alone: a condition may refer to its own strings and to the eight modules above, and to nothing else.

The practical consequence is that the shared "helper rule" pattern does not transfer. If you were planning one small rule that a dozen others reference, inline that shared condition into each of the twelve instead. That is repetitive, and it is the trade you are making: in exchange, any rule can be read, tested, disabled, exported, or handed to someone in a different organization without dragging a dependency along behind it. A rule that stands alone is a rule you can reason about on its own, which is worth more than the duplication costs.

What do the hashes in the metadata do?

They tell Stairwell which files you wrote the rule against, and those files get looked at sooner.

Put one hash in a metadata entry whose key starts with hash, sha256, sha1, or md5, and Stairwell treats that file as a reference file for the rule:

    meta:
        hash = "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f"
        hash_packed = "44d88612fea8a8f36de82e1278abb02f"

Two things follow, and both of them are feedback you would otherwise wait for:

  • Reference files, and files that resemble them, are scanned ahead of the ordinary queue. You find out whether the rule catches the thing you wrote it for, and its near relatives, in far less time than a pass over everything takes.
  • Those hashes are offered to you in Test Scan as the Must match list, and the editor header shows how many reference hashes it found and how many of them the rule currently matches, updated as you type.

Format matters here. The value has to be exactly one hash and nothing else: 32, 40, or 64 hexadecimal characters, no filename appended, no comment, no two hashes separated by a comma. Use a separate metadata entry per hash and give each one a distinct key. Anything that is not a bare hash is treated as ordinary documentation and quietly ignored.

The habit to build: whenever you write a rule against a known sample, put that sample's hash in the metadata. You get faster confirmation now, and you have recorded what the rule was built from for whoever reads it next.

How do I test a rule before saving it?

Use Test Scan, at the bottom of the rule editor. It becomes available as soon as the rule is free of errors, and it answers the only question worth asking before you save: does this rule match what I meant, and does it leave alone what I meant it to leave alone.

  1. Select Test Scan. Two boxes open, Must match and Must not match.
  2. Paste hashes into each. Both accept MD5, SHA-1, and SHA-256, one per line or comma separated, and the editor flags any value that is not a valid hash length. If your metadata already carried hash entries, they are loaded into Must match for you, and the panel header shows how many reference hashes it found alongside how many of them the rule currently matches.
  3. Fill in Must not match deliberately. This is the half people skip, and it is the half that catches the noisy rule. Good candidates: the legitimate tool your target resembles, a signed build of the same vendor's software, and any file that made a previous version of this rule embarrassing.
  4. Start the scan and read the results. Must match reports what matched, what failed to match, and any hashes Stairwell does not hold and therefore could not test. Must not match reports anything that matched when it should not have. For each matching file you get the byte offset, the name of the string that matched, and the matched bytes in both text and hex, and the offset links into that file's hex view so you can see the match in context.
  5. Fix and repeat. Nothing has been saved yet, so the loop costs you nothing.

Testing takes a minute and it is the habit that separates a rule your team relies on from a rule your team mutes. Do it before every save, not only the first one.

Why is a rule that matches everything worse than no rule?

Because a rule that matches everything spends your team's attention and returns nothing, and it does that permanently, quietly, and to everybody at once. No rule at all leaves a gap that somebody can notice and fill. A rule that fires on every Windows executable in the estate teaches your team that rule matches are noise, and that lesson gets applied to your good rules too.

So the important part, plainly: specificity is the craft. Writing a rule that matches a known bad file is the easy half and takes about a minute. Writing a rule that matches that file and not the twelve thousand ordinary programs that share a string with it is the actual work, and it is why the condition matters more than the strings do.

Two corollaries worth internalizing early:

  • A rule matching a legitimate administration tool is a match, not a detection. Remote administration tools, packers, credential utilities, and penetration-testing frameworks match malware rules because attackers use exactly those tools. Such a rule is not so much wrong as answering a different question than the one you asked.
  • Narrow beats clever. A rule that requires a specific file type, a bounded size, and two specific artifacts outlives a rule built on one long string, because you can read it a year later and still tell what it claims.

Stairwell limits the damage a broad rule can do rather than trusting that you got it right the first time. A new rule is measured against known-good files, and one that matches too many of them is excluded from scanning with the reason shown in the Rules list. Treat that as a safety net rather than a substitute for testing: it tells you a rule was too broad after the fact, where Must not match tells you before you save.

What else should I know before I save?

  • One rule per save. Save a multi-rule file one rule at a time, or bulk-load it.
  • No import lines, and no modules beyond the eight. Delete any other import you inherited.
  • No references to other rules. Every rule stands alone.
  • Global and private rules are not supported. A rule marked global or private is rejected.
  • Errors block the save; warnings do not. The editor lists both, anchored to the line they came from, and tells you which kind you are looking at. One warning is treated as an error: a pattern too slow to scan with. Rewrite it, usually by giving a regular expression some fixed text to anchor on.
  • A saved rule is active by default, so it starts working through your history right away. If you want it recorded without it running, turn it off from the rule panel. See Manage YARA Rules.

What should I read next?

  • YARA Rule Best Practices, for naming, string length, file-type guards, and what to do when a rule matches too much.
  • Manage YARA Rules, for editing, versioning, turning rules off, and bulk-loading a directory of rules.
  • YARA Rule Feeds, for the rules Stairwell writes and licenses, which are also worth reading as worked examples.

Did this page help you?