Guides authoring of high-quality YARA-X detection rules for malware identification. Use when writing, reviewing, or optimizing YARA rules. Covers naming conventions, string selection, performance optimization, migration from legacy YARA, and false positive reduction. Triggers
Installs just this skill. Get the whole plugin for auto-invocation.
⚡ How it fires
How this skill gets triggered: by you, by Claude, or both.
Fires itselfClaude auto-loads it when your prompt matches the work.
You can call itInvoke it directly when you want it.
Slash command/yara-rule-authoring
👁️ Context preview
The summary Claude sees to decide when to auto-load this skill.
Guides authoring of high-quality YARA-X detection rules for malware identification. Use when writing, reviewing, or optimizing YARA rules. Covers naming conventions, string selection, performance optimization, migration from legacy YARA, and false positive reduction. Triggers
📊 Stats
Stars6,227
Forks540
LanguagePython
LicenseCC-BY-SA-4.0
📦 Ships with trailofbits-skills
</> SKILL.md
yara-rule-authoring.SKILL.md
---name: yara-rule-authoring
description: >
Guides authoring of high-quality YARA-X detection rules for malware identification.
Use when writing, reviewing, or optimizing YARA rules. Covers naming conventions,
string selection, performance optimization, migration from legacy YARA, and false
positive reduction. Triggers on: YARA, YARA-X, malware detection, threat hunting,
IOC, signature, crx module, dex module.
---# YARA-X Rule Authoring
Write detection rules that catch malware without drowning in false positives.
> **This skill targets YARA-X**, the Rust-based successor to legacy YARA. YARA-X powers VirusTotal's production systems and is the recommended implementation. See [Migrating from Legacy YARA](#migrating-from-legacy-yara) if you have existing rules.
## Core Principles
1. **Strings must generate good atoms** — YARA extracts 4-byte subsequences for fast matching. Strings with repeated bytes, common sequences, or under 4 bytes force slow bytecode verification on too many files.
2. **Target specific families, not categories** — "Detects ransomware" catches everything and nothing. "Detects LockBit 3.0 configuration extraction routine" catches what you want.
3. **Test against goodware before deployment** — A rule that fires on Windows system files is useless. Validate against VirusTotal's goodware corpus or your own clean file set.
4. **Short-circuit with cheap checks first** — Put `filesize < 10MB and uint16(0) == 0x5A4D` before expensive string searches or module calls.
5. **Metadata is documentation** — Future you (and your team) need to know what this catches, why, and where the sample came from.
## When to Use
- Writing new YARA-X rules for malware detection
- Reviewing existing rules for quality or performance issues
- Optimizing slow-running rulesets
- Converting IOCs or threat intel into detection signatures
- Debugging false positive issues
- Preparing rules for production deployment
- Migrating legacy YARA rules to YARA-X
- Analyzing Chrome extensions (crx module)
- Analyzing Android apps (dex module)
## When NOT to Use
- Static analysis requiring disassembly → use Ghidra/IDA skills
- Dynamic malware analysis → use sandbox analysis skills
- Network-based detection → use Suricata/Snort skills
- Memory forensics with Volatility → use memory forensics skills
- Simple hash-based detection → just use hash lists
## YARA-X Overview
YARA-X is the Rust-based successor to legacy YARA: 5-10x faster regex, better errors, built-in formatter, stricter validation, new modules (crx, dex), 99% rule compatibility.
**Install:** `brew install yara-x` (macOS) or `cargo install yara-x`
| "This is just for hunting" | Hunting rules become detection rules. Same quality bar. |
| "The API name makes it malicious" | Legitimate software uses same APIs. Need behavioral context. |
| "any of them is fine for these common strings" | Common strings + any = FP flood. Use `any of` only for individually unique strings. |
| "This regex is specific enough" | `/fetch.*token/` matches all auth code. Add exfil destination requirement. |
| "The JavaScript looks clean" | Attackers poison legitimate code with injects. Check for eval+decode chains. |
| "I'll use .* for flexibility" | Unbounded regex = performance disaster + memory explosion. Use `.{0,30}`. |
| "I'll use --relaxed-re-syntax everywhere" | Masks real bugs. Fix the regex instead of hiding problems. |
## Decision Trees
### Is This String Good Enough?
```
Is this string good enough?
├─ Less than 4 bytes?
│ └─ NO — find longer string
├─ Contains repeated bytes (0000, 9090)?
│ └─ NO — add surrounding context
├─ Is an API name (VirtualAlloc, CreateRemoteThread)?
│ └─ NO — use hex pattern of call site instead
├─ Appears in Windows system files?
│ └─ NO — too generic, find something unique
├─ Is it a common path (C:\Windows\, cmd.exe)?
│ └─ NO — find malware-specific paths
├─ Unique to this malware family?
│ └─ YES — use it
└─ Appears in other malware too?
└─ MAYBE — combine with family-specific marker
```
### When to Use "all of" vs "any of"
```
Should I require all strings or allow any?
├─ Strings are individually unique to malware?
│ └─ any of them (each alone is suspicious)
├─ Strings are common but combination is suspicious?
│ └─ all of them (require the full pattern)
├─ Strings have different confidence levels?
│ └─ Group: all of ($core_*) and any of ($variant_*)
└─ Seeing many false positives?
└─ Tighten: switch any → all, add more required strings
```
**Lesson from production:** Rules using `any of ($network_*)` where strings included "fetch", "axios", "http" matched virtually all web applications. Switching to require credential path AND network call AND exfil destination eliminated FPs.
### When to Abandon a Rule Approach
Stop and pivot when:
- **yarGen returns only API names and paths** → See [When Strings Fail, Pivot to Structure](#when-strings-fail-pivot-to-structure)
- **Can't find 3 unique strings** → Probably packed. Target the unpacked version or detect the packer.
└─ TEXT with modifier: $s = "config" xor(0x00-0xFF)
```
### Is the Sample Packed? (Check First)
Before writing any string-based rule:
```
Is the sample packed?
├─ Entropy > 7.0?
│ └─ Likely packed — find unpacked layer first
├─ Few/no readable strings?
│ └─ Likely packed — use entropy, PE structure, or packer signatures
├─ UPX/MPRESS/custom packer detected?
│ └─ Target the unpacked payload OR detect the packer itself
└─ Readable strings available?
└─ Proceed with string-based detection
```
**Expert guidance:** Don't write rules against packed layers. The packing changes; the payload doesn't.
### When Strings Fail, Pivot to Structure
If yarGen returns only API names and generic paths:
```
String extraction failed — what now?
├─ High entropy sections?
│ └─ Use math.entropy() on specific sections
├─ Unusual imports pattern?
│ └─ Use pe.imphash() for import hash clustering
├─ Consistent PE structure anomalies?
│ └─ Target section names, sizes, characteristics
├─ Metadata present?
│ └─ Target version info, timestamps, resources
└─ Nothing unique?
└─ This sample may not be detectable with YARA alone
```
**Expert guidance:** "One can try to use other file properties, such as metadata, entropy, import hashes or other data which stays constant." — Kaspersky Applied YARA Training
## Expert Heuristics
**String selection:** Mutex names are gold; C2 paths silver; error messages bronze. Stack strings are almost always unique. If you need >6 strings, you're over-fitting.
**Condition design:** Start with `filesize <`, then magic bytes, then strings, then modules. If >5 lines, split into multiple rules.
**Quality signals:** yarGen output needs 80% filtering. Rules matching <50% of variants are too narrow; matching goodware are too broad.
**Modifier discipline:**
- **Never use `nocase` or `wide` speculatively** — only when you have confirmed evidence the case/encoding varies in samples
- `nocase` doubles atom generation; `wide` doubles string matching — both have real costs
- "If you don't have a clear reason for using those modifiers, don't do it" — Kaspersky Applied YARA
**Regex anchoring:**
- Regex without a 4+ byte literal substring **evaluates at every file offset** — catastrophic performance
- Always anchor regex to a distinctive literal: `/mshta\.exe http:\/\/.../` not `/http:\/\/.../`
- If you can't anchor, consider hex pattern with wildcards instead
**Loop discipline:**
- Always bound loops with filesize: `filesize < 100KB and for all i in (1..#a) : ...`
- Unbounded `#a` can be thousands in large files — exponential slowdown
**YARA-X tips:** `$_unused` to suppress warnings; `private $s` to hide from output; `yr check` + `yr fmt` before every commit.
### When to Use Modules vs. Byte Checks
```
Should I use a module or raw bytes?
├─ Need imphash/rich header/authenticode?
│ └─ Use PE module — too complex to replicate
├─ Just checking magic bytes or simple offsets?
│ └─ Use uint16/uint32 — faster, no module overhead
├─ Checking section names/sizes?
│ └─ PE module is cleaner, but add magic bytes filter FIRST
├─ Checking Chrome extension permissions?
│ └─ Use crx module — string parsing is fragile
└─ Checking LNK target paths?
└─ Use lnk module — LNK format is complex
```
**Expert guidance:** "Avoid the magic module — use explicit hex checks instead" — Neo23x0. Apply this principle: if you can do it with uint32(), don't load a module.
## YARA-X New Features
Key additions from recent releases:
- **Private patterns** (v1.3.0+): `private $helper = "pattern"` — matches but hidden from output
# 4. Dump module output to inspect file structure (no dummy rule needed)
yr dump -m pe sample.exe --output-format yaml
# 5. Scan with timing info
time yr scan -s rule.yar corpus/
```
**When to use `yr dump`:**
- Investigating what PE/ELF/Mach-O fields are available
- Debugging why module conditions aren't matching
- Exploring new modules (crx, lnk, dotnet) before writing rules
**YARA-X diagnostic advantage:** Error messages include precise source locations. If `yr check` points to line 15, the issue is actually on line 15 (unlike legacy YARA).
## Chrome Extension Analysis (crx module)
The `crx` module enables detection of malicious Chrome extensions. Requires YARA-X v1.5.0+ (basic), v1.11.0+ for `permhash()`.
for any perm in crx.permissions : (perm == "debugger")
}
```
See [crx-module.md](references/crx-module.md) for complete API reference, permission risk assessment, and example rules.
## Android DEX Analysis (dex module)
The `dex` module enables detection of Android malware. Requires YARA-X v1.11.0+. **Not compatible with legacy YARA's dex module** — API is completely different.
6. **Goodware validation** — VirusTotal corpus or local clean files
7. **Deploy** — Add to repo with full metadata, monitor for FPs
See [testing.md](references/testing.md) for detailed validation workflow and FP investigation.
For a comprehensive step-by-step guide covering all phases from sample collection to deployment, see [rule-development.md](workflows/rule-development.md).
## Common Mistakes
| Mistake | Bad | Good |
|---------|-----|------|
| API names as indicators | `"VirtualAlloc"` | Hex pattern of call site + unique mutex |
| Apple XProtect | Production macOS rules at `/System/Library/CoreServices/XProtect.bundle/` |
| [objective-see](https://objective-see.org/) | macOS malware research and samples |
| [macOS Security Tools](https://github.com/0xmachos/macos-security-tools) | Reference list |
### Multi-Indicator Clustering Pattern
Production rules often group indicators by type:
```yara
strings:
// Category A: Library indicators
$a1 = "SRWebSocket" ascii
$a2 = "SocketRocket" ascii
// Category B: Behavioral indicators
$b1 = "SSH tunnel" ascii
$b2 = "keylogger" ascii nocase
// Category C: C2 patterns
$c1 = /https:\/\/[a-z0-9]{8,16}\.onion/
condition:
filesize < 10MB and
any of ($a*) and any of ($b*) // Require evidence from BOTH categories
```
**Why this works:** Different indicator types have different confidence levels. A single C2 domain might be definitive, while you need multiple library imports to be confident. Grouping by `$a*`, `$b*`, `$c*` lets you express graduated requirements.