Indicators
A capability tagger that answers a different question from the CWE catalog: not what is wrong with this binary? but what does this binary do?
Nine behavior categories · ~160 curated rules · 430 macOS-relevant MITRE ATT&CK techniques mapped · 28 malware-family fingerprints · 6 evidence types · facts-only output.
Each hit carries a stable rule id, a category, an evidence type, optional matched_text and matched_va, and zero or more MITRE ATT&CK technique IDs. No severity, no score, no risk rating — a keylogger and a productivity app that listens for global hotkeys produce the same keystroke.global_hook indicator. Scoring those facts into a verdict lives in Malware or in the consumer's own policy layer.
The taxonomy mirrors Mandiant's capa project, trimmed to nine categories that make sense for a static analyzer running over macOS, Linux, and Windows binaries.
The nine behavior categories
| Category | What it tags | Rules |
|---|---|---|
| Persistence | How the binary maintains presence across reboots | 36 |
| Collection | How it gathers data from the user or system | 13 |
| Communication | How it talks to remote endpoints (C2 shapes) | 29 |
| Host interaction | Shell, post-compromise utilities, reverse shells, software-stack ID | 36 |
| Data manipulation | Cryptographic primitives embedded inline | 17 |
| Malware family | String-level family fingerprints (not attribution) | 28 |
| Targeting | Impersonation and brand-spoofing | 1 |
| Anti-analysis | Packing, obfuscation, anti-debug | reserved v1 |
| Load code | Dynamic loading abuse | reserved v1 |
The two reserved categories carry no rules in v1 — the populated signals for those areas live in the Findings CWE detectors (CWE-693 anti-debug, CWE-94 dynamic load) and the consistency layer. The empty category is held open so consumers can rely on the nine-element taxonomy regardless.
Persistence
LaunchAgent and LaunchDaemon plist paths (~/Library/LaunchAgents/, /Library/LaunchDaemons/, /System/Library/LaunchDaemons/), LoginItem (SMLoginItemSetEnabled), shell-profile injection (.zshrc, .bashrc, .bash_profile, .zprofile, .zshenv), legacy Unix (crontab, emond, /etc/rc.common), Linux systemd and cron equivalents, and the two dynamic-linker injection variables (DYLD_INSERT_LIBRARIES, LD_PRELOAD). Substring-only by design — false positives on documentation that mentions these paths are accepted because the alternative (interprocedural taint to a write site) misses the legitimate persistence shapes.
Collection
Clipboard harvest (NSPasteboard with dataForType), keychain dump (SecItemCopyMatching with kSecMatchLimitAll), credential access via UI primitives (synthetic keystrokes to a password field), file-tree walking with find plus a cp or tar outbound. Composite rules require multiple primitives co-located in the same function.
Communication
Raw IPv4-and-port literals (%d.%d.%d.%d:%d), Tor onion v2 and v3 domains, dynamic-DNS providers (duckdns.org, no-ip.com, dyndns.org), DNS TXT tunneling shapes (dnscat, dns2tcp), protocol-specific webhook templates (Slack, Discord, Mattermost), Mirai and Mozi IoT-botnet markers, Stratum mining-pool URI templates (stratum+tcp://), and the IRC PRIVMSG shape older bot families still emit.
Host interaction
Shell markers (/bin/sh -c, /bin/bash -c, osascript -e), post-compromise utilities (base64 -d, chmod +x, xattr -d com.apple.quarantine, SetFile -a V), reverse-shell patterns (nc -e, bash -i >& /dev/tcp/), privilege escalation (chmod 4755, chown root:wheel), and Linux/IoT patterns that show up in BusyBox-bundled implants.
Data manipulation
Cryptographic constants scanned as byte patterns against non-__TEXT sections: AES forward and reverse S-boxes, the AES Rcon table, MD5 / SHA-1 / SHA-256 / SHA-512 init constants, ChaCha20 and Salsa20 "expand 32-byte k" / "expand 16-byte k" sigmas, the Curve25519 base point, secp256k1 generator coordinates, the eight DES S-boxes, and the Blowfish P-array. A match means the constant is present in the binary's data — strong evidence the algorithm is implemented inline rather than called through libcrypto, useful when a binary appears to avoid the system crypto framework.
Targeting
Impersonation and brand-spoofing. One rule today: a com.apple.* bundle identifier signed by a team ID that isn't Apple's. More rules will land here as the curation set grows.
Anti-analysis
Reserved category in v1, empty rule table. Anti-debug and obfuscation signals come from Findings CWE-693 and the consistency layer's AdhocPrivileged and PrivateEntitlementNonApple checks.
Load code
Reserved category in v1, empty rule table. Dynamic-loading abuse comes from Findings CWE-94, which produces per-call-site findings rather than coarse capability hits.
Malware family
Family-attributable string patterns. See Family fingerprints below for the 28 seeded rules and the critical caveat about what a hit actually claims.
Evidence types
Every hit names where the evidence came from. The shape determines how matched_text and matched_va are populated.
| Evidence | Source | Anchored VA? |
|---|---|---|
| Raw string | C-string pool match in __TEXT,__cstring | yes — string VA |
| CFString | Decoded CFString body match | yes — struct VA |
| Selector | Objective-C selector name | sometimes (selectors don't always carry a VA) |
| Import | Import-symbol match (SecItemCopyMatching, CGEventTapCreate) | yes — IAT address |
| Entitlement | Key or value from the code-signature plist | no — metadata, not code |
| Section bytes | Byte-pattern match in a named section (the crypto-constant scans) | yes — match offset relative to section base |
For cross-surface predicates that don't anchor to a specific instruction — the targeting bundle-ID-vs-team-ID check, for example — matched_text carries the implicated string and the VA field is absent.
MITRE ATT&CK mapping
Every rule declares zero or more canonical ATT&CK technique IDs. The current snapshot covers 430 macOS-relevant techniques derived from MITRE's enterprise-attack.json and frozen in the build; reproducibility wins over freshness.
T-numbers use the canonical sub-technique form. Sample mappings:
| Rule | ATT&CK |
|---|---|
persistence.launch_agent | T1543.001 — Create or Modify System Process: Launch Agent |
shell.curl_pipe_sh | T1105 + T1059.004 — Ingress Tool Transfer + Unix Shell |
crypto.aes_sbox_forward | T1027 + T1573.001 — Obfuscated Files or Information + Encrypted Channel |
targeting.bundle_id_apple_domain | T1036.005 — Masquerading: Match Legitimate Name or Location |
keychain.security_find_internet_password | T1555.001 — Credentials from Password Stores: Keychain |
Rules can tag multiple T-numbers; the per-binary DNA vector materializes the union of all matched techniques, deduplicated and sorted. A consumer that wants per-tactic counts (the columns of the ATT&CK matrix — persistence, command-and-control, credential-access, defense-evasion, and so on) looks each T-number up in the frozen technique table and buckets by tactic.
Family fingerprints
The malware-family category houses 28 rules split into two groups.
Objective-See 2025 concretes — 8 multi-predicate rules across five families. Curated from Patrick Wardle's Objective-See 2025 macOS malware roundup. Each rule requires its predicate set to co-occur so a single shared string can't fire it:
- ChillyHell — three rules: a specific plist filename plus two co-occurring code symbols (
Utils::RunCommand,Utils::KillProcess) - Phexia — a specific persistence-plist filename
- DigitStealer — Google DNS literals plus a DNS-TXT C2 extraction shape
- NightPaw — a codesign-resigning runtime pattern
- AMOS (Atomic Stealer) — an AppleScript keychain-phishing prompt plus a wallet / Telegram credential-grabber shape
Legacy stubs — 20 single-predicate rules. Single-needle placeholders from earlier threat-research material, expanded as triage data accumulates: CloudMensis, XCSSET, CrescentCore, Shlayer, Silver Sparrow, Pirrit, OSAMiner, Dacls, IPStorm, KeRanger, EvilQuest, WindTail, FruitFly, MacRansom, OceanLotus, Komplex, Snake, XAgent, Lazarus, WireLurker. Weaker signals than the multi-predicate concretes — they'll gain co-occurring predicates as the per-family corpus grows.
The critical caveat. A family hit means this binary shares strings with samples attributed to family X. It does not mean this binary is family X. The evidence is string-level; attribution requires more. Downstream consumers own any thresholding.
What this surface does not do
- No embedded YARA parser. Rules are in-tree constants, diffable in git, version-controlled with the engine.
- No string decryption or runtime unpacking. Rules detect the presence of obfuscation; they never attempt to defeat it.
- No DGA detection. Regex-shaped suspicious TLDs only — algorithmically-generated domains need more than static patterns to catch usefully.
- No live ATT&CK fetches. Snapshot only.
- No scoring, severity, or ML. Indicators emit features, not models. Weighting and verdicts live downstream.
- No per-basic-block disassembly matching. Section-level byte scans only.
- No rule-update mechanism. Updates ship with macdecompile releases.
Where this fits
Indicators are one of four security surfaces. They feed the Malware classifier directly — the indicator-category aggregation rules (+10 per Communication hit, +10 per Collection hit, +15 per Anti-analysis hit, +25 per Malware-family hit) are how the malware composer turns capability facts into a tier verdict. They are independently consumed by openbinary's per-binary capability view, the per-package compare view, and external pipelines that want a stable feature stream without a verdict attached.
See Findings for the CWE catalog, Malware for the verdict surface, and CVEs & SBOM for the parallel version-based surface that runs on the same input.