Security Incident Report: XMRig Cryptominer
A persistent Monero miner that kept coming back — even after deleting its files, killing its process, and changing the root password. The culprit turned out to be a malicious kernel module.
1. Executive Summary
On 2026-08-29 I detected and fully eradicated a persistent XMRig cryptocurrency miner from my Proxmox VE 8 host (kernel 7.0.14-8-pve). The infection was more sophisticated than a typical miner: it used an unsigned, out-of-tree kernel module (nf_conntrack_kernel) as a self-healing re-dropper that re-created the miner binary and relaunched it from kernel space every ~18 minutes — surviving process kills, file deletion, password rotation, and every userspace-level cleanup.
Root cause was a WAN-exposed Proxmox management interface (pveproxy, port 8006) combined with a weak root password, which granted the attacker full root access.
Every component of the infection was mapped and removed. This report documents the technical details, indicators of compromise (IOCs), and the remediation steps taken.
2. Incident Timeline
| Time (CST) | Event |
|---|---|
| ~Aug 20 | Initial compromise; attacker stages payloads under /var/tmp/xmrig/ |
| ~Aug 22 | Attacker adds a (malformed) persistence entry to root’s crontab |
| Aug 29 ~18:11 | Host reboots; miner reappears shortly after (~18:36), proving boot-time persistence |
| Aug 29 ~19:14 | Miner killed, binaries deleted, LD_PRELOAD injection stripped |
| Aug 29 ~19:18 | Miner respawns — first clue that a re-dropper was active |
| Aug 29 ~19:24 | Root password rotated |
| Aug 29 ~19:36 | Miner respawns again — confirming the re-dropper is on-box, not the attacker reconnecting |
| Aug 29 ~19:49 | Malicious kernel module nf_conntrack_kernel identified, unloaded, and removed; cleanup verified |
3. Root Cause
The host’s Proxmox web management interface (pveproxy, port 8006) was exposed to the WAN, and the root account used a weak password. The attacker obtained root credentials, then gained a root shell via the Proxmox root@pam authentication path.
This single point of failure let the attacker install a kernel-level rootkit — which is why the infection proved so resilient to conventional cleanup.
4. Infection Chain
4.1 Payloads
The attack dropped four distinct components:
| Component | Path | Details |
|---|---|---|
| Kernel-module dropper | /lib/modules/7.0.14-8-pve/kernel/net/ipv4/netfilter/nf_conntrack_kernel.ko |
337,792 B; unsigned (taint OE); version 0.1; no description/author; exposes params worker, delay, path |
| LD_PRELOAD hider | /usr/local/lib/kthreadd32.so |
16,536 B; MD5 a964ca6f9e18a7ec30c047ddff02b458; hooks readdir/readdir64 to hide processes named “kthreadd64” |
| Miner binary | /sbin/kthreadd64, /usr/sbin/kthreadd64 |
8,297,712 B; static, stripped ELF; MD5 8f4fff0ded94f1141768220906abfbb8 |
| Staging directory | /var/tmp/xmrig/ |
config.json, miner.log, SHA256SUMS, xmrig (8,350,992 B, XMRig 6.26.0) |
4.2 Persistence
The miner persisted through two layers:
/etc/profile.d/bash_completion.shhad an injectedexport LD_PRELOAD="/usr/local/lib/kthreadd32.so"— loading the hider library into new shells to conceal the miner process fromps/ls./etc/modulescontained the linenf_conntrack_kernel(prefixed by a fake# AUTO GENERATED BY KERNEL SYSTEM MODULEcomment). At boot,systemd-modules-loadloaded the kernel module, which usedcall_usermodehelper()to periodically re-drop the miner binary and relaunch it.
The second layer is what made the infection self-healing: because the miner was re-created and executed from the kernel, it was invisible to process-list and file-level cleanup. It also lived in the root cgroup (0::/) with a near-empty environment — the classic signature of call_usermodehelper().
4.3 Mining Configuration
The miner connected to two pools with the command line:
/sbin/kthreadd64 --cpu-priority=5 --donate-level=0 \
-o 89.117.109.224:143 \
-u 4B7vsy8ccUwQufiyMN9jgoDphPUDzGUvBhE4f19U5z3WMPZqx2gjHrv2PxpuBSZRHAdD5qfEnPiApdFk4fhHZGVwU1YG1L2.worker86/[email protected] \
--coin=monero -k
5. Indicators of Compromise (IOCs)
Files
| IOC | Hash / Size |
|---|---|
nf_conntrack_kernel.ko |
337,792 B, unsigned (out-of-tree) |
/usr/local/lib/kthreadd32.so |
MD5 a964ca6f9e18a7ec30c047ddff02b458 |
/sbin/kthreadd64, /usr/sbin/kthreadd64 |
MD5 8f4fff0ded94f1141768220906abfbb8 |
Network & Identity
| Type | Value |
|---|---|
| Mining pool | 89.117.109.224:143 |
| Mining pool | 107.155.109.94:443 |
| Monero wallet | 4B7vsy8ccUwQufiyMN9jgoDphPUDzGUvBhE4f19U5z3WMPZqx2gjHrv2PxpuBSZRHAdD5qfEnPiApdFk4fhHZGVwU1YG1L2 |
| Attacker email | [email protected] |
| Worker ID | worker86 |
6. Remediation
The following actions fully neutralized the infection:
- Unloaded the kernel module —
rmmod nf_conntrack_kernel. - Removed boot persistence — deleted the module entry from
/etc/modules. - Deleted the
.kofile and rebuilt the module index (depmod -a). - Blacklisted the module in
/etc/modprobe.d/(blacklist+install … /bin/false) so it cannot be loaded again. - Killed the miner and deleted
/sbin/kthreadd64and/usr/sbin/kthreadd64. - Removed the LD_PRELOAD hider (
kthreadd32.so) and stripped the injection frombash_completion.sh. - Removed the staging directory
/var/tmp/xmrig/. - Blocked the C2 IPs via a persistent systemd unit (
block-mining-ips.service), using iptables on both INPUT and OUTPUT. - Rotated the root password.
7. Lessons Learned & Recommendations
- Never expose the Proxmox management interface (port 8006) to the WAN. Access it over a VPN or SSH tunnel instead.
- Use strong, unique passwords — or better, key-based authentication and 2FA — for any internet-facing service.
- A root compromise cannot be trusted away. A kernel-level rootkit means the attacker had full control of the host; the only way to be truly certain is a clean reinstall from known-good media.
- Rotate all secrets that lived on the compromised host — API tokens, credentials, and keys stored on disk should all be considered exposed.
- Defense in depth: even a strong password is no substitute for network-layer access control.