Writing a Suricata rule for the double-letter alliterative C2 URL pattern
Following up on the Gamaredon URL-pattern observation from November — turning it into an actually-shippable Suricata rule, with false-positive notes.
Following up on the November post about UAC-0010 / Gamaredon’s bare-IP plain-HTTP beacon URL pattern, I spent a couple of January evenings tightening up a Suricata rule that catches the shape of the path without hard-coding the operator’s specific verb list. Here’s the writeup.
The pattern, restated
Pterodo HTML / VBS implants from late-2024 onwards beacon to URLs of the form:
http://<bare-IPv4>/<verb><suffix>?-<DD>-<MM>
Where:
<bare-IPv4>= a literal IP, no Host header pointing to a hostname. This is the high-fidelity bit — bare-IP HTTP traffic from a workstation to an external IP is almost never legitimate in 2025. (Cloud control planes, CRL distribution, and a few niche update mechanisms aside.)<verb>= a short alpha string. Observed:Svvr,SSsr,Akad,Akk,Gpps,Mouuds. Five of six are double-letter alliterative (vv,Ss,kk,pp,uu). The doubled-letter is consistent with Gamaredon’s well-documented alliterative-naming TTP — the same pattern producesriontos.ru-style apex names.<suffix>=Htm,Ua,U, or empty. Sometimes encodes campaign variant or victim cohort.<DD>-<MM>= the campaign date, baked into the URL path at generation time. Two-digit zero-padded.
The rule
alert http any any -> any any (
msg:"GAMAREDON Pterodo bare-IP beacon URL (alliterative path + DD-MM date)";
flow:established,to_server;
http.method; content:"GET";
http.host; pcre:"/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:80)?$/";
http.uri; pcre:"/^\/[A-Z][A-Za-z]{2,7}-\d{2}-\d{2}\/?$/";
classtype:trojan-activity;
sid:9001502;
rev:2;
metadata:mitre T1071.001 T1568.002;
reference:url,cert.gov.ua/article/...;
)
The two pcre clauses are the meaty bit:
http.hostmatches a bare IPv4 (with optional explicit:80). This is the hard-to-evade signal — Pterodo’s HTML/VBS landers are hard-coded to fetch bare-IP URLs, and changing that requires re-tooling.http.urimatches a path of: leading slash, capital letter, 2–7 more letters (mixed case), dash, two digits, dash, two digits, optional trailing slash. That covers the observed verbs and suffixes without enumerating them.
I deliberately did NOT enumerate the specific verb list (Svvr,
SSsr, etc.) — operators rotate verbs across campaigns, and a rule
keyed on a fixed list would fail closed on the next rotation. Better
to match the path shape and accept some false-positive volume.
False-positive notes
Things this rule will fire on that aren’t Pterodo:
- Cloud metadata endpoint requests that some misconfigured agents
make to literal
169.254.169.254(AWS, GCP) — the IP-only Host header is right, but those URIs are typically not in the<verb>-DD-MMshape, so the URI regex saves us. Negligible FP. - Internal IP-addressed health checks —
http://10.0.0.5/Health-01-15could match. Add a! $HOME_NETfilter onhttp.host. (My version scopes destination asanybecause home-lab purposes; for enterprise, scope it.) - CRL / OCSP distribution — some old CA setups serve CRLs from
bare IPs. URIs are typically
/crl/<name>.crl, not the<verb>-DD-MMshape, so the URI regex filters this out too. Low. - Misconfigured monitoring — Nagios / Icinga / Zabbix sometimes emit traffic to bare IPs with whatever URI the operator typed in. If you get a fire from this, it’s exactly because the agent is misconfigured and worth investigating regardless. Possible.
Tuning
In my home-lab (the Suricata-elk-lab Docker-Compose I shipped with my BSc thesis support repo), the rule fires on:
- Replayed Pterodo pcaps from MTA — yes, every time, as expected.
- A week of my own home-network traffic — zero hits.
nmapscans I run as part of CTF practice — zero hits (nmap doesn’t do<verb>-DD-MMURIs).curlto bare-IP API endpoints I happen to use — zero hits.
So in a low-noise environment, false positives look like zero. In a real SOC the actual answer depends on your environment — please tune before deploying.
Coverage gaps
Things this rule misses:
- Beacon URLs that don’t have the
-DD-MMsuffix — some Pterodo variants (older, e.g. early-2024) used path forms without the date. My rule won’t catch those. Different rule. - Variants with dotted-DD.MM or ISO date in the path — possible operator rotation. Adapt the regex.
- HTTPS-wrapped variants — if Gamaredon wraps the beacon in TLS, Suricata sees only the SNI/handshake, not the URI. Pterodo’s late-2024 wave is plain-HTTP-only, but that could change. The TLS-handshake-side detection is a separate rule and a harder problem.
Usefulness in context
For a small SOC (Brights-sized, ~50 staff) the rule is essentially
free to run. The bare-IP HTTP outbound is rare enough in modern
enterprise that it’s a high-confidence alert on its own; combining
with the URI regex makes it specific to the Pterodo shape. Cumulative
behavioural scoring makes this stronger — if a workstation also
recently downloaded an HTML attachment with a Scan_X_Y_Z_NNNN name,
you have a high-confidence Gamaredon detonation.
(Caveat: I’m a junior. This is rule-writing as a learning exercise, not as production guidance from someone with a decade of detection engineering. Run any rule through your own tuning loop before trusting it in alerts.)
Thanks
To Florian Roth’s signature-base project for being the canonical example of “how to structure a public-facing detection rule”. Most of the formatting choices here are imitations of his.