|9 min read|Yvann Lièvre

Writing a YARA Rule for Malware: From Sample to a Reliable Signature (Without False Positives)

You get a malware sample, a DFIR engagement, a sandbox, a feed. You want to detect the whole family across your estate. Here is how to write a YARA rule that catches the threat without firing on legitimate software, and how to prove it.

YARAMalwareDFIRDetection
Writing a YARA Rule for Malware: From Sample to a Reliable Signature (Without False Positives)

You get a sample: a booby-trapped attachment from an incident-response engagement, a binary surfaced by your sandbox, a file flagged by a threat-intelligence feed. The question is not "what is it?" (analysis will tell you) but "how many other copies of this family are dormant across my estate?". YARA is the reference tool for answering that: a rule language that describes patterns (strings, bytes, structure) and searches for them at scale across files, memory, a repository.

The pitfall is the same as everywhere in detection: a rule that is too loose fires on perfectly clean software (a false positive (you drown the analyst), a rule that is too strict recognizes only the exact sample and misses the rest of the family (a false negative) you believe you are covered). This article shows how to write a rule that catches the family without crying wolf, and above all how to prove it.

Why write your own rule

  • Fresh, targeted threats. Malware seen at your site during a DFIR engagement, a regional variant, a bespoke implant: no public collection will cover it in time, if ever.
  • Your own samples. What your sandbox and your incidents produce is exclusive material. The rule that comes out of it detects what no one else sees.
  • Precision. A generic third-party rule may "match" a family at the cost of false positives you cannot tolerate. Writing your own means tuning that dial for your environment.

The anatomy of a YARA rule

A rule has three blocks: meta (the metadata), strings (the patterns to look for), and condition (the logic that combines them).

rule ACME_Stealer_Downloader {
    meta:
        description = "ACME Stealer - download stage"
        author      = "your-team"
        reference   = "internal DFIR report 2026-05"
        date        = "2026-05-28"
        hash        = "e3b0c44298fc1c149afbf4c8996fb924..."
 
    strings:
        $mz   = { 4D 5A }                       // PE header "MZ"
        $s1   = "acme_stealer_v" ascii
        $s2   = "/gate.php?id=" ascii
        $pdb  = "C:\\build\\acme\\loader.pdb" ascii
 
    condition:
        $mz at 0 and 2 of ($s1, $s2, $pdb)
}

(The example is illustrative: the method matters more than the chosen family.) The meta block documents the rule and makes it usable (reference, hash, date). The strings block lists the patterns, text (ascii, wide), bytes ({ 4D 5A }), or regular expressions. The condition block is the heart: it decides, from the patterns present, whether the file matches.

Start from the sample, not a copied rule

The reflex is to find an existing rule for the family and reuse it. Two reasons to be wary.

First, legal rigor: a rule published under a copyleft license (GPL) or with no license cannot go as-is into a closed or resold rule set, it would contaminate its rights. The fact carried by the sample, however, a given internal string, a given PDB path, a given C2 marker, belongs to no one. The signature you derive from it is your own work, just as an antivirus signature does not inherit the rights of the malware it targets.

Second, quality: a good rule describes what characterizes the family, not what happens to be in some random file. You extract the sample's strings and byte sequences, then keep the ones that are distinctive. Tools like yarGen automate the first draft: they extract strings and compare them against a database of legitimate files ("goodware") to weed out the ones that appear everywhere. But their output is a draft, human review remains indispensable.

Choosing good strings: where it is won or lost

This is where reliability is made or broken.

  • Avoid generic strings. Microsoft Corporation, GetProcAddress, a well-known library URL: they are in thousands of clean files. Including them means signing up for false positives.
  • Prefer unique markers. An internal string specific to the malware's author (acme_stealer_v), a compilation PDB path, a mutex, a C2 pattern, an unusual error message. Those are the fingerprints the family drags from sample to sample.
  • Anchor with structure. For a Windows executable, imphash (the hash of the import table) is a strong anchor: two binaries compiled from the same source often share it. Combined with two or three distinctive strings, it makes the rule both precise and resilient to variants.

The condition: neither too loose nor too strict

The condition turns patterns into a verdict. A few principles:

  • Bound it. filesize < 500KB avoids scanning large files needlessly and cuts false positives.
  • Check the type. uint16(0) == 0x5A4D confirms a PE before going further.
  • Require a quorum. 2 of ($s*) (two patterns out of several) catches variants that do not have all the strings, while staying discriminating. Requiring all five limits you to the exact sample.

The right setting lies between "a single pattern" (too much noise) and "all patterns" (overfitting on one file). Two or three distinctive markers, anchored by structure, is often the balance point.

Validating is not enough: you have to prove

YARA compiles the rule and reports syntax errors:

yara my_rules.yar sample.bin

And a linter like yaraQA flags the classic defects, a rule that can never match, a regular expression that is ruinous for performance. Necessary, but insufficient: "it compiles" says nothing about "it detects well."

The real proof is empirical, and it comes down to two tests:

  1. Does the rule fire on the whole family? Run it across your full set of samples for the family (retro-match). If it only catches the original file, it is overfitted, broaden the strings or lower the quorum.
  2. Does it stay silent on clean software? Run it against a benign corpus (goodware), your system binaries, your legitimate applications. Any fire is a false positive to fix before you deploy. This is the test most public rules have never undergone.

A rule that has passed both is worth infinitely more than a merely "valid" one. That is the difference between "it compiles" and "it works, proven, without noise."

The traps that cost you

  • The forgotten goodware string. The number-one source of false positives. The benign-corpus test is not optional.
  • The rule that matches nothing. A mistyped string, a wide encoding instead of ascii, and the rule never fires, a silent false negative. yaraQA catches some of these.
  • Performance. An over-open regular expression or a too-short string slows down a whole estate scan. Prefer long, specific patterns.
  • Sloppy metadata. Fill in hash, reference, date. That is what makes an alert usable and a rule maintainable over time.

In summary

Writing a good YARA rule is a method: start from the sample (not a copied rule), choose distinctive strings anchored by structure (not library strings), tune the condition between too-loose and too-strict, and prove empirically that it catches the family without touching clean software. The rest (compilation, linting, metadata) is hygiene.

That is exactly the discipline we apply at scale in the ThreatClaw YARA feed: every rule is validated on the real engine, retro-matched against its samples, and tested against a benign corpus to hold a feed's hardest promise, few false positives, proven. That is what a plain rule dump cannot show.

Related articles