DIICOT · Chapter 27
Only the leader talks
The kit sat still on the disk, so I switched it on myself in a cage with no way out. Inside there's a bot that insists on knowing its own address before anything else, joins a mesh of up to two thousand nodes, holds an election and crowns itself leader — and only then opens a Telegram chat. The operator never logs into any machine: he sends a message to the head of the pack. And when you burn the chat, the botnet heals itself.
In the previous chapter the kit arrived intact and never started: it walked straight into a noexec mount. So what it does isn't something the decoy showed me — the lab had to show me.
Of the five modules it carried inside, the one in charge is the smallest: a shade over two megabytes, a file called cache. It mines nothing. Its job is to decide who mines and when, and for that it needs to receive orders. How it receives them is what this chapter is about.
01Before switching anything on
This gets detonated in a cage: a virtual machine inside its own network space, with no route to the internet, and the critter running as an unprivileged user. But "no way out" has to be proved every time, not assumed — if the isolation fails, what gets out is a real botnet node joining a real mesh.

The test is daft and that's why it works: try to reach a couple of places that always answer. If any of them does, nothing gets detonated and the cage gets fixed first.
02It insists on knowing where it is, or it dies
The first thing it does on starting isn't to call its master. It's to ask what its own public address is. And it tries three ways, in this order:
[WRN] self-hosted IP fail → 31.57.105.94:42 # a service of the attacker's own
[WRN] api4.ipify fail → … # public fallback
[WRN] ifconfig attempt 1..5/5 fail # public fallback, five times
[ERR] pub IP fail after all methods — exiting # and it shuts downSeven attempts, some fifty seconds, and if it doesn't manage it it shuts itself down. On a machine with no way out to the internet, this critter never gets to do anything at all.
The way out of the deadlock was to give it what it asked for: a fake service, inside the cage itself, handing back a made-up address from the ranges reserved for documentation. With that it believes it and unfolds everything. Note the asymmetry: to watch it work you don't have to let it out, you have to lie to it.
03It crowns itself leader
With its address in hand, it builds the network. And this is where it stops looking like a miner:

Four things happen in that start-up, and each one adds a piece:
It listens on port 8081 and announces itself there. It isn't a client calling home: it's a node that also receives.
It carries a bootstrap neighbour hardcoded — one specific address to call the first time, to get into the network. It's the chicken-and-egg problem of every peer-to-peer network: to meet somebody you have to know somebody already.
It aims for two thousand connections and, if it has few, it goes looking for more on its own. The mesh isn't decoration: it's sized.
And it holds an election. The node starts out as a follower, and the moment it sees there's nobody above it, it proclaims itself chief. Then, and only then, does it open the channel to its operator.
Here only one talks to the outside, and the rest find out through the mesh. The operator sends a message and the order reaches thousands of machines without him connecting to any of them. And if the leader falls, the mesh elects another. For anyone defending, the uncomfortable consequence is that you can have a node of this on your network and never see it make a single suspicious connection: your machine only talks to other victims.
04Why strings says nothing
The normal move with a critter like this is to run the tool that pulls the readable text out of the file and watch addresses, domains and paths appear. Here nothing comes out: not the chat domain, not the bot's identifier, not one of the words you've just seen on screen.
The reason is the obfuscator it was compiled with. It stores every string encrypted in the file and decrypts it in memory just before using it, with a two-line loop that mixes a table and a seed that also travel inside.

There are two ways round that. The slow one is to read the code and undo the encryption by hand, table by table. The fast one is to let it do the decrypting: start it, freeze it mid-run and read its memory. There they all are, already in the clear, because the program needs to use them.
It's the most reusable lesson in the chapter: an obfuscator protects the file, not the execution. Anything the program needs to understand, it will have to decrypt — and at that moment it's in plain view of whoever is looking.
05Who gives the orders, and the gate on the door
Out of memory comes the channel, and it's about as convenient as they get: a Telegram bot. The leader node asks every few seconds whether there are new messages, with a request that carries the bot's identifier inside it:
GET https://api.telegram.org/bot8778142498:AAE2YhxC6AB5PF8GOucHxCviYV4FA1JJnIE/getUpdates?offset=0&timeout=30
Host: api.telegram.org
User-Agent: skemaAs a design decision it has its charm. The traffic goes to a legitimate domain that half the world passes through, encrypted, and there's no server of their own to take down: as long as Telegram works, the channel works. That skema at the end is the only thing out of place — a browser identifier that looks like nothing else, and which is exactly why it works as a signature.
And there's a piece of craft in there: it doesn't use the machine's name server. It brings its own along and resolves the domain by itself, skipping the system's. Anyone watching their network by looking at which names each machine asks for will never see this one ask.
The next thing was the obvious question: if the channel is public and the identifier is in plain sight, can anybody send it orders? I injected commands from a fake Telegram set up inside the cage. Answer:
{"chat_id":…,"text":"Unauthorized."}There's a gate. The bot compares who sent the message against a number stored inside it and, if it doesn't match, it does nothing. Reading it meant going down into the code and then into the running process: the number lives in one specific field of its internal structure, and there it was.

06The repertoire, and what it calls itself
Past the gate, the bot obeys. This is its full repertoire:
/peers lists the neighbours it knows
/connect ip:port adds one by hand
/leader ip appoints who is in charge
/hub ip appoints a supernode: everyone converges there
/check ip is this machine on the mesh?
/update hands a new binary around the mesh ← the important oneThat /check is worth a pause. It's there to ask the network whether a specific address is infected. It's the tool of an operator who wants to know whether he's already inside a target before spending effort on it — or whether he's lost it.
And when you ask it for its status, the bot introduces itself. This is what it answers:
DIICOT-BOTNET
Nodes : 1 | Cores: 4 | Miners: 0
RAM : 3.8 GB | Disk: 19.6 GB
Ver : v2-update-1There's the signature, and no antivirus or community label handed it to me: the program says its own name. It carries the family name written into its control panel, next to the count of nodes, cores and active miners — a dashboard for whoever is collecting.
And it comes with a version number. v2-update-1: second generation, first update. Somebody is keeping count.
07The masterstroke: it heals itself
That leaves the important command. /update doesn't download anything from outside. It does something rather more elegant:

The node opens its own file, splits it into more than seventeen hundred pieces and hands them around the mesh. The other nodes put them back together, keep the new version and restart themselves. There's no download server, no domain to block, not one connection to the outside: the binary travels from victim to victim.

And now put that together with what came before, because this is where it all fits. The Telegram bot's identifier is in plain sight of anyone who opens the sample — and Telegram cancels the ones that leak. When that happens, the channel dies. It would look like the end.
It isn't, because the mesh doesn't depend on Telegram. The nodes carry on talking to each other, and the encryption on that channel uses the identifier they carry burned in as its seed: it still works as a key even once Telegram stops accepting it. The operator takes command back that way, fires an /update with a fresh build —with a fresh identifier inside— and the whole network renews itself.
To really take this down you have to go to the nodes, one by one. There's no central plug to pull — which is exactly what it was designed this way for.
Which leaves an uncomfortable question about the sample in front of me: its identifier no longer works. It's been cancelled. And in a freshly caught critter, that can mean two very different things — that the campaign is dead, or that it was renewed a while ago and this is an abandoned build.
08Indicators (IOCs)
The ones from the arrival are in chapter 26. These are the command ones.
| Type | Value |
|---|---|
| Mesh port | TCP 8081 — listens and announces itself; target 2,000 connections |
| Bootstrap neighbour | 91.92.47.220:8081 (NL, ASN 197170 TechTies; Shodan also lists it as a scanner) |
| "What's my IP" service | 31.57.105.94:42 · fallbacks api4.ipify.org and ifconfig.me |
| Command channel | api.telegram.org/bot<id>/getUpdates?offset=N&timeout=30 — with its own DNS resolver |
| Bot identifier | 8778142498:AAE2YhxC6AB5PF8GOucHxCviYV4FA1JJnIE (already cancelled) |
| User-Agent | skema — looks like no browser at all: a good network signature |
| Operator's number | 6059167279 — the only one authorised to give orders |
| Commands | /peers /connect /leader /hub /check /update |
| Identifies itself as | DIICOT-BOTNET, version v2-update-1 |
| Peer file | peers.dat, with the seed p2p-peers-salt-v1 |
| Miners it drives | xmrig · ccminer · nbminer · gminer · bminer · t-rex and the algorithms randomx · kawpow · etchash |
The cheapest detection of all: a machine that listens on 8081 and talks to arbitrary other machines over that port. A normal server doesn't do that. And if it also heads out to Telegram with an agent called skema, there's no doubt left.
To be continued — two loose ends are left, and both are about money. The first: how I know the identifier has been cancelled — I didn't infer it, I checked it. And the second, the one that really matters — this kit carries two miners and neither of them has the wallet to pay into. The mining config doesn't travel with the critter: it pulls it down afterwards, from somewhere you have to go and find. I went. In Chapter 28. 🍯
Comments