Files
perfct/perfctl.yar
T
root c20c40159a v1
2026-04-19 00:21:08 +00:00

653 lines
21 KiB
Plaintext

/*
* 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
}