Defensive Security · lesson 7 of 8
Intermediate
6- 2 Sigma Detection Rules: Catch SSH Brute Force and Map It to ATT&CK
- 3 Suricata on a Packet Capture: Write an IDS Rule That Fires
- 4 Alert Triage and Tuning: From 41 Alerts to the 3 That Matter
- 5 Incident Response: Preserve the Evidence, Then Contain
- 6 Building an Incident Timeline from Auth, Web and IDS Logs
- 7 Static File Analysis with EICAR and YARA: Hashes, file, strings and Rules
Also part of Defensive Security
6- + A Firing Alert Doesn't Mean Anyone Was Notified from Monitoring & Observability
- + MTTR Isn't a Guess — Compute It From Alertmanager's Own Timestamps from Monitoring & Observability
- + Logging Security Events Without Logging Secrets from Security Fundamentals
- + Watching the TCP Handshake with tcpdump from Networking
- + Host Firewalls with nftables: DROP Times Out, REJECT Refuses from Networking
- + Hunting in AWS CloudTrail Logs with Python from Cloud Security
Static File Analysis with EICAR and YARA: Hashes, file, strings and Rules
Triage a suspicious file safely with sha256sum, file and strings, then write a YARA rule that matches the EICAR test file and not a note that mentions it.
By SkillByExample · Published
When a suspicious file turns up, the first questions are what it is, whether anyone has seen it before,
and whether it matches anything you already detect. All three can be answered without running it. This
lesson uses the EICAR test file, a harmless 68-byte string that every anti-malware product agrees to
detect as if it were malware, so you can practise the whole workflow with no risk. You’ll hash it,
identify it by content rather than name, and write a YARA rule that catches it without catching a text
file that merely mentions it. Tested with Docker Engine 29.8.2, Debian 13.7 (debian:trixie-slim), YARA
4.5.2, file 5.46, GNU strings 2.44 and Python 3.13.5.
This lesson works with EICAR only. It doesn’t handle real malware, and it never executes a sample.
An isolated analysis box
# Dockerfile
FROM debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends yara file binutils python3 \
&& rm -rf /var/lib/apt/lists/*
docker build -t yara-lab .
docker run -d --name analysis --network none -v "$PWD":/work -w /work yara-lab sleep infinity
--network none gives the container no network at all, the standard precaution when handling a sample.
The samples are created inside the container, in /samples, not in the mounted directory. Your own
anti-malware software would rightly quarantine an EICAR file written to your disk, which is the
detection working, but it would also break the lesson.
The script builds the EICAR file from two halves, so the script itself isn’t a match, and adds three other files: the same bytes under an innocent name, a text note that mentions EICAR, and a clean file.
# make_samples.py: build the EICAR test file and some harmless files in /samples
from pathlib import Path
# The EICAR string is split so this script isn't itself flagged by a scanner.
EICAR = r"X5O!P%@AP[4\PZX54(P^)7CC)7}$" + "EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
samples = Path("/samples")
samples.mkdir(exist_ok=True)
files = {
"eicar.com": EICAR,
"invoice.pdf": EICAR, # same bytes, misleading name
"notes.txt": "Analyst notes: the EICAR test file is harmless.\n",
"readme.txt": "Quarterly report draft, nothing to see here.\n",
}
for name, text in files.items():
path = samples / name
path.write_text(text)
print(f"{name:<12} {path.stat().st_size:>3} bytes")
$ docker exec analysis python3 make_samples.py
eicar.com 68 bytes
invoice.pdf 68 bytes
notes.txt 48 bytes
readme.txt 45 bytes
Hash, identify, read
Start with a hash. It’s the file’s identity: what you search threat-intel sources for, and what goes in the incident record.
$ docker exec analysis sh -c 'cd /samples && sha256sum *'
275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f eicar.com
275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f invoice.pdf
a0984f3071ad7b1d3fb974cd3faff55536bc94bc76e9a85397f73d0e9099fb31 notes.txt
cd986a908a9e7a50113d5a522e4d0078dd579b18d5c309259b5f8eaa12ea7060 readme.txt
invoice.pdf has the same hash as eicar.com, so it’s the same file, whatever it’s called. 275a02...
is the well-known SHA-256 of the EICAR file; search it on any malware database and you’ll find it
labelled as the test file.
$ docker exec analysis sh -c 'cd /samples && file *'
eicar.com: EICAR virus test files
invoice.pdf: EICAR virus test files
notes.txt: ASCII text
readme.txt: ASCII text
file ignores the extension and reads the content, comparing the first bytes against a database of
known formats. It even has an entry for EICAR. A real PDF would say PDF document; an “invoice” that
file doesn’t call a PDF is worth a closer look.
$ docker exec analysis sh -c "strings -n 8 /samples/invoice.pdf | grep -o 'EICAR[A-Z-]*'"
EICAR-STANDARD-ANTIVIRUS-TEST-FILE
strings prints every run of at least 8 printable characters. On real binaries it turns up URLs,
file paths, error messages and command names, often enough to guess what a program does. Here the grep
picks out the readable marker in the middle of the EICAR string.
A YARA rule that’s too loose
YARA describes files by their content: named strings, and a condition on them. The obvious first rule looks for the word EICAR.
// eicar_loose.yar: any file that mentions EICAR
rule EICAR_Loose
{
strings:
$name = "EICAR"
condition:
$name
}
$ docker exec analysis yara eicar_loose.yar /samples
EICAR_Loose /samples/eicar.com
EICAR_Loose /samples/invoice.pdf
EICAR_Loose /samples/notes.txt
$ docker exec analysis yara -s eicar_loose.yar /samples/notes.txt
EICAR_Loose /samples/notes.txt
0x13:$name: EICAR
It catches both copies, and the analyst’s notes. -s shows why: the string EICAR at offset 0x13
(byte 19) of the note. That’s the YARA version of the alert-tuning problem: a rule that matches on a word
will match every document that discusses the threat, including your own reports and wiki pages.
A rule that describes the file
The EICAR specification says exactly what the file is: a fixed string at the very start, and a total size of at most 128 bytes. Write the rule to that.
// eicar.yar: the EICAR test file itself, not text about it
rule EICAR_Test_File
{
meta:
description = "EICAR anti-malware test file"
sha256 = "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f"
strings:
$head = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$"
$tail = "EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
condition:
$head at 0 and $tail at 28 and filesize < 128
}
$ docker exec analysis yara eicar.yar /samples
EICAR_Test_File /samples/eicar.com
EICAR_Test_File /samples/invoice.pdf
$ docker exec analysis yara -s eicar.yar /samples/invoice.pdf
EICAR_Test_File /samples/invoice.pdf
0x0:$head: X5O!P%@AP[4\PZX54(P^)7CC)7}$
0x1c:$tail: EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
$ docker exec analysis yara -n eicar.yar /samples
EICAR_Test_File /samples/notes.txt
EICAR_Test_File /samples/readme.txt
Both copies match and the note doesn’t. at 0 and at 28 pin each half to its exact offset (0x1c is
28), and filesize < 128 rules out a large document that happens to begin with the string. In YARA
strings, a backslash is an escape character, so the single \ in the EICAR string is written \\. -n
prints the files that don’t match, which is the quickest way to check a rule against known-good files.
The meta section is free-form; recording the sample’s hash there tells the next analyst where the rule
came from.
Writing a rule is the easy half. Before it goes anywhere near production, run it over a large set of
known-clean files, as -n just did on a small scale, and treat every match there as a bug in the rule.
The last lesson in this topic examines the other file type every SOC triages daily: a suspicious email.
Practice
- Write
ioc_check.py, which compares each sample’s SHA-256 with a small dictionary of known-bad hashes and prints a verdict per file.
Solution
# ioc_check.py: compare every sample's SHA-256 with a list of known-bad hashes
import hashlib
from pathlib import Path
KNOWN_BAD = {
"275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f": "EICAR test file",
}
for path in sorted(Path("/samples").iterdir()):
digest = hashlib.sha256(path.read_bytes()).hexdigest()
verdict = KNOWN_BAD.get(digest, "no match")
print(f"{path.name:<14} {digest[:16]} {verdict}")
$ docker exec analysis python3 ioc_check.py
eicar.com 275a021bbfb6489e EICAR test file
invoice.pdf 275a021bbfb6489e EICAR test file
notes.txt a0984f3071ad7b1d no match
readme.txt cd986a908a9e7a50 no match
This is how a hash-based indicator of compromise (IOC) feed is used: an exact lookup, fast and with no false positives.
- Make a variant of the file with one extra byte, a trailing newline:
{ cat /samples/eicar.com; echo; } > /samples/eicar_nl.com. Runioc_check.py,yara eicar.yarandfileon it. Which of the three still recognise it?
Solution
$ docker exec analysis sh -c 'wc -c /samples/eicar_nl.com'
69 /samples/eicar_nl.com
$ docker exec analysis python3 ioc_check.py
eicar.com 275a021bbfb6489e EICAR test file
eicar_nl.com 131f95c51cc81946 no match
invoice.pdf 275a021bbfb6489e EICAR test file
notes.txt a0984f3071ad7b1d no match
readme.txt cd986a908a9e7a50 no match
$ docker exec analysis yara eicar.yar /samples
EICAR_Test_File /samples/eicar_nl.com
EICAR_Test_File /samples/eicar.com
EICAR_Test_File /samples/invoice.pdf
One byte gives a completely different hash, so the hash lookup misses it. The YARA rule still matches,
because the content it describes hasn’t moved, and file still reports EICAR virus test files. That’s
the trade-off: hashes are exact and brittle, content rules are flexible and need testing for false
positives. Real detection uses both. Delete eicar_nl.com afterwards so later commands match.
- Print the rule’s
metafields alongside the match.
Solution
$ docker exec analysis yara -m eicar.yar /samples/eicar.com
EICAR_Test_File [description="EICAR anti-malware test file",sha256="275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f"] /samples/eicar.com
-m prints the metadata, so the person reading a match sees the rule’s own description and reference
hash without opening the rule file. Rule sets shared between teams usually add author, date and a
reference URL in the same place.
- Remove
filesize < 128from the rule. Which harmless file could it then match, and why does the EICAR specification limit the size at all?
Solution
Without the size check, any file that starts with the EICAR string matches, however large: for example a long document someone pasted the string into at the top. The specification allows the 68-byte string plus optional trailing whitespace, up to 128 bytes, precisely so scanners can recognise the test file and not every file containing it. When you write rules for real samples, a filesize bound and fixed offsets are among the cheapest ways to cut false positives.
Clean up
docker rm -f analysis
Removing the container also removes /samples, so no EICAR file is left behind.