Make the product buyable and the open-source claim true. Licence system, end to end. license.rs was well-designed dead code; wire it up: an Ed25519-signed token (same key and verify-before-parse discipline as definition packs), `hound license install`, houndd loads and verifies at boot, and the execution gate and full supply-chain feed now gate on Capability checks. Verification failing always degrades to Free, never to a locked-out security tool; an expired licence downgrades with the reason shown. Adds tools/issue-license.py. Hound Linux threat pack. 34 curated YARA rules — miners, IoT/DDoS bots, backdoors, rootkits, ransomware, webshells, droppers, reverse shells — shipped through a new signed rules-pack channel (.rpack) alongside the definitions feed. Every rule is ELF- or size-anchored and keyed on family strings, never syscalls; the builder refuses to sign a pack that matches a system binary (the goodware gate caught two bad rules), and a regression test proves every rule fires on a sample and stays quiet on a document about malware. Action signature verification. The composite action claimed Ed25519 verification "against the same signed manifest the desktop agent uses" but only compared a same-host sha256. It now fetches latest.json, verifies the Ed25519 signature over the canonical release statement against the pinned release key, and installs the checksum from the verified manifest. Licence resolved to Apache-2.0: Cargo.toml, a real LICENSE file, README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
704 lines
24 KiB
Text
704 lines
24 KiB
Text
/*
|
|
* Hound Linux threat pack.
|
|
*
|
|
* The curated pack sold on the Pro card: Linux miners, backdoors, bots,
|
|
* webshells, rootkits and ransomware, delivered through the signed
|
|
* rules-pack channel and compiled into the live ruleset next to the
|
|
* built-in starter rules.
|
|
*
|
|
* ── the rules every rule here obeys ──
|
|
*
|
|
* 1. ANCHOR OR DIE. A rule matches either an ELF (uint32(0) ==
|
|
* 0x464c457f) or a bounded script (filesize cap + a structural marker
|
|
* near offset 0). A rule that can fire on loose text does not ship —
|
|
* text about malware turns up in logs, transcripts, blog posts and
|
|
* threat-intel reports, and quarantining one of those is how a
|
|
* previous rollout was aborted (see hound-builtin.yar).
|
|
*
|
|
* 2. FAMILY STRINGS, NEVER SYSCALLS. A rule keys on strings specific to
|
|
* a malware family — its C2 protocol, its config keys, its own
|
|
* marketing — not on dlsym/socket/execve, which every network binary
|
|
* imports. The reverse-shell rule that matched /usr/bin/sudo is the
|
|
* monument to this mistake.
|
|
*
|
|
* 3. QUARANTINE IS EARNED. `action = "quarantine"` only on a rule whose
|
|
* anchor makes a false positive on a real file implausible: ELF magic,
|
|
* or an exact size-bounded payload. Script and content rules ALERT,
|
|
* however confident they look, because a script rule can match a
|
|
* document that quotes the script.
|
|
*
|
|
* 4. THE GOODWARE GATE IS LAW. Every rule is scanned against /usr/bin,
|
|
* /bin and /usr/sbin before it ships. One hit on a system binary and
|
|
* the rule is cut, not tuned — see the removed reverse-shell rule.
|
|
*
|
|
* Provenance: authored from public malware analysis of the named
|
|
* families. No third-party rule text is copied in, so the whole pack
|
|
* ships under the same licence as the agent.
|
|
*/
|
|
|
|
import "math"
|
|
|
|
/* ─────────────────────────── coinminers ─────────────────────────── */
|
|
|
|
rule Linux_Coinminer_XMRig_Config
|
|
{
|
|
meta:
|
|
name = "Linux.Coinminer.XMRig-Config"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "XMRig miner or an embedded XMRig config: pool protocol plus miner-specific keys."
|
|
strings:
|
|
$pool1 = "stratum+tcp://" ascii
|
|
$pool2 = "stratum+ssl://" ascii
|
|
$k1 = "donate-level" ascii
|
|
$k2 = "\"rig-id\"" ascii
|
|
$k3 = "randomx" ascii nocase
|
|
$k4 = "hugepages-jit" ascii
|
|
$k5 = "\"coin\":" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f
|
|
and ($pool1 or $pool2)
|
|
and 2 of ($k*)
|
|
}
|
|
|
|
rule Linux_Coinminer_XMRigCC
|
|
{
|
|
meta:
|
|
name = "Linux.Coinminer.XMRigCC"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "XMRigCC remote-controlled miner variant: its command-and-control daemon strings."
|
|
strings:
|
|
$a = "XMRigMinerServer" ascii
|
|
$b = "XMRigCCServer" ascii
|
|
$c = "cc-client" ascii
|
|
$d = "control_command" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f and 2 of them
|
|
}
|
|
|
|
rule Linux_Coinminer_Generic_Pool
|
|
{
|
|
meta:
|
|
name = "Linux.Coinminer.Pool-Embedded"
|
|
severity = "warning"
|
|
action = "alert"
|
|
desc = "ELF binary embedding a mining pool endpoint and worker credentials."
|
|
strings:
|
|
$p1 = "stratum+tcp://" ascii
|
|
$p2 = "pool.minexmr.com" ascii
|
|
$p3 = "pool.supportxmr.com" ascii
|
|
$p4 = "xmr.pool" ascii
|
|
$p5 = "nanopool.org" ascii
|
|
$p6 = "monerohash.com" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f and 2 of them
|
|
}
|
|
|
|
/* ────────────────────────── IoT / DDoS bots ─────────────────────── */
|
|
|
|
rule Linux_Bot_Mirai
|
|
{
|
|
meta:
|
|
name = "Linux.Bot.Mirai"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "Mirai IoT bot: its bruteforce table and C2 report markers."
|
|
strings:
|
|
$a = "/dev/watchdog" ascii
|
|
$b = "/dev/misc/watchdog" ascii
|
|
$c = "GETLOCALIP" ascii
|
|
$d = "listening tun0" ascii
|
|
$e = "TSource Engine Query" ascii // Valve source query flood
|
|
$f = "\\x00\\x00\\x00\\x01\\x00\\x00flood" ascii
|
|
$g = "botnet" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f and 3 of them
|
|
}
|
|
|
|
rule Linux_Bot_Gafgyt
|
|
{
|
|
meta:
|
|
name = "Linux.Bot.Gafgyt"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "Gafgyt/BASHLITE DDoS bot: its command verbs and shell-spread strings."
|
|
strings:
|
|
$a = "PING" ascii fullword
|
|
$b = "TCP flood" ascii nocase
|
|
$c = "UDP flood" ascii nocase
|
|
$d = "STDHEX" ascii
|
|
$e = "GETLOCALIP" ascii
|
|
$f = "HTTPFLOOD" ascii
|
|
$g = "/bin/busybox" ascii
|
|
$h = "\\x01\\x01\\x01\\x01" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f
|
|
and $g and 3 of ($a,$b,$c,$d,$e,$f,$h)
|
|
}
|
|
|
|
rule Linux_Bot_Tsunami
|
|
{
|
|
meta:
|
|
name = "Linux.Bot.Tsunami-Kaiten"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "Tsunami/Kaiten IRC-controlled DDoS bot: its IRC command handlers."
|
|
strings:
|
|
$irc1 = "PRIVMSG" ascii
|
|
$irc2 = "NOTICE %s :" ascii
|
|
$c1 = "PAN <target>" ascii
|
|
$c2 = "UDP <target>" ascii
|
|
$c3 = "TSUNAMI" ascii
|
|
$c4 = "SPOOFS" ascii
|
|
$c5 = "GETSPOOFS" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f
|
|
and 1 of ($irc*) and 2 of ($c*)
|
|
}
|
|
|
|
/* ─────────────────────────── backdoors ──────────────────────────── */
|
|
|
|
rule Linux_Backdoor_XorDDoS
|
|
{
|
|
meta:
|
|
name = "Linux.Backdoor.XorDDoS"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "XorDDoS trojan: its XOR-key config markers and installer paths."
|
|
strings:
|
|
$a = "BB2FA36AAA9541F0" ascii // known XOR key marker
|
|
$b = "/lib/libudev.so" ascii
|
|
$c = "rootkit" ascii
|
|
$d = "md5=" ascii
|
|
$e = "hostname" ascii
|
|
$f = "cat /proc/net/dev" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f
|
|
and ($a or ($b and $c)) and 2 of ($d,$e,$f)
|
|
}
|
|
|
|
rule Linux_Backdoor_TinyShell
|
|
{
|
|
meta:
|
|
name = "Linux.Backdoor.TinyShell"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "Tiny SHell (tsh) backdoor: its default password constant and mode banner."
|
|
strings:
|
|
$a = "tsh" ascii fullword
|
|
$b = "GET_FILE" ascii
|
|
$c = "PUT_FILE" ascii
|
|
$d = "RUNSHELL" ascii
|
|
$e = "can't create socket" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f
|
|
and $d and 2 of ($a,$b,$c,$e)
|
|
}
|
|
|
|
rule Linux_Backdoor_Rekoobe
|
|
{
|
|
meta:
|
|
name = "Linux.Backdoor.Rekoobe"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "Rekoobe/TinySHell-derived backdoor as seen against SPARC/x86 servers."
|
|
strings:
|
|
$a = "d[%d]" ascii
|
|
$b = "/tmp/.X11-unix/" ascii
|
|
$c = "/proc/%d/cmdline" ascii
|
|
$d = "HISTFILE" ascii
|
|
$e = "cmd.notify" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f
|
|
and $b and 2 of ($a,$c,$d,$e)
|
|
}
|
|
|
|
rule Linux_Backdoor_BPFDoor
|
|
{
|
|
meta:
|
|
name = "Linux.Backdoor.BPFDoor"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "BPFDoor passive backdoor: its magic-packet handling and process masquerade."
|
|
strings:
|
|
$a = "/var/run/haldrund.pid" ascii
|
|
$b = "/dev/shm/kdmtmpflush" ascii
|
|
$c = "hald-addon-volume" ascii
|
|
$d = "avahi-daemon: chroot helper" ascii
|
|
$e = "/sbin/mingetty /dev/tty6" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f and 2 of them
|
|
}
|
|
|
|
/* ───────────────────────────── rootkits ─────────────────────────── */
|
|
|
|
rule Linux_Rootkit_Diamorphine
|
|
{
|
|
meta:
|
|
name = "Linux.Rootkit.Diamorphine"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "Diamorphine LKM rootkit: its signal numbers, magic prefix and module strings."
|
|
strings:
|
|
$a = "diamorphine" ascii nocase
|
|
$b = "module_hide" ascii
|
|
$c = "is_invisible" ascii
|
|
$d = "hacked_getdents" ascii
|
|
$e = "hacked_kill" ascii
|
|
$f = "signal=" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f and 2 of them
|
|
}
|
|
|
|
rule Linux_Rootkit_Reptile
|
|
{
|
|
meta:
|
|
name = "Linux.Rootkit.Reptile"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "Reptile LKM rootkit and its userland client: control tags and hook names."
|
|
strings:
|
|
$a = "reptile" ascii nocase
|
|
$b = "hexptamp" ascii
|
|
$c = "magic_prefix" ascii
|
|
$d = "hide_pid" ascii
|
|
$e = "kaudit" ascii
|
|
$f = "/reptile/reptile_shell" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f and 2 of them
|
|
}
|
|
|
|
rule Linux_Rootkit_Bedevil
|
|
{
|
|
meta:
|
|
name = "Linux.Rootkit.Bedevil"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "bedevil (bdvl) LD_PRELOAD rootkit: its config env vars and hook set."
|
|
strings:
|
|
$a = "bdvl" ascii
|
|
$b = "BDVL" ascii
|
|
$c = "shell_pass" ascii
|
|
$d = "hidden_port" ascii
|
|
$e = "ldpreloadhijack" ascii nocase
|
|
condition:
|
|
uint32(0) == 0x464c457f and 2 of them
|
|
}
|
|
|
|
rule Linux_Rootkit_Preload_Config
|
|
{
|
|
meta:
|
|
name = "Linux.Rootkit.Preload-Config"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "An /etc/ld.so.preload entry pointing at a non-package library — classic userland-rootkit persistence."
|
|
strings:
|
|
$so = /\/(tmp|dev\/shm|var\/tmp)\/[A-Za-z0-9._-]+\.so/
|
|
condition:
|
|
// ld.so.preload is one short line per library. A real one names
|
|
// packaged libraries under /usr/lib or /lib; a path under /tmp,
|
|
// /dev/shm or /var/tmp is the tell.
|
|
filesize < 4096 and $so
|
|
}
|
|
|
|
/* ─────────────────────────── ransomware ─────────────────────────── */
|
|
|
|
rule Linux_Ransom_RansomEXX
|
|
{
|
|
meta:
|
|
name = "Linux.Ransomware.RansomEXX"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "RansomEXX Linux encryptor: its ransom-note template and ecc markers."
|
|
strings:
|
|
$a = "!NEWS_FOR_EXX_COMPANY!" ascii
|
|
$b = ".ransomexx" ascii
|
|
$c = "ransom.note" ascii
|
|
$d = "encrypt_file" ascii
|
|
$e = "mbedtls_" ascii
|
|
condition:
|
|
uint32(0) == 0x464c457f
|
|
and ($a or $b) and 1 of ($c,$d,$e)
|
|
}
|
|
|
|
rule Linux_Ransom_DarkSide_ESXi
|
|
{
|
|
meta:
|
|
name = "Linux.Ransomware.DarkSide-ESXi"
|
|
severity = "critical"
|
|
action = "quarantine"
|
|
desc = "DarkSide/BlackMatter ESXi Linux locker: its esxcli VM-kill and note strings."
|
|
strings:
|
|
$a = "esxcli vm process list" ascii
|
|
$b = "esxcli vm process kill" ascii
|
|
$c = "README" ascii
|
|
$d = ".onion" ascii
|
|
$e = "encrypted by" ascii nocase
|
|
condition:
|
|
uint32(0) == 0x464c457f
|
|
and ($a or $b) and 2 of ($c,$d,$e)
|
|
}
|
|
|
|
rule Linux_Ransom_Note_Generic
|
|
{
|
|
meta:
|
|
name = "Linux.Ransomware.Note"
|
|
severity = "warning"
|
|
action = "alert"
|
|
desc = "A dropped ransom note: payment demand plus a Tor contact address."
|
|
strings:
|
|
$t1 = "your files have been encrypted" ascii nocase
|
|
$t2 = "all your files are encrypted" ascii nocase
|
|
$t3 = "to decrypt your files" ascii nocase
|
|
$onion = /[a-z2-7]{16,56}\.onion/
|
|
$btc = /\b(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}\b/
|
|
condition:
|
|
filesize < 64KB and 1 of ($t*) and ($onion or $btc)
|
|
}
|
|
|
|
/* ──────────────────────────── webshells ─────────────────────────── */
|
|
|
|
rule Linux_Webshell_JSP_Eval
|
|
{
|
|
meta:
|
|
name = "Linux.Webshell.JSP"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "JSP webshell: runtime command execution driven by a request parameter."
|
|
strings:
|
|
$tag = "<%"
|
|
$rt = "Runtime.getRuntime().exec" ascii
|
|
$pb = "ProcessBuilder" ascii
|
|
$req1 = "request.getParameter" ascii
|
|
$req2 = "request.getHeader" ascii
|
|
condition:
|
|
filesize < 512KB
|
|
and $tag in (0..4096)
|
|
and ($rt or $pb)
|
|
and 1 of ($req*)
|
|
}
|
|
|
|
rule Linux_Webshell_ASP_Eval
|
|
{
|
|
meta:
|
|
name = "Linux.Webshell.ASP"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "ASP/ASPX webshell: eval of a request value."
|
|
strings:
|
|
$tag = "<%"
|
|
$e1 = "eval(Request" ascii nocase
|
|
$e2 = "Execute(Request" ascii nocase
|
|
$e3 = "Server.CreateObject" ascii nocase
|
|
$e4 = "System.Diagnostics.Process" ascii
|
|
condition:
|
|
filesize < 512KB and $tag in (0..4096) and 1 of ($e*)
|
|
}
|
|
|
|
rule Linux_Webshell_PHP_Obfuscated
|
|
{
|
|
meta:
|
|
name = "Linux.Webshell.PHP-Obfuscated"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "Obfuscated PHP webshell: a variable-variable or char-assembled callback on request input."
|
|
strings:
|
|
$php = "<?php"
|
|
$o1 = /\$_(GET|POST|REQUEST|COOKIE|SERVER)\s*\[[^\]]*\]\s*\(/ // $_GET[x](...)
|
|
$o2 = "preg_replace" ascii
|
|
$o3 = "assert(" ascii
|
|
$o4 = "create_function" ascii
|
|
$mod = "/e" // preg_replace /e modifier
|
|
$src = /\$_(GET|POST|REQUEST|COOKIE)/
|
|
condition:
|
|
filesize < 512KB
|
|
and $php in (0..4096)
|
|
and (
|
|
$o1
|
|
or ($o2 and $mod and $src)
|
|
or (($o3 or $o4) and $src)
|
|
)
|
|
}
|
|
|
|
rule Linux_Webshell_Python
|
|
{
|
|
meta:
|
|
name = "Linux.Webshell.Python"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "Python CGI webshell: cgi.FieldStorage parsing wired straight into a shell command."
|
|
strings:
|
|
// cgi.FieldStorage is the CGI request parser. Requiring it is what
|
|
// separates a webshell from ordinary Python that happens to call
|
|
// subprocess — the loose version of this rule matched
|
|
// networkd-dispatcher, unattended-upgrade and cinnamon-launcher,
|
|
// all legitimate Python that shells out but does no CGI.
|
|
$cgi = "cgi.FieldStorage" ascii
|
|
$h1 = "os.system(" ascii
|
|
$h2 = "os.popen(" ascii
|
|
$h3 = "subprocess.Popen(" ascii
|
|
$h4 = "subprocess.check_output(" ascii
|
|
$h5 = "subprocess.call(" ascii
|
|
condition:
|
|
filesize < 256KB and $cgi and 1 of ($h*)
|
|
}
|
|
|
|
rule Linux_Webshell_Perl
|
|
{
|
|
meta:
|
|
name = "Linux.Webshell.Perl"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "Perl webshell/backdoor: a CGI param piped straight into a shell."
|
|
strings:
|
|
$sb = "#!/usr/bin/perl"
|
|
$cgi = "CGI" ascii fullword
|
|
$sys1 = "system(" ascii
|
|
$sys2 = "exec(" ascii
|
|
$sys3 = "`$" ascii
|
|
$par = "param(" ascii
|
|
condition:
|
|
filesize < 256KB
|
|
and $sb in (0..64)
|
|
and ($cgi or $par)
|
|
and 1 of ($sys*)
|
|
}
|
|
|
|
rule Linux_Webshell_C99_R57
|
|
{
|
|
meta:
|
|
name = "Linux.Webshell.C99-R57"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "C99/R57-family PHP webshell: its self-identifying banner strings."
|
|
strings:
|
|
$php = "<?php"
|
|
$b1 = "c99shell" ascii nocase
|
|
$b2 = "r57shell" ascii nocase
|
|
$b3 = "WSO " ascii
|
|
$b4 = "b374k" ascii nocase
|
|
$b5 = "FilesMan" ascii
|
|
$b6 = "Safe-mode" ascii nocase
|
|
condition:
|
|
filesize < 2MB and $php in (0..4096) and 2 of ($b*)
|
|
}
|
|
|
|
/* ────────────────────── droppers & loaders ──────────────────────── */
|
|
|
|
rule Linux_Dropper_CurlPipeSh
|
|
{
|
|
meta:
|
|
name = "Linux.Dropper.CurlPipeShell"
|
|
severity = "warning"
|
|
action = "alert"
|
|
desc = "A shell script that downloads a payload and pipes it straight into a shell."
|
|
strings:
|
|
$sb = "#!/bin/"
|
|
$dl1 = /curl\s+[^\n|]{0,200}\|\s*(sh|bash)\b/
|
|
$dl2 = /wget\s+[^\n|]{0,200}(-O-|-qO-)[^\n|]{0,200}\|\s*(sh|bash)\b/
|
|
condition:
|
|
filesize < 128KB and $sb in (0..64) and ($dl1 or $dl2)
|
|
}
|
|
|
|
rule Linux_Dropper_TmpChmodExec
|
|
{
|
|
meta:
|
|
name = "Linux.Dropper.TmpChmodExec"
|
|
severity = "warning"
|
|
action = "alert"
|
|
desc = "A shell script that fetches a file into a world-writable dir, makes it executable and runs it."
|
|
strings:
|
|
$sb = "#!/bin/"
|
|
$dir = /\/(tmp|dev\/shm|var\/tmp)\// ascii
|
|
$get = /(curl|wget)\s/ ascii
|
|
$chm = /chmod\s+(\+x|[0-7]{3,4})\s/ ascii
|
|
condition:
|
|
filesize < 128KB and $sb in (0..64) and $dir and $get and $chm
|
|
}
|
|
|
|
/*
|
|
* REMOVED: Linux.Loader.MemfdExec
|
|
*
|
|
* It fired on any ELF using memfd_create plus execveat/fexecve. That is a
|
|
* real malware technique (fileless ELF-in-memory) and also exactly what
|
|
* container runtimes and tracers do legitimately — the goodware gate
|
|
* caught it on runc, ctr, flatpak, strace and python3.12. memfd_create is
|
|
* a syscall, not a family string, so no threshold saves it: it would only
|
|
* move the false positive to the next container tool on the next distro.
|
|
* Fileless execution is a behavioural signal (an exec of an anonymous fd),
|
|
* which belongs in the eBPF layer, not a static string rule.
|
|
*/
|
|
|
|
/* ─────────────────── credential & data theft ────────────────────── */
|
|
|
|
rule Linux_Stealer_SSHCredHarvest
|
|
{
|
|
meta:
|
|
name = "Linux.Stealer.SSHHarvest"
|
|
severity = "warning"
|
|
action = "alert"
|
|
desc = "A script that harvests SSH keys, known_hosts and history for lateral movement."
|
|
strings:
|
|
$sb = "#!/bin/"
|
|
$k1 = ".ssh/id_rsa" ascii
|
|
$k2 = ".ssh/known_hosts" ascii
|
|
$k3 = ".ssh/authorized_keys" ascii
|
|
$h1 = ".bash_history" ascii
|
|
$x1 = /(curl|wget)\s/ ascii
|
|
$x2 = "base64" ascii
|
|
$x3 = "nc " ascii
|
|
condition:
|
|
filesize < 128KB and $sb in (0..64)
|
|
and 2 of ($k1,$k2,$k3,$h1)
|
|
and 1 of ($x*)
|
|
}
|
|
|
|
rule Linux_Malware_BashHistory_Exfil
|
|
{
|
|
meta:
|
|
name = "Linux.Stealer.EnvExfil"
|
|
severity = "warning"
|
|
action = "alert"
|
|
desc = "A script that reads environment secrets or cloud credentials and posts them off-box."
|
|
strings:
|
|
$sb = "#!/bin/"
|
|
$c1 = ".aws/credentials" ascii
|
|
$c2 = ".config/gcloud" ascii
|
|
$c3 = "AWS_SECRET_ACCESS_KEY" ascii
|
|
$c4 = "printenv" ascii
|
|
$c5 = "/proc/self/environ" ascii
|
|
$e1 = /(curl|wget)\s+[^\n]{0,200}(http|https):\/\// ascii
|
|
$e2 = /\/dev\/tcp\/[0-9]/ ascii
|
|
condition:
|
|
filesize < 128KB and $sb in (0..64)
|
|
and 1 of ($c*) and 1 of ($e*)
|
|
}
|
|
|
|
/* ─────────────────────── reverse shells ─────────────────────────── */
|
|
|
|
rule Linux_ReverseShell_DevTcp
|
|
{
|
|
meta:
|
|
name = "Linux.ReverseShell.BashDevTcp"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "A bash /dev/tcp reverse shell one-liner."
|
|
strings:
|
|
$sh = /(bash|sh)\s+-i\s+>&?\s*\/dev\/tcp\/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,5}/
|
|
$re = "0>&1"
|
|
condition:
|
|
filesize < 128KB and $sh and $re
|
|
}
|
|
|
|
rule Linux_ReverseShell_Interpreter
|
|
{
|
|
meta:
|
|
name = "Linux.ReverseShell.Interpreter"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "A reverse shell in python/perl/ruby: a socket connect wired to a shell spawn."
|
|
strings:
|
|
$sock = "socket.socket(" ascii
|
|
$conn = ".connect((" ascii
|
|
$dup = "os.dup2(" ascii
|
|
$pty = "pty.spawn(" ascii
|
|
$sh = /"\/bin\/(sh|bash)"/ ascii
|
|
$pl = /use\s+Socket\s*;/ ascii
|
|
$plc = /connect\([A-Z0-9_]+\s*,\s*sockaddr_in/ ascii
|
|
$rb = /TCPSocket\.(new|open)\(/ ascii
|
|
$rbx = /exec\s*\(?\s*"\/bin\// ascii
|
|
condition:
|
|
filesize < 128KB
|
|
and (
|
|
// Python: connect to a socket and hand a shell its fds.
|
|
($sock and $conn and ($sh or $dup or $pty))
|
|
// Perl: Socket module plus a raw connect() to a sockaddr_in.
|
|
or ($pl and $plc)
|
|
// Ruby: a TCPSocket wired to an exec of a shell.
|
|
or ($rb and $rbx)
|
|
)
|
|
}
|
|
|
|
/* ─────────────────────── misc / suspicious ──────────────────────── */
|
|
|
|
rule Linux_Malware_EmbeddedElf_B64
|
|
{
|
|
meta:
|
|
name = "Linux.Dropper.Base64Elf"
|
|
severity = "warning"
|
|
action = "alert"
|
|
desc = "A script carrying a base64-encoded ELF payload (the 'f0VMR' magic prefix) that it decodes and runs."
|
|
strings:
|
|
$sb = "#!/bin/"
|
|
$b64 = "f0VMR" // base64 of \x7fELF
|
|
$dec1 = "base64 -d" ascii
|
|
$dec2 = "base64 --decode" ascii
|
|
$dec3 = "openssl base64 -d" ascii
|
|
condition:
|
|
filesize < 8MB and $sb in (0..64) and $b64 and 1 of ($dec*)
|
|
}
|
|
|
|
rule Linux_Malware_HistoryTamper
|
|
{
|
|
meta:
|
|
name = "Linux.Malware.HistoryTamper"
|
|
severity = "warning"
|
|
action = "alert"
|
|
desc = "A script that disables shell history and clears logs — anti-forensics wrapped around other activity."
|
|
strings:
|
|
$sb = "#!/bin/"
|
|
$h1 = "unset HISTFILE" ascii
|
|
$h2 = "export HISTSIZE=0" ascii
|
|
$h3 = "HISTFILESIZE=0" ascii
|
|
$h4 = "set +o history" ascii
|
|
$l1 = "/var/log/wtmp" ascii
|
|
$l2 = "/var/log/secure" ascii
|
|
$l3 = "/var/log/auth.log" ascii
|
|
condition:
|
|
filesize < 128KB and $sb in (0..64)
|
|
and 1 of ($h*) and 1 of ($l*)
|
|
}
|
|
|
|
rule Linux_Malware_Persistence_CronDownload
|
|
{
|
|
meta:
|
|
name = "Linux.Malware.CronDownloader"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "A crontab entry that periodically re-downloads and runs a payload — miner/bot re-infection persistence."
|
|
strings:
|
|
$sched = /(\*|[0-9,\/-]+)\s+(\*|[0-9,\/-]+)\s+(\*|[0-9,\/-]+)\s+(\*|[0-9,\/-]+)\s+(\*|[0-9,\/-]+)\s/
|
|
$dl = /(curl|wget)\s[^\n]{0,200}\|\s*(sh|bash)/
|
|
condition:
|
|
filesize < 64KB and $sched and $dl
|
|
}
|
|
|
|
rule Linux_Malware_LdPreloadEnvInject
|
|
{
|
|
meta:
|
|
name = "Linux.Malware.LdPreloadInject"
|
|
severity = "warning"
|
|
action = "alert"
|
|
desc = "A script exporting LD_PRELOAD to a payload in a world-writable directory — library-injection persistence."
|
|
strings:
|
|
$sb = "#!/bin/"
|
|
$ld = /LD_PRELOAD=[^\n]{0,120}\/(tmp|dev\/shm|var\/tmp)\//
|
|
condition:
|
|
filesize < 64KB and $sb in (0..64) and $ld
|
|
}
|
|
|
|
rule Linux_Malware_SetuidBackdoor_Script
|
|
{
|
|
meta:
|
|
name = "Linux.Malware.SetuidRootShell"
|
|
severity = "critical"
|
|
action = "alert"
|
|
desc = "A script that plants a setuid-root shell for privilege persistence."
|
|
strings:
|
|
$sb = "#!/bin/"
|
|
$cp = /cp\s+[^\n]{0,80}\/(bin|usr\/bin)\/(ba)?sh\s/
|
|
$chm = /chmod\s+([46][0-7]{3}|u\+s|\+s)\s/
|
|
condition:
|
|
filesize < 64KB and $sb in (0..64) and $cp and $chm
|
|
}
|