What Is an Indicator of Compromise (IOC)?
HuntRule Team · · 10 min read

On this page
A SHA-256 hash identifies exactly one file. Flip one byte in that file and the hash is gone, which is why hash-based blocking loses to a recompile. That single property explains most of what an indicator of compromise is good for and most of what it is not.
An IOC is evidence that something already happened. A hash of a binary that ran. An IP a beacon called. A registry value a loader wrote. It is a record of the past pointed at the present, which is both the whole value and the hard ceiling. IOCs answer "have we seen this" cheaply. They do not answer "would we catch it next time".
The types, and how long each one lasts
Every IOC type decays, but they decay at different rates and for different reasons.
Type | Example artifact | Where you see it | Practical shelf life |
|---|---|---|---|
File hash | SHA-256 of a loader DLL | Sysmon EID 1 | One build. Minutes if the actor recompiles per target |
IP address | C2 listener on a rented VPS | Firewall, proxy, Sysmon EID 3 | Days to weeks, then reassigned to someone innocent |
Domain | Registered a week before use | DNS logs, Sysmon EID 22, proxy | Weeks. Longer if it is a compromised legitimate site |
URL |
| Proxy, web gateway | Shortest of the network types. Paths rotate per campaign |
Registry key |
| Sysmon EID 12/13, EDR registry events | Months, because it is tied to the tooling not the infra |
Mutex |
| Memory, process handle listings | Long. Often survives across malware versions |
JA3 / JA4 | TLS client fingerprint of the implant | Zeek | Months, and it survives an IP change |
Certificate hash | SHA-256 of a C2 TLS certificate | Zeek | Until the operator regenerates the cert |
The mutex in that table is WannaCry's. Didier Stevens documented that the shipped samples use Global\MsWinZonesCacheCounterMutexA0, with a zero on the end, rather than the MsWinZonesCacheCounterMutexA that early reporting listed. IOCs get copied between reports faster than they get verified.
Why hashes are the weakest useful thing you can share
David Bianco's Pyramid of Pain (2013) put hash values at the bottom for a reason. Changing a hash costs an attacker one build. Changing an IP costs a few dollars. Changing a TTP costs them a redesign.
Hashes still get shared more than anything else because they are unambiguous and legally safe. A hash cannot false positive on a colleague's home router the way an IP can. That makes them a fine confirmation artifact and a fine retro-hunt seed. It makes them a bad foundation for a programme, because a ruleset built on hashes has a coverage number that is really a percentage of files somebody else already saw. A hash tells you whether you were in a specific campaign. It never tells you whether you would survive the next one.
The operational failures nobody puts in the feed documentation
Three things break IOC programmes, and all three are process problems rather than data problems.
An IOC with no expiry poisons a blocklist forever. Nothing in a plain list of IPs says when it was observed or when to stop trusting it. Blocklists grow monotonically and nobody dares prune them, so a VPS that hosted a Cobalt Strike listener in 2023 is still blocked in 2026, three tenants later.
A shared IOC without context produces alerts nobody can triage. "IP 203.0.113.10, malicious" gives an analyst no way to set severity and no way to tell beaconing from a user clicking a link on a shared host. The alert gets closed as noise, and once a feed's alerts are closed in bulk the feed is dead whether or not it is accurate.
Sinkholed and reused infrastructure produces false positives long after the campaign ended. A sinkholed C2 domain resolves to a researcher's collector, so a hit is a real infection signal or a stale artifact depending on context the feed did not carry. Block a domain fronted by a large CDN and you have blocked a slice of the internet.
TLS fingerprints age better, with caveats
JA3 hashes five fields from the TLS ClientHello into an MD5: version, cipher list, extension list, elliptic curves, and curve point formats. JA3S does the same for the ServerHello with version, cipher and extensions. The fingerprint follows the implant's TLS stack rather than its infrastructure, so it survives an IP rotation.
Two things happened to JA3. Chrome began permuting ClientHello extension order, rolling out broadly from 20 January 2023 in Chrome 108 and 109 ahead of the Chrome 110 release it was scheduled for, which makes a browser's JA3 differ per connection. Salesforce archived the JA3 repository on 1 May 2025.
JA4, from FoxIO, sorts the cipher and extension lists before hashing, which removes ordering as a source of instability. The format is a_b_c, for example t13d1516h2_8daaf6152771_e5627efa2ab1. The readable first section decodes as TLS over TCP, TLS 1.3, SNI present as a domain, 15 ciphers, 16 extensions, ALPN h2. Sections b and c are 12-character truncated SHA-256 values over the sorted ciphers and over the extensions plus signature algorithms. Because the sections are delimited you can hunt on a and c alone.
Neither arrives for free. Zeek does not emit JA3 out of the box: it is a package, zkg install ja3, which adds ja3 and ja3s fields to ssl.log. Suricata has ja3.hash and ja4.hash keywords, but the fingerprints must be enabled under app-layer.protocols.tls in suricata.yaml, or loaded implicitly by a rule that uses them.
Certificates sit in the same family. The default Cobalt Strike team server certificate has serial 146473198, CN jquery.com, and SHA-256 87f2085c32b6a2cc709b365f55873e207a9caa10bffecf2fd16d3cf9d94d390c, and Elastic ships a prebuilt rule for it. JA4X fingerprints how a certificate was generated instead, hashing the issuer RDN keys, subject RDN keys and extension keys, which catches self-signed C2 certs whose field values are randomised per deployment.
How an IOC should enter a SIEM
Not as a bare string. Every indicator needs the metadata that lets you expire and triage it. STIX 2.1 already has the shape:
{
"type": "indicator",
"spec_version": "2.1",
"id": "indicator--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
"created": "2026-06-14T09:12:00.000Z",
"modified": "2026-06-14T09:12:00.000Z",
"pattern_type": "stix",
"pattern": "[file:hashes.'SHA-256' = 'a5f8...c31d']",
"indicator_types": ["malicious-activity"],
"valid_from": "2026-06-14T09:12:00.000Z",
"valid_until": "2026-09-14T09:12:00.000Z",
"confidence": 70
}valid_until is the field most feeds omit and the one that decides whether your blocklist stays maintainable. If your intel platform will not carry an expiry, set one at ingest per type: short for URLs and IPs, longer for registry and mutex artifacts, longest for TLS and certificate fingerprints. Load indicators into a lookup, never into rule text. A rule that hardcodes an IP has to be edited and redeployed to expire it.

The retro-hunt
The one thing IOCs do better than any behavioural rule is answer a question about the past. Splunk against Sysmon network events, seeded from a lookup:
index=sysmon EventCode=3 earliest=-90d@d latest=now
[| inputlookup c2_indicators.csv
| search type=ipv4
| fields value
| rename value AS DestinationIp]
| stats min(_time) AS first_seen,
max(_time) AS last_seen,
dc(Computer) AS hosts,
values(Image) AS processes
BY DestinationIp
| convert ctime(first_seen), ctime(last_seen)
| sort - last_seenTwo limits before anyone reports the result upward. The query only sees as far back as your retention, so ninety days of search over thirty days of hot data quietly returns thirty days of answer. And a clean result is not proof of absence: it proves the indicator did not appear in that data source, in that window, on hosts that were reporting. Check dc(Computer) against your asset inventory first. That reporting-coverage check is the same one that gates a new detection, covered in detection engineering.
Deciding what to keep
Four questions per indicator. Anything failing the first two goes to hunt-only rather than into an alerting rule.
Does it carry a first-seen timestamp and an expiry. No expiry, no blocklist entry.
Does it carry enough context to write two sentences of runbook. If an analyst cannot decide what to do on a hit, the rule gets muted within a month.
Would a hit be actionable, or ambiguous by construction. Shared hosting, CDN-fronted domains, Tor exit nodes and known sinkholes belong in enrichment, not in blocking.
Is it tied to infrastructure or to tooling. Mutexes, named pipes, TLS fingerprints and service names outlive IPs and URLs by a wide margin.
From an IOC to a behavioural rule
Say a report gives you two indicators: a URL, hxxp://198.51.100.24/aa.txt, and the SHA-256 of the payload it served. Retro-hunt both. Assume no hits. Both are now worth almost nothing, because the host is burned and the payload will be rebuilt.
What is worth something is the sentence next to them in the report: the payload was fetched with certutil.exe. That is the behaviour, it maps to T1105 (Ingress Tool Transfer), and it is stable across campaigns because it belongs to the operator's tradecraft rather than to their hosting.
title: Certutil URL cache download
id: 3f1c2d54-9b0e-4a7c-a2f1-6b8d5e0c47aa
status: experimental
description: Detects certutil.exe fetching a remote file using the URL cache flags.
references:
- https://lolbas-project.github.io/lolbas/Binaries/Certutil/
author: HuntRule
date: 2026-07-31
tags:
- attack.command-and-control
- attack.t1105
logsource:
category: process_creation
product: windows
detection:
selection_img:
- Image|endswith: '\certutil.exe'
- OriginalFileName: 'CertUtil.exe'
selection_flags:
CommandLine|contains:
- 'urlcache'
- 'verifyctl'
condition: all of selection_*
falsepositives:
- Enterprise PKI scripts refreshing CRLs against an internal CA
- Build and packaging scripts that fetch installers over HTTP
level: mediumWhat it catches: any use of certutil as a downloader, on any URL, for any payload. The two IOCs from the report no longer decide whether it fires. SigmaHQ ships an equivalent rule, proc_creation_win_certutil_download.yml, tagged the same way.
What it misses matters more. It needs command line logging, so Sysmon Event ID 1 or Windows Security 4688 with the command line auditing policy enabled. Without CommandLine populated the rule returns nothing and never tells you why. It misses a renamed certutil unless your telemetry populates OriginalFileName. It misses every other download method: curl.exe, bitsadmin, msiexec /i http://..., PowerShell. An operator who reads detection blogs will use one of those instead.
That is the honest trade. The IOC was precise and worthless a fortnight later. The rule is imprecise, needs tuning against your own PKI scripts, and keeps working.
Summary
An IOC is evidence of a past event, which makes it good for retro-hunting and poor as a detection strategy. Hashes are the most-shared and least-durable type, and a programme measured in hash counts is measuring somebody else's campaigns. The failures that kill IOC programmes are operational: no expiry, no context, no handling of sinkholed or reused infrastructure. Treat every indicator as a seed for a behavioural rule and keep it only long enough to write that rule. Browse behavioural rules by domain at /rules?domain=network, or search the catalog for certutil.
Techniques referenced: T1105 (Ingress Tool Transfer) for the certutil rule, T1547.001 (Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder) for the Run key artifact, T1573 (Encrypted Channel) for the TLS fingerprint section.
Minimum fields to carry on every indicator record:
value the indicator itself
type sha256 | ipv4 | domain | url | registry | mutex | ja3 | ja4 | cert_sha256
source feed or report name, not "OSINT"
first_seen ISO 8601
valid_until ISO 8601, mandatory, set at ingest if the feed omits it
confidence 0-100
context campaign or malware family, one line of triage guidance
disposition block | alert | hunt_only | enrich_onlyTelemetry to collect so those indicators are searchable at all:
Sysmon EID 1 process creation, with CommandLine and Hashes
Sysmon EID 3 network connection, with Image and DestinationIp
Sysmon EID 12/13 registry key and value set
Sysmon EID 22 DNS query, with QueryName and QueryResults
Zeek ssl.log with the ja3 package installed (zkg install ja3)
Zeek x509.log certificate issuer, subject and serial
Suricata tls and quic events with ja3/ja4 fingerprints enabledRelated articles

What Is Operational Threat Intelligence?
AnyDesk, Fleetdeck.io, Level.io, Mimikatz, Ngrok, Pulseway, Screenconnect, Splashtop, Tactical.RMM, Tailscale, TeamViewer and Teleport.sh. That is Table 1 of CISA advisory AA23-320A, the legitimate…
2025-02-2110 min read

What Is an Indicator of Attack (IOA)?
A SHA-256 hash stops matching the moment the operator recompiles the payload. The way that payload opens LSASS does not change, because the API it needs has been in Windows since Windows XP. That gap…
2025-02-069 min read

What is an advanced persistent threat (APT)?
In one compromise, Volt Typhoon likely extracted NTDS.dit from three domain controllers over a four-year period. CISA, the NSA and the FBI published that detail in advisory AA24-038A in February…
2025-01-219 min read