1,001 IPs, 64 countries, one operation: mapping a botnet by its back end · HoneyLabs blog
Blog · 2026-05-29<br>1,001 IPs, 64 countries, one operation: mapping a botnet by its back end<br>A single attacking IP tells you little. The back end it pulls its payload from, and the client fingerprint it presents, are the parts operators reuse. Correlating both across the sensor network collapses internet noise into discrete operations: one cluster of 1,001 IPs across 306 networks and 64 countries, tied to eight shared staging servers and a single TLS and HTTP fingerprint that appears nowhere else, plus smaller botnets that fall into clean separate islands. With node graphs.<br>A single attacking IP does not tell you much on its own. It is one compromised box out of a sea of them, and by the time it reaches your logs it has usually been cleaned, reassigned, or rotated out. The thing that identifies an operation is the part the operator reuses, and the part they reuse most is the back end: the server the payload gets pulled from once the exploit lands.
We can chase that across the whole sensor network because we keep the payloads and not only the connections. Every dropper that comes through carries the URL of its next stage somewhere, sometimes in the clear after a wget, sometimes inside a base64 blob in a PHP shell_exec. If you pull that URL out of every dropper, link each attacking IP to the servers it fetched from, and then connect any two servers that share delivering IPs, the internet's background noise starts to separate into discrete operations.
one operation, a thousand nodes
Run that over the last hundred days and the biggest cluster that falls out looks like this.
That is 1,001 source IPs bound to eight staging servers, the orange hubs. The IPs sit in 306 different autonomous systems across 64 countries, which is exactly why you would never connect them by looking at addresses. What ties them together is the back end. The eight servers behave as one pool, because the same delivering IPs pull from many of them. The two busiest, 31.57.216.121 and 46.151.182.82, share 51 delivering IPs between them, 178.16.55.224 and 31.57.216.121 share 45, and the two servers hosted on Korea Telecom (125.135.169.171 and 14.46.136.77, both AS4766) share 38. The IPs that fetch from more than one server are the glue, and they are why this reads as one campaign rather than eight unrelated ones.
The exploit underneath it is old and dull. It is Apache path traversal, CVE-2021-41773 and its %%32%65 double-encoded cousin, hitting /cgi-bin/.../bin/sh on ports 80 and 443. The payload is a self-replicating shell stage the operators tag apache.selfrep, so every server it lands on becomes another node that scans and re-delivers. It has run at a steady 70 to 125 distinct IPs a week for the whole window, with a small lift in early May.
the fingerprints agree
The shared back end is one way to draw the boundary. The TLS handshake is a second, and it lands on the same population. Of the IPs in this cluster that negotiate TLS, 655 present the exact same JA4 fingerprint, t13i170900_5b57614c22b0_78e6aca7449b, and that value appears on no other IP anywhere else in the dataset over the same period. The HTTP fingerprint tells the same story: 687 of them share one JA4H, po11nn0700_c5a94e7539c9, again unique to this set. Two independent signatures, the staging pool and the client build, draw the same outline, which is about as good as honeypot data gets for saying a thousand scattered addresses are running one tool.
The diamonds in the graph are those fingerprints, and the cyan dots are the IPs that carry them. A smaller green island hangs off to one side. About a tenth of these nodes also turn up brute-forcing SSH, and 102 of them do it with a single shared SSH client fingerprint (HASSH 19532158b559096b89b1a5f7d17175b2). That HASSH is the softer of the three links, since it also shows up on roughly 120 addresses outside this cluster, so it is better read as a common brute-force client that this campaign happens to use than as a signature unique to it.
the small operations separate just as cleanly
The same method pulls the small fish apart instead of lumping them together.
The graph breaks into four islands. One is a multi-architecture Mirai variant whose binaries are named manji.arm7, manji.mips, manji.x86 and xd.x86, served from two hosts in the same 94.156.152.0 block. Another serves nullnet_load.mips and memory_load.mips from a separate pair. A third pulls heilong.mips, which is Chinese for black dragon, from a host paired with a DuckDNS name, 69sexy.duckdns.org, the kind of free dynamic-DNS C2 that small operators lean on. The fourth quietly fetches a plain install.sh from file.gpu5.com. None of them connect to each other or to the big cluster, because none of them share delivering IPs or fingerprints. That is the same test running in the other direction: when operations really are separate, the graph leaves them separate.
why this works,...