/* * perfctl.yar — YARA rules for perfctl/perfcc Linux userland rootkit family * and adjacent threats commonly seen on the same compromised hosts * (cryptominers, proxyjacking agents, LD_PRELOAD rootkits, SSH backdoors). * * Usage with clean.sh: * YARA_RULES=/srv/perfctl.yar ROOTFS=/vz/root/101 ./clean.sh * * Or standalone: * yara -r /srv/perfctl.yar /mnt/target-rootfs/ * * SOURCES: * - Aqua Nautilus, "perfctl: A Stealthy Malware Targeting Millions of Linux * Servers" (October 2024) — full IoC table: file paths, hashes, IPs, * domains, env vars. https://www.aquasec.com/blog/perfctl-a-stealthy- * malware-targeting-millions-of-linux-servers/ * - Florian Roth / Nextron Systems, signature-base, XMRig + cryptominer * rules. https://github.com/Neo23x0/signature-base — DRL 1.1 licensed, * attribution retained in meta. * - Yara-Rules community repo (MALW_XMRIG_Miner). * https://github.com/Yara-Rules/rules * - bleepingcomputer / thehackernews / darkreading perfctl writeups * (Oct 2024) for independent IoC confirmation. * - Remaining rules (proxyjacking agents, PAM stealer, LKM names, * perfctl-specific binary rules) written for this file based on the * published IoCs — no equivalent public rules exist as of April 2026. */ import "hash" // ========================================================================= // perfctl / perfcc rootkit family — specific rules // ========================================================================= rule perfctl_known_hashes { meta: description = "File matches a known perfctl/perfcc MD5 from Aqua IoC table" author = "clean.sh incident response" family = "perfctl" severity = "critical" reference = "https://www.aquasec.com/blog/perfctl-a-stealthy-malware-targeting-millions-of-linux-servers/" condition: // main malware hash.md5(0, filesize) == "656e22c65bf7c04d87b5afbe52b8d800" or // cryptominer hash.md5(0, filesize) == "6e7230dbe35df5b46dcd08975a0cc87f" or // rootkit (libgcwrap.so) hash.md5(0, filesize) == "835a9a6908409a67e51bce69f80dd58a" or // trojanized ldd hash.md5(0, filesize) == "cf265a3a3dd068d0aa0c70248cd6325d" or // trojanized top hash.md5(0, filesize) == "da006a0b9b51d56fa3f9690cf204b99f" or // wizlmsh persistence binary hash.md5(0, filesize) == "ba120e9c7f8896d9148ad37f02b0e3cb" } rule perfctl_libgcwrap_rootkit { meta: description = "perfctl userland rootkit — LD_PRELOAD shared object" author = "clean.sh incident response" family = "perfctl" severity = "critical" reference = "https://www.aquasec.com/blog/perfctl-a-stealthy-malware-targeting-millions-of-linux-servers/" strings: $soname = "libgcwrap.so" // operator-bypass env vars (from Aqua IoC table) $env1 = "AAZHDE" $env2 = "FPROF" $env3 = "A2ZNODE" $env4 = "VEI" $env5 = "ABWTRX" $hide1 = "perfctl" $hide2 = "perfcc" $hook1 = "readdir" $hook2 = "readdir64" $hook3 = "__xstat" $hook4 = "__lxstat" $hook5 = "getdents" $hook6 = "getdents64" $preload = "/etc/ld.so.preload" condition: uint32(0) == 0x464C457F and filesize < 5MB and ( $soname or ( 1 of ($env*) and 3 of ($hook*) ) or ( any of ($hide*) and $preload ) ) } rule perfctl_perfcc_installer { meta: description = "perfctl main installer / dropper binary (perfcc)" author = "clean.sh incident response" family = "perfctl" severity = "critical" strings: $n1 = "perfcc" $n2 = "perfctl" $n3 = "libgcwrap" $d1 = "/tmp/.xdiag" $d2 = "/tmp/.perf.c" $d3 = "/tmp/.dmesg" $d4 = "/tmp/smpr" $d5 = "/root/.config/cron/perfcc" $p1 = "ld.so.preload" $p2 = "AAZHDE" $p3 = "FPROF" $p4 = "A2ZNODE" $p5 = "ABWTRX" condition: uint32(0) == 0x464C457F and filesize < 50MB and ( 2 of ($n*) or (1 of ($n*) and any of ($d*)) or (any of ($p*) and any of ($d*)) ) } rule perfctl_wizlmsh_suid_backdoor { meta: description = "perfctl SUID backdoor (wizlmsh / kkbush / zipgrepjournalctl)" author = "clean.sh incident response" family = "perfctl" severity = "critical" strings: $n1 = "wizlmsh" $n2 = "kkbush" $n3 = "zipgrepjournalctl" $s1 = "setuid" $s2 = "execve" $s3 = "/bin/sh" condition: uint32(0) == 0x464C457F and filesize < 5MB and any of ($n*) and 2 of ($s*) } rule perfctl_dropper_paths_in_any_file { meta: description = "File of any type references known perfctl dropper paths" author = "clean.sh incident response" family = "perfctl" severity = "high" strings: $p1 = "/tmp/.xdiag" $p2 = "/tmp/.perf.c" $p3 = "/tmp/.apid" $p4 = "/tmp/gd.sh" $p5 = "/tmp/ccrl" $p6 = "/tmp/javax64" $p7 = "/tmp/kubeupd" $p8 = "/tmp/wttwe" $p9 = "/tmp/smpr" $p10 = "/dev/shm/libfsnldev.so" $p11 = "libfsnldev.so" $p12 = "libfsnkdev.so" // variant spelling seen in Aqua IoC $p13 = "libpprocps.so" $p14 = "/root/.config/cron/perfcc" condition: filesize < 20MB and 2 of them } rule perfctl_cron_persistence { meta: description = "Cron job referencing perfctl/perfcc persistence" author = "clean.sh incident response" family = "perfctl" severity = "high" strings: $c1 = "perfcc" $c2 = "perfctl" $c3 = "perfclean" $c4 = "libgcwrap" $c5 = "AAZHDE" $c6 = "FPROF" $c7 = "A2ZNODE" $c8 = "ABWTRX" $cron1 = "* * * * *" $cron2 = "@reboot" $cron3 = "@hourly" $cron4 = "@daily" condition: filesize < 100KB and 1 of ($cron*) and 1 of ($c*) } rule perfctl_known_filenames { meta: description = "File matches a known perfctl IoC filename (catch-all)" author = "clean.sh incident response" family = "perfctl" severity = "critical" strings: $n1 = "libgcwrap.so" $n2 = "libfsnldev.so" $n3 = "libfsnkdev.so" $n4 = "libpprocps.so" $n5 = "wizlmsh" $n6 = "kkbush" $n7 = "kubeupd" $n8 = "perfcc" $n9 = "perfctl" $n10 = "perfclean" $n11 = "zipgrepjournalctl" $n12 = "javax64" condition: filesize < 100MB and any of them } rule perfctl_network_indicators { meta: description = "Binary or script references published perfctl IPs / C2 domains" author = "clean.sh incident response" family = "perfctl" severity = "high" reference = "Aqua Nautilus IoC table (Oct 2024)" strings: // C2 / download servers $ip1 = "211.234.111.116" $ip2 = "46.101.139.173" $ip3 = "104.183.100.189" $ip4 = "198.211.126.180" // Tor relay nodes the malware connects to $ip5 = "80.67.172.162" $ip6 = "176.10.107.180" $ip7 = "78.47.18.110" $ip8 = "95.217.109.36" $ip9 = "145.239.41.102" // proxyjacking SaaS domains the malware installs clients for $d1 = "bitping.com" $d2 = "earn.fm" $d3 = "speedshare.app" $d4 = "repocket.com" condition: // require ELF or shebang — rule is IoC-aware, not a plain grep ( uint32(0) == 0x464C457F or uint16(0) == 0x2123 ) and filesize < 10MB and any of them } // ========================================================================= // XMRig / cryptominer — Neo23x0 signature-base (DRL 1.1, Florian Roth) // ========================================================================= rule XMRIG_Monero_Miner : HIGHVOL { meta: description = "Detects Monero mining software (XMRig)" license = "Detection Rule License 1.1 https://github.com/Neo23x0/signature-base/blob/master/LICENSE" author = "Florian Roth (Nextron Systems)" reference = "https://github.com/xmrig/xmrig/releases" date = "2018-01-04" modified = "2022-11-10" hash1 = "5c13a274adb9590249546495446bb6be5f2a08f9dcd2fc8a2049d9dc471135c0" hash2 = "08b55f9b7dafc53dfc43f7f70cdd7048d231767745b76dc4474370fb323d7ae7" hash3 = "f3f2703a7959183b010d808521b531559650f6f347a5830e47f8e3831b10bad5" hash4 = "0972ea3a41655968f063c91a6dbd31788b20e64ff272b27961d12c681e40b2d2" id = "71bf1b9c-c806-5737-83a9-d6013872b11d" strings: $s1 = "'h' hashrate, 'p' pause, 'r' resume" fullword ascii $s2 = "--cpu-affinity" ascii $s3 = "set process affinity to CPU core(s), mask 0x3 for cores 0 and 1" ascii $s4 = "password for mining server" fullword ascii $s5 = "XMRig/%s libuv/%s%s" fullword ascii condition: ( uint16(0) == 0x5a4d or uint16(0) == 0x457f ) and filesize < 10MB and 2 of them } rule XMRIG_Monero_Miner_Config { meta: description = "XMRig config.json file" license = "Detection Rule License 1.1" author = "Florian Roth (Nextron Systems)" reference = "https://github.com/xmrig/xmrig/releases" date = "2018-01-04" id = "374efe7f-9ef2-5974-8e24-f749183ab2d0" strings: $s2 = "\"cpu-affinity\": null, // set process affinity to CPU core(s), mask \"0x3\" for cores 0 and 1" fullword ascii $s5 = "\"nicehash\": false // enable nicehash/xmrig-proxy support" fullword ascii $s8 = "\"algo\": \"cryptonight\", // cryptonight (default) or cryptonight-lite" fullword ascii condition: ( uint16(0) == 0x0a7b or uint16(0) == 0x0d7b ) and filesize < 5KB and 1 of them } rule PUA_LNX_XMRIG_CryptoMiner { meta: description = "Detects XMRIG CryptoMiner (Linux ELF)" license = "Detection Rule License 1.1" author = "Florian Roth (Nextron Systems)" reference = "Internal Research" date = "2018-06-28" modified = "2023-01-06" hash1 = "10a72f9882fc0ca141e39277222a8d33aab7f7a4b524c109506a407cd10d738c" id = "bbdeff2e-68cc-5bbe-b843-3cba9c8c7ea8" strings: $x1 = "number of hash blocks to process at a time (don't set or 0 enables automatic selection o" fullword ascii $s2 = "'h' hashrate, 'p' pause, 'r' resume, 'q' shutdown" fullword ascii $s3 = "* THREADS: %d, %s, aes=%d, hf=%zu, %sdonate=%d%%" fullword ascii $s4 = ".nicehash.com" ascii condition: uint16(0) == 0x457f and filesize < 8000KB and ( 1 of ($x*) or 2 of them ) } rule SUSP_XMRIG_String { meta: description = "Suspicious XMRIG string in executable" license = "Detection Rule License 1.1" author = "Florian Roth (Nextron Systems)" date = "2018-12-28" modified = "2026-04-18 (clean.sh — require ELF/PE magic to reduce FP)" id = "8c6f3e6e-df2a-51b7-81b8-21cd33b3c603" strings: $x1 = "xmrig.exe" fullword ascii $x2 = "XMRig" fullword ascii condition: // only flag executables, not arbitrary text files ( uint16(0) == 0x457f or uint16(0) == 0x5a4d ) and filesize < 10MB and 1 of them } rule CoinMiner_Strings : SCRIPT HIGHVOL { meta: description = "Detects mining pool protocol strings in executable" license = "Detection Rule License 1.1" author = "Florian Roth (Nextron Systems)" reference = "https://minergate.com/faq/what-pool-address" date = "2018-01-04" modified = "2021-10-26" id = "ac045f83-5f32-57a9-8011-99a2658a0e05" strings: $sa1 = "stratum+tcp://" ascii $sa2 = "stratum+udp://" ascii $sa3 = "stratum+ssl://" ascii $sb1 = "\"normalHashing\": true," condition: filesize < 3000KB and 1 of them } rule PUA_CryptoMiner_cpuminer { meta: description = "cpuminer-style crypto miner" license = "Detection Rule License 1.1" author = "Florian Roth (Nextron Systems)" date = "2019-01-31" hash1 = "ede858683267c61e710e367993f5e589fcb4b4b57b09d023a67ea63084c54a05" id = "aebfdce9-c2dd-5f24-aa25-071e1a961239" strings: $s1 = "Stratum notify: invalid Merkle branch" fullword ascii $s2 = "-t, --threads=N number of miner threads (default: number of processors)" fullword ascii $s3 = "User-Agent: cpuminer/" ascii $s4 = "hash > target (false positive)" fullword ascii $s5 = "thread %d: %lu hashes, %s khash/s" fullword ascii condition: filesize < 5000KB and 1 of them } rule PUA_Crypto_Mining_CommandLine_Indicators : SCRIPT { meta: description = "Command-line flags used by crypto miners" license = "Detection Rule License 1.1" author = "Florian Roth (Nextron Systems)" reference = "https://www.poolwatch.io/coin/monero" date = "2021-10-24" id = "afe5a63a-08c3-5cb7-b4b1-b996068124b7" strings: $s01 = " --cpu-priority=" $s02 = "--donate-level=0" $s03 = " -o pool." $s04 = " -o stratum+tcp://" $s05 = " --nicehash" $s06 = " --algo=rx/0 " // base64'd versions $se1 = "LS1kb25hdGUtbGV2ZWw9" $se2 = "0tZG9uYXRlLWxldmVsP" $se3 = "tLWRvbmF0ZS1sZXZlbD" $se4 = "c3RyYXR1bSt0Y3A6Ly" $se5 = "N0cmF0dW0rdGNwOi8v" $se6 = "zdHJhdHVtK3RjcDovL" $se7 = "c3RyYXR1bSt1ZHA6Ly" $se8 = "N0cmF0dW0rdWRwOi8v" $se9 = "zdHJhdHVtK3VkcDovL" condition: filesize < 5000KB and 1 of them } rule linux_miner_pool_urls { meta: description = "Binary or config references known Monero mining pools" author = "clean.sh incident response" family = "cryptominer" severity = "medium" strings: $p1 = "pool.supportxmr.com" $p2 = "xmr.nanopool.org" $p3 = "xmr.2miners.com" $p4 = "xmrpool.eu" $p5 = "monerohash.com" $p6 = "monero.crypto-pool.fr" $p7 = "minexmr.com" $p8 = "moneroocean.stream" $p9 = "pool.hashvault.pro" $p10 = "c3pool.com" $p11 = "solscan.io" $p12 = "pool.minexmr.com" $p13 = "gulf.moneroocean.stream" $p14 = "auto.c3pool.org" condition: filesize < 50MB and any of them } // ========================================================================= // Proxyjacking agents (residential-proxy SaaS abused for bandwidth theft) // ========================================================================= rule linux_proxyjacking_agents { meta: description = "Proxyjacking agent binary or install script (requires ELF or shell-script magic)" author = "clean.sh incident response" family = "proxyjacking" severity = "high" reference = "Aqua Nautilus perfctl report + Akamai/Sysdig proxyjacking writeups" strings: $a1 = "peer2profit" nocase $a2 = "traffmonetizer" nocase $a3 = "repocket" nocase $a4 = "earnapp" nocase $a5 = "honeygain" nocase $a6 = "packetstream" nocase $a7 = "bitping" nocase $a8 = "speedshare" nocase $a9 = "earnfm" nocase $a10 = "earn.fm" nocase $a11 = "IPRoyal" nocase $a12 = "mysterium" nocase $a13 = "urnetwork" nocase $a14 = "proxyrack" nocase $a15 = "pawns.app" nocase $a16 = "traffmonetizer.com" condition: // require ELF binary or shell/python shebang — reject plain text ( uint32(0) == 0x464C457F or uint16(0) == 0x2123 ) and filesize < 100MB and any of them } rule linux_proxyjacking_container_images { meta: description = "Docker/container config referencing proxyjacking images" author = "clean.sh incident response" family = "proxyjacking" severity = "high" strings: $i1 = "xmrig/xmrig" nocase $i2 = "minerd" nocase $i3 = "bitpinger" nocase $i4 = "peer2profit/p2pclient" nocase $i5 = "repocket/repocket" nocase $i6 = "traffmonetizer" nocase $i7 = "packetstream/psclient" nocase $i8 = "honeygain/honeygain" nocase $i9 = "iproyal/pawns" nocase $cfg1 = "\"Image\":" $cfg2 = "config.v2.json" $cfg3 = "\"Cmd\":" condition: filesize < 10MB and any of ($i*) and any of ($cfg*) } // ========================================================================= // Generic LD_PRELOAD rootkit indicators (beyond perfctl-specific) // ========================================================================= rule linux_ldpreload_rootkit_generic { meta: description = "Generic LD_PRELOAD userland rootkit traits (tight condition to avoid libc FP)" author = "clean.sh incident response" family = "rootkit.ldpreload" severity = "high" strings: $h1 = "readdir" $h2 = "readdir64" $h3 = "getdents" $h4 = "getdents64" $h5 = "__xstat" $h6 = "__lxstat" $d1 = "RTLD_NEXT" $d2 = "dlsym" $p1 = "/etc/ld.so.preload" // distinctive rootkit config-string markers that do NOT appear in libc $c1 = "HIDE_PROC" fullword $c2 = "HIDE_FILE" fullword $c3 = "MAGIC_STRING" fullword $c4 = "hide_pid" fullword $c5 = "hide_port" fullword $c6 = "HIDE_TCP" fullword $c7 = "HIDE_UDP" fullword condition: uint32(0) == 0x464C457F and filesize < 5MB and // require at least one distinctive rootkit marker — libc has the hook // names and dlsym but not these config keywords ( 2 of ($h*) and any of ($d*) and any of ($c*) ) or ( 2 of ($h*) and $p1 and any of ($c*) ) } rule linux_ldpreload_config_file { meta: description = "ld.so.preload file pointing at known rootkit (tight — matches only known malicious SO names)" author = "clean.sh incident response" family = "rootkit.ldpreload" severity = "critical" strings: $s1 = "libgcwrap" $s2 = "libfsnldev" $s3 = "libfsnkdev" $s4 = "libpprocps" condition: filesize < 1KB and any of them } // ========================================================================= // SSH backdoors and credential stealers // ========================================================================= rule linux_ssh_backdoor_authorized_keys_in_binary { meta: description = "Binary contains embedded SSH public key (credential implant)" author = "clean.sh incident response" family = "backdoor.ssh" severity = "high" strings: $k1 = "ssh-rsa AAAA" $k2 = "ssh-ed25519 AAAA" $k3 = "ecdsa-sha2-nistp256 AAAA" $path1 = ".ssh/authorized_keys" $path2 = "/root/.ssh" condition: uint32(0) == 0x464C457F and filesize < 20MB and any of ($k*) and any of ($path*) } rule linux_pam_credential_stealer { meta: description = "PAM module that logs plaintext credentials to disk" author = "clean.sh incident response" family = "backdoor.pam" severity = "critical" strings: $sym1 = "pam_sm_authenticate" $sym2 = "pam_get_item" $sym3 = "PAM_AUTHTOK" $w1 = "fopen" $w2 = "fprintf" $w3 = "/tmp/" $w4 = "/var/tmp/" $w5 = "/dev/shm/" $d1 = ".passwd" $d2 = ".creds" $d3 = ".log" $d4 = "passwords.txt" condition: uint32(0) == 0x464C457F and filesize < 2MB and 2 of ($sym*) and any of ($w*) and any of ($d*) } // ========================================================================= // LKM rootkit name match (cold-disk: filename-content only) // ========================================================================= rule linux_lkm_rootkit_known_names { meta: description = "Known Linux LKM rootkit by filename or symbol" author = "clean.sh incident response" family = "rootkit.lkm" severity = "critical" strings: $n1 = "diamorphine" $n2 = "reptile_module" nocase $n3 = "suterusu" nocase $n4 = "nuk3gh0st" nocase $n5 = "kbeast" nocase $n6 = "module_hide" $n7 = "module_hidden" $n8 = "adore-ng" nocase condition: filesize < 10MB and any of them }