A practical YARA cheat-sheet — CLI usage, rule syntax, modules, and detection tips — followed by every YARA rule published alongside a report on this blog, ready to copy or download.
yara rule.yar target_file yara rule.yar target_directory/ yara rules/ target_directory/
yara -r rule.yar /path/to/dir/ yara -r rules/ /samples/
-r recurses into subdirectories. Essential for bulk sample scanning.
yara rule.yar $(pgrep -x notepad)
yara rule.yar <PID>
# Scan ALL running processes
for pid in /proc/[0-9]*/; do
yara rule.yar ${pid##*/} 2>/dev/null
done
# Timeout per file (seconds) yara --timeout=30 rule.yar /samples/ # Skip files larger than N bytes yara -z 5242880 rule.yar /samples/ # (-z = max file size, here 5MB)
yara -s rule.yar target.exe # Output: # RuleName target.exe # 0x1234:$string1: 4d 5a 90 00 # 0x5678:$string2: This program
-s prints the offset and value of every matched string. Critical for
triage.# Only print matching files yara rule.yar /samples/ # Only print NON-matching files yara -n rule.yar /samples/ # Invert: alert on files that DON'T match yara --negate rule.yar /samples/
-n is useful for allowlisting — finding samples that evade a rule.
# Two rule files yara rule1.yar rule2.yar target.exe # Entire rules directory yara rules/*.yar target.exe # Compiled rules (faster) yarac rules/*.yar compiled.yarc yara compiled.yarc /samples/
yarac for repeated scans — much faster than
re-parsing.# Save results to file
yara -r rule.yar /samples/ > results.txt
# Print rule name only (no filename)
yara -o rule.yar target.exe
# Count matches only
yara -c rule.yar /samples/
# JSON output (yara-python)
import yara, json
rules = yara.compile('rule.yar')
matches = rules.match('target.exe')
print(json.dumps([str(m) for m in matches]))
yara-python for structured output.# Pass external vars at runtime
yara -d filename=malware.exe rule.yar target.exe
yara -d extension=.exe -d size=1024 rule.yar /samples/
# Rule using external var:
rule uses_ext {
condition:
filename matches /malware/
}
import yara
# Compile from file or string
rules = yara.compile(filepath='rule.yar')
rules = yara.compile(source='''
rule test { strings: $a = "MZ"
condition: $a }
''')
# Scan file
matches = rules.match('target.exe')
# Scan process
matches = rules.match(pid=1234)
# Scan bytes in memory
matches = rules.match(data=b'\x4d\x5a...')
for m in matches:
print(m.rule, m.strings)
rule RuleName : tag1 tag2 {
meta:
author = "SalahEldin Kamil"
date = "2025-06-01"
description = "What this rule detects"
reference = "https://..."
hash = "md5_of_sample"
version = "1.0"
strings:
$s1 = "malicious string"
$b1 = { 4D 5A 90 00 }
$r1 = /HKLM\\Software\\[A-Z]{4}/
condition:
uint16(0) == 0x5A4D and
filesize < 2MB and
any of them
}
meta, optional strings, and a
mandatory condition.// Private: matches but never reported
private rule IsPE {
condition:
uint16(0) == 0x5A4D and
uint32(uint32(0x3C)) == 0x00004550
}
// Global: if it fails, ALL rules skip file
global rule NotTooBig {
condition:
filesize < 10MB
}
// Using a private rule
rule Malware {
condition:
IsPE and $string1
}
private rule HasMZHeader {
condition:
uint16(0) == 0x5A4D
}
private rule HasPEHeader {
condition:
HasMZHeader and
uint32(uint32(0x3C)) == 0x00004550
}
rule SuspiciousPE {
strings:
$s = "VirtualAllocEx"
condition:
HasPEHeader and $s
}
// In your rule file:
include "common/pe_helpers.yar"
include "common/anti_debug.yar"
rule MyRule {
condition:
IsPE and HasAntiDebug
}
strings:
// Plain text (case-sensitive)
$plain = "This program cannot be run"
// Case-insensitive
$ci = "powershell" nocase
// Wide (UTF-16LE — Windows strings)
$wide = "cmd.exe" wide
// Both ASCII and Wide
$both = "malware" ascii wide
// Hex bytes (exact)
$hex = { 4D 5A 90 00 03 00 00 00 }
// Regex
$re = /HKLM\\[A-Za-z\\]{10,50}/
// XOR-obfuscated string (all single-byte keys)
$xor = "This program" xor
// XOR with specific key range
$xor2 = "cmd.exe" xor(0x01-0xff)
nocase, wide,
ascii, xor. Combine as needed.strings:
// Wildcard byte (?? = any byte)
$h1 = { 4D 5A ?? 00 ?? ?? 00 00 }
// Wildcard nibble (? = any nibble)
$h2 = { E8 ?4 00 00 00 }
// Jump (skip 4 to 8 bytes)
$h3 = { 4D 5A [4-8] 50 45 00 00 }
// Jump (skip any number of bytes)
$h4 = { 4D 5A [-] 50 45 00 00 }
// Alternatives (OR within pattern)
$h5 = { ( 4D 5A | 7F 45 4C 46 ) }
// matches MZ or ELF magic
strings: $s1 = "CreateRemoteThread" nocase $s2 = "kernel32.dll" wide nocase $s3 = "payload" base64 $s4 = "payload" base64wide $s5 = "shellcode" xor $s6 = "shellcode" xor(0x20-0x7f) $s7 = "config" fullword // fullword: won't match "myconfig"
fullword enforces word boundary — prevents matching if adjacent to
alphanumeric chars. base64 matches all 3 base64 alignments automatically.strings: $inject_1 = "VirtualAllocEx" $inject_2 = "WriteProcessMemory" $inject_3 = "CreateRemoteThread" $inject_4 = "NtCreateThreadEx" $c2_http = "User-Agent:" $c2_post = "POST /" condition: // Named set 3 of ($inject_*) // Any string starting with $c2 any of ($c2_*) // All strings all of them // All of a set all of ($inject_*)
$name_*) are the most powerful grouping tool in YARA —
match N of M related strings.condition: // String at a specific offset $mz at 0 $pe at uint32(0x3C) // String within a range $string in (0..1024) $string in (pe.overlay.offset..filesize) // Count occurrences #string > 5 #string == 1 // Occurrence number (0-indexed) @string[0] // offset of 1st match @string[1] // offset of 2nd match !string[0] // length of 1st match
# = count, @ = offset, ! = length. Combine
for precise positional logic.strings:
// IPv4 address
$ip = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
// Domain-like string
$dom = /[a-z0-9\-]{3,30}\.(com|net|org|ru|cn)/
// Base64-looking string (40+ chars)
$b64 = /[A-Za-z0-9+\/]{40,}={0,2}/
// Registry run key path
$reg = /Software\\Microsoft\\Windows\\CurrentVersion\\Run/i
// URL with IP
$url = /https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
// Hex-encoded shellcode blob
$shx = /([0-9a-fA-F]{2}){16,}/
nocase for domains/paths.
Anchors (^ $) are not supported.condition: $a and $b $a or $b not $a ($a or $b) and not $c $a and ($b or ($c and $d))
// Read integers from file at offset uint8(offset) // 1-byte unsigned uint16(offset) // 2-byte unsigned LE uint32(offset) // 4-byte unsigned LE uint64(offset) // 8-byte unsigned LE int8(offset) // 1-byte signed int16be(offset) // 2-byte signed BE uint32be(offset) // 4-byte unsigned BE // Classic PE check: condition: uint16(0) == 0x5A4D and // MZ uint32(uint32(0x3C)) == 0x00004550 // PE
uint32(0x3C) reads
e_lfanew; dereferencing it reaches the NT signature.condition: // Size comparisons filesize < 1MB filesize < 500KB filesize > 100 and filesize < 2MB // Entrypoint (requires PE module or raw) entrypoint == 0x1000 $code at entrypoint // PE entrypoint via module pe.entry_point == 0x1000
filesize supports KB/MB suffixes. entrypoint is
deprecated — prefer pe.entry_point.// At least N strings match at offset > X for any of ($str*) : ( @ > 0x1000 ) // All strings in first 512 bytes for all of ($hdr*) : ( @ < 512 ) // Count strings in a range for 3 of ($api*) : ( @ < pe.overlay.offset ) // Iterate over pe sections for any i in (0..pe.number_of_sections-1) : ( pe.sections[i].name == ".upx0" or pe.sections[i].entropy > 7.0 )
condition: // Arithmetic filesize \ 512 == 2 // integer division filesize % 16 == 0 // modulo (aligned) (filesize + 0xFF) & ~0xFF // round up // Bitwise uint8(0) & 0x0F == 0x0D uint16(0x3C) | 0x0001 uint32(0) ^ 0xFFFFFFFF uint8(0) >> 4 // right shift uint8(0) << 2 // left shift
import "hash"
condition:
// MD5 of entire file
hash.md5(0, filesize) ==
"d41d8cd98f00b204e9800998ecf8427e"
// SHA256 of first 512 bytes
hash.sha256(0, 512) ==
"e3b0c44298fc1c149afb..."
// MD5 of a specific section (via pe module)
hash.md5(pe.sections[0].raw_data_offset,
pe.sections[0].raw_data_size) ==
"abc123..."
condition:
// Count string occurrences
#s1 >= 3
#s1 + #s2 > 5
// Weighted scoring
(
(uint16(0) == 0x5A4D ? 1 : 0) +
($inject ? 2 : 0) +
($persistence ? 2 : 0) +
($c2 ? 3 : 0)
) >= 5
import "pe" condition: pe.is_pe // valid PE pe.machine == pe.MACHINE_AMD64 // x64 pe.machine == pe.MACHINE_I386 // x86 pe.subsystem == pe.SUBSYSTEM_WINDOWS_GUI pe.subsystem == pe.SUBSYSTEM_WINDOWS_CUI pe.characteristics & pe.DLL // is a DLL pe.timestamp < 1000000000 // old timestamp pe.entry_point // OEP RVA pe.image_base == 0x400000 pe.overlay.offset > 0 // has overlay
import "pe" at the top. Most PE-specific conditions need this
module.import "pe"
condition:
pe.number_of_sections > 8
// Named section exists
pe.section_index(".text") >= 0
// Section by name
for any i in (0..pe.number_of_sections-1) : (
pe.sections[i].name == ".upx0" or
pe.sections[i].name == ".themida"
)
// High entropy section (packed)
for any i in (0..pe.number_of_sections-1) : (
pe.sections[i].entropy > 7.0
)
// Writable + executable section (W^X violation)
for any i in (0..pe.number_of_sections-1) : (
(pe.sections[i].characteristics &
(pe.SECTION_MEM_WRITE | pe.SECTION_MEM_EXECUTE))
== (pe.SECTION_MEM_WRITE | pe.SECTION_MEM_EXECUTE)
)
import "pe"
condition:
// Single import
pe.imports("kernel32.dll", "VirtualAllocEx")
pe.imports("kernel32.dll", "WriteProcessMemory")
pe.imports("kernel32.dll", "CreateRemoteThread")
// Import by ordinal
pe.imports("ws2_32.dll", 23) // connect()
// Count imports from a DLL
pe.imports("ntdll.dll") > 5
// DLL imported at all
for any i in (0..pe.number_of_imports-1) : (
pe.import_details[i].library_name
matches /crypt32/i
)
import "pe"
condition:
// Export by name
pe.exports("ReflectiveDLLInjection")
pe.exports("DllInstall")
// Has exports at all
pe.number_of_exports > 0
// Resource by type
pe.resources[0].type == pe.RESOURCE_TYPE_ICON
// Resource count
pe.number_of_resources > 10
// Resource language
pe.resources[0].language == 0x0412 // Korean
import "pe"
condition:
// Authenticode: not signed
not pe.is_signed
// Signed but invalid
pe.is_signed and not pe.valid_signature
// Rich header present (compiled with MSVC)
pe.rich_signature.present
// Rich header XOR key
pe.rich_signature.key == 0xDEADBEEF
// Specific linker version from Rich header
for any i in (0..pe.rich_signature.length-1) : (
pe.rich_signature.toolid[i] == 0x0101
)
import "elf"
condition:
elf.type == elf.ET_EXEC // executable
elf.type == elf.ET_DYN // shared object
elf.machine == elf.EM_386
elf.machine == elf.EM_X86_64
elf.machine == elf.EM_ARM
elf.machine == elf.EM_AARCH64
// Section check
for any i in (0..elf.number_of_sections-1) : (
elf.sections[i].name == ".plt"
)
// Dynamic symbol
for any i in (0..elf.symtab_entries-1) : (
elf.symtab[i].name == "system"
)
elf module for Linux malware, IoT samples, and Android native
libraries.import "math"
condition:
// Entropy of entire file
math.entropy(0, filesize) > 7.0
// Entropy of specific range
math.entropy(0, 512) > 6.5
// Entropy of a PE section (combine with pe)
math.entropy(
pe.sections[0].raw_data_offset,
pe.sections[0].raw_data_size
) > 7.2
// Mean byte value (0-255)
math.mean(0, filesize) > 100
// Serial correlation coefficient
math.serial_correlation(0, filesize) < 0.1
import "hash"
condition:
hash.md5(0, filesize) ==
"d41d8cd98f00b204e9800998ecf8427e"
hash.sha1(0, filesize) ==
"da39a3ee5e6b4b0d3255bfef95601890afd80709"
hash.sha256(0, 512) ==
"e3b0c44298fc1c149afbf4c8996fb924..."
hash.crc32(0, filesize) == 0x12345678
hash.checksum32(0, filesize) == 0xDEADBEEF
import "dotnet" import "macho" // .NET detection dotnet.is_dotnet dotnet.assembly.name == "Malware.exe" dotnet.number_of_streams > 0 // Mach-O (macOS) macho.magic == 0xFEEDFACF // 64-bit macho.cputype == macho.CPU_TYPE_X86_64 for any i in (0..macho.number_of_cmds-1) : ( macho.cmds[i].type == macho.LC_CODE_SIGNATURE ) // time module import "time" // (use in condition for time-based meta)
meta: // Identity author = "SalahEldin Kamil" email = "contact@secblog.com" date = "2025-06-01" version = "1.2" // Description description = "Detects Lazarus Group BLINDINGCAN implant" reference = "https://cisa.gov/..." report = "https://secblog.com/reports/lazarus" // Sample info hash = "d41d8cd98f00b204e9800998ecf8427e" hash_sha256 = "e3b0c44298fc1c14..." filetype = "PE32+" sample_size = "245760" // Classification malware_family = "BLINDINGCAN" actor = "Lazarus Group / ZINC" threat_level = "CRITICAL" mitre_att = "T1055.001,T1027,T1497.001"
// Tags go after the rule name
rule LazyLoader : APT RAT injector {
condition: true
}
// Filter by tag on command line:
yara -t APT rules/ /samples/
yara -t injector,RAT rules/ /samples/
// Common tag conventions:
// APT — nation-state attributed
// ransomware — encrypts files
// dropper — drops/installs payload
// loader — loads next stage
// injector — process injection
// stealer — credential/data theft
// backdoor — remote access
// miner — crypto mining
// worm — self-propagating
-t to run only rules with a specific tag.
Essential for large rulesets.// Default namespace
rule MyRule { condition: true }
// From CLI — each file gets its own NS:
yara rules/apt.yar:APT
rules/ransomware.yar:RANSOM
target.exe
// In yara-python:
rules = yara.compile(sources={
'apt': open('apt.yar').read(),
'ransomware': open('ransom.yar').read(),
})
for m in rules.match('target.exe'):
print(m.namespace, m.rule)
rule AntiDebug_PEB {
strings:
$peb_x86 = { 64 A1 30 00 00 00 } // mov eax, fs:[30h]
$peb_x64 = { 65 48 8B ?? 60 } // mov rax, gs:[60h]
$chk1 = "IsDebuggerPresent" nocase
$chk2 = "CheckRemoteDebuggerPresent" nocase
$chk3 = "NtQueryInformationProcess" nocase
condition:
uint16(0) == 0x5A4D and
2 of them
}
rule ProcessInjection {
strings:
$va = "VirtualAllocEx" nocase
$wpm = "WriteProcessMemory" nocase
$crt = "CreateRemoteThread" nocase
$op = "OpenProcess" nocase
$ncte = "NtCreateThreadEx" nocase
$qat = "QueueUserAPC" nocase
$ntm = "NtMapViewOfSection" nocase
condition:
uint16(0) == 0x5A4D and
3 of ($va, $wpm, $crt, $op, $ncte, $qat, $ntm)
}
rule XOR_Encoded_Strings {
strings:
// Match "cmd.exe" XOR'd with any single byte
$xor_cmd = "cmd.exe" xor
// Match "powershell" XOR'd with 0x01–0x7F
$xor_ps = "powershell" xor(0x01-0x7f)
// Match "VirtualAlloc" XOR'd
$xor_va = "VirtualAlloc" xor
condition:
any of them
}
xor modifier (YARA ≥ 3.11) tries all 255 single-byte keys
automatically. Use key ranges to reduce FPs.import "pe"
import "math"
rule Packed_PE {
condition:
uint16(0) == 0x5A4D and
uint32(uint32(0x3C)) == 0x00004550 and
(
// High entropy in any section
for any i in (0..pe.number_of_sections-1) : (
math.entropy(
pe.sections[i].raw_data_offset,
pe.sections[i].raw_data_size) > 7.2
)
or
// VirtualSize >> SizeOfRawData (classic packer)
for any i in (0..pe.number_of_sections-1) : (
pe.sections[i].virtual_size >
pe.sections[i].raw_data_size * 10
)
)
// 1. Put fast conditions FIRST (short-circuit)
condition:
uint16(0) == 0x5A4D and // fast — 2 bytes
filesize < 5MB and // fast
$expensive_regex // slow — last
// 2. Use filesize before string searches
condition:
filesize > 10KB and filesize < 2MB and
any of them
// 3. Compile rules for repeated scans
yarac *.yar compiled.yarc
yara compiled.yarac /large_sample_dir/
// 4. Anchor hex patterns tightly
// Bad (slow): { 90 90 90 90 [-] 4D 5A }
// Good (fast): { 4D 5A 90 00 03 00 00 00 }
// 5. Avoid .* in regex — very slow
// Bad: /ABC.*XYZ/
// Good: /ABC.{0,100}XYZ/
and. Put cheap checks first.# Test rule compiles without errors
yara --compile-only rule.yar
# Verbose match output
yara -s -p 4 rule.yar target.exe
# Print ALL strings (matched + not)
yara -s rule.yar target.exe
# Test against clean files for FPs
yara -r rule.yar /Windows/System32/ 2>/dev/null
# yara-python: debug individual strings
import yara
rules = yara.compile(filepath='rule.yar')
m = rules.match('target.exe')
for match in m:
for s in match.strings:
print(hex(s.offset), s.identifier, s.plaintext())
# Scan memory of all running PIDs
ps aux | awk '{print $2}' | \
xargs -I{} yara rule.yar {} 2>/dev/null
# Find files matching rule, hash them
yara -r rule.yar /samples/ | \
awk '{print $2}' | \
xargs md5sum
# Scan ZIP without extracting
yara --scan-list rule.yar <<< "archive.zip"
# Time a rule against large dataset
time yara -r rule.yar /large_dir/
# yara-python: scan bytes from IDA
import yara, idaapi
data = idaapi.get_bytes(idc.get_inf_attr(idc.INF_MIN_EA),
idc.get_inf_attr(idc.INF_MAX_EA))
rules = yara.compile(filepath='rule.yar')
print(rules.match(data=data))
# Scan process memory in a dump
python vol.py -f mem.dmp \
windows.vadyarascan \
--yara-rules rule.yar \
--pid 1234
# Scan all processes
python vol.py -f mem.dmp \
windows.vadyarascan \
--yara-rules rules/
# Scan kernel pool
python vol.py -f mem.dmp \
windows.poolscanner \
--yara-rules rule.yar
# Scan with YARA string (no file)
python vol.py -f mem.dmp \
windows.vadyarascan \
--yara-string 'rule t{condition:true}'
vadyarascan scans VAD regions per process — the primary tool for
finding injected shellcode in memory dumps.