Skip to content

MMD-071-2026 KinsingBot a "bad-bad"

Malwaremustdie.Blogspot •Unknown ([email protected]) • November 11, 2026

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.

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.

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.

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.

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

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 x shows file offsets).

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 .

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.

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.

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.

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 header too: Server: nginx/1.14.2 and Last-Modified help you fingerprint and date the attacker's server.

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.

Content-Type: text/plain on 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.

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.

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

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.

lsof -p 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/ /fd/ while the process runs. Grab it before you kill the process!

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.

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)

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 the initial-access exploit used in that wave; verify it before you write it as fact.

The low detection in VirusTotal is commonly came-up if a Linux (ELF) or Unix-like malware that was firstly spotted checked, there are limitation in behavioral aspects and supported detection topology or different range of known detection technologies that is often hard to apply in UNIX series of binaries due to variation in building, different metadata than Endpoint binaries, which where VirustTotal is good at. The only effective way is to upload and explain as many samples as we can get to build a good reference and dataset to help others who face the similar threat to know what we are dealing with.

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

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 》

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

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.

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 to the C2: the "up" handshake and getting tasks

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 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_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 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

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

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 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 / /nook/go/src/my/bot and the build timestamp (2019-12-29).

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

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_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 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 and checks the . If the contains OK , the hit is reported back to the C2 through addResult() .

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.

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 》

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.

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

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 the bot and its operator.

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

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 .gopclntab even 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 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 /app with target /go/bin/bot : a container-based build (the official Go Docker image layout). The v28 build still showed the developer's path / /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 1000 and device 2066 , 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:

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:

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

■ 2. Version and what is new in v31

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 = 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

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

The C2 address is no longer a plain string: it is hex-encoded data decrypted by main.healthChecker with a rotator. Static string 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

■ 7. C2 callbacks and HTTP requests

v31 splits the protocol: /mg is the first call-in, /ms reports the miner PID (12 encrypted bytes), /get polls for tasks. A 404 on /get simply 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-unix and /var/tmp/.ICEd-unix , and writes uuid .

It profiles the host: it starts lsb_release and later kills that child process.

First call-in GET /mg ; the is a short encrypted blob.

It kills any copy of its miner by name , writes the embedded miner to /tmp/ 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 with POST /ms .

The miner reads /proc/cpuinfo , /proc/mounts and 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 /mg cycle re-drops and restarts it. GET /get answered with 404 : 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 lsof as (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

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

■ 11. The mass-scan script carried by v31

■ 12. Important functions (.gopclntab) and host profiling

■ 13. Debug view: lsof during the run

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

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:

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

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

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 the family in general (public reporting). Both are useful, but they carry different confidence.

《 21. CTI: Actors and attribution 》

Attribution in CTI is clustering activity with stated confidence, not naming people. Below is what this analysis recorded, compared with how the industry names the same activity.

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.

■ Source reliability and information credibility (Admiralty code)

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 》

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

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.

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.

■ Additional techniques in later builds (a71ad3 / v31)

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)

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

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.

■ IOC additions in v5 (all spotted in the wild attack)

■ Copy-friendly IOC list (defanged)

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.

Follow the order of volatility (RFC 3227): memory and network state first, disk later. A reboot wipes RAM, the /proc view 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 ).

If /etc/ld.so.preload exists and points to an unknown library, assume the normal tools are lying to you. Use static tools and read /proc directly.

■ Hot forensics 1 — Triage: find the bot and the miner

A Go bot shows as one process with many threads ( 5*[{kinsing}] ). Count threads with ls /proc/ /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

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/ /fd/12 while the process lives. Do it now; it is gone after the kill.

■ Hot forensics 3 — /proc copies and a short strace

Always use -f with Go malware: the network work happens in other threads, and without -f you 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

All the memory evidence in this post (C2 replies, the decrypted download_and_exec task, the RSA key, the scripts) came from the Go heap at 0xc000000000 . Knowing where a runtime keeps its heap makes memory forensics much faster.

SIGSTOP instead of SIGKILL : 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_scope blocks it, run as root or temporarily lower the setting, and write that change in your notes.

■ Hot forensics 5 — Persistence and lateral-movement scope

The spreader script builds its target list from known_hosts , ssh config, /etc/hosts and bash history. Read the same files and you have the list of hosts to check . 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

Mount with ro,noexec,nodev and, for ext4, noload ; without it, even a read-only mount can write to the image by replaying the journal.

/tmp is often tmpfs: the work directory /tmp/.ICEd-unix/ and /tmp/kdevtmpfsi may not exist in the disk image. That is why the hot phase matters.

■ Cold forensics 2 — Timeline and artifacts

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 2020 in 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/ /exe , the core dump and the carved files). Each screenshot shows the commands; the outputs are the ones recorded in this analysis.

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/ /exe for 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 , list the maps, then write each region to a file:

dm lists the memory maps; seek to a map and write size bytes with wt ( wtf in 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

The full query pack is in the detection content chapter.

■ Tabletop exercise (30 minutes)

■ Who does what (RACI)

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

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

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

Example: the network rule that survived from v28 to v31, because the bot's header set never changed:

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 likely victims. For YARA / Sigma / Snort / Suricata / osquery / STIX detection content, see the detection content chapter.

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.