# Declawing PUMAKIT

PUMAKIT LKM rootkit hooks 18 syscalls via ftrace, using rmdir() with 'zarya' commands for root escalation and hiding files/C2.

- Payout: Real-world hack
- Severity: high
- Program: Linux
- Analysis by: Remco Sprooten, Ruben Groenewoud
- Disclosed: 2024-12-12
- Write-up quality: 8/10
- Original: https://www.elastic.co/security-labs/threat-command/declawing-pumakit
- Library page: https://bugbountylibrary.com/r/blog%3Aelastic-co-security-labs-threat-command-declawing-pumakit
- Tags: area:os domain:desktop impact:data-leak impact:internal-access impact:privesc kind:incident platform:threat-intel sev:high tech:linux technique:reverse-engineering technique:source-review vuln:privilege-escalation

**Target:** Linux kernel (loadable kernel module, syscall table, ftrace)
**Root cause:** Rootkit LKM resolves kallsyms_lookup_name via fake GPL license and uses ftrace to hook 18 syscalls; rmdir_hook parses 'zarya' prefixed paths, and when the 6th char is 0 it calls prepare_creds/commit_creds to set all creds to root.
**Steps:**
1. Dropper 'cron' writes embedded ELFs to memfd via memfd_create, forks, child execveat()s /memfd:wpn (loader), parent execveat()s /memfd:tgt (benign cron).
2. Loader checks secure boot via dmesg, drops /tmp/script.sh which decompresses /boot/vmlinuz-* (gzip magic \037\213\010 at offset +10957311) to /tmp/vmlinux, then deletes script and vmlinux.
3. Loader resolves __ksymtab/__kcrctab offsets in vmlinux and loads puma.ko.
4. LKM hooks syscalls via ftrace_set_filter_ip/register_ftrace_function; rmdir_hook checks prefix 'zarya', 6th char = command, 8th char = argument.
5. Trigger root: call `rmdir("zarya.0")` from a C wrapper; hook runs prepare_creds, zeroes UID/EUID/GID/EGID/FSUID/FSGID, commit_creds; then `cat /etc/shadow` succeeds.
6. Other commands: `rmdir zarya.t` (test), `rmdir zarya.v` (version, returns 240513), `rmdir zarya.c` (config dump), `rmdir zarya.k.<pid>` (hide PID).
7. getdents/getdents64 hooks filter entries prefixed 'zov_' from directory listings while direct path access still works.
**How found:** Routine VirusTotal threat hunting surfaced a 'cron' binary uploaded 2024-09-04 with 0 detections; embedded strings referencing vmlinuz manipulation prompted deeper analysis, leading to memfd extraction and LKM reverse engineering.
**Exploitation:** Attacker with code execution on a Linux host runs the cron dropper; loader deploys LKM rootkit that hooks syscalls to hide files (zov_ prefix), conceal itself, block debugging, and beacon to C2 (89.23.113.204, *.opsecurity1.art). Any local user can invoke rmdir("zarya.0") to gain root creds in-process and read /etc/shadow.
**Escalation:** Unprivileged local user escalates to root via rmdir hook; rootkit then hides its files/dirs and maintains C2 persistence.
**Insight:** Rootkits can abuse innocuous syscalls like rmdir() as a covert command channel and privilege-escalation primitive; hunt for rmdir calls producing uid/gid changes and for ftrace hooking of getdents/rmdir in unsigned LKMs.

## Proof of concept (copied exactly from the original report)

**C wrapper triggering rmdir zarya.0 privilege escalation**

```
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>

int main() {
 const char *directory = "zarya.0";

 // Attempt to remove the directory
 if (syscall(SYS_rmdir, directory) == -1) {
 fprintf(stderr, "rmdir: failed to remove '%s': %s\n", directory, strerror(errno));
 } else {
 printf("rmdir: successfully removed '%s'\n", directory);
 }

 // Execute the `id` command
 printf("\n--- Running 'id' command ---\n");
 if (system("id") == -1) {
 perror("Failed to execute 'id'");
 return 1;
 }

 // Display the contents of /etc/shadow
 printf("\n--- Displaying '/etc/shadow' ---\n");
 if (system("cat /etc/shadow") == -1) {
 perror("Failed to execute 'cat /etc/shadow'");
 return 1;
 }

 return 0;
}
```

**rmdir zarya command sequence for rootkit interaction**

```
ubuntu-rk:~$ rmdir test
rmdir: failed to remove 'test': No such file or directory
ubuntu-rk:~$ rmdir zarya.t
ubuntu-rk:~$

When using the rmdir command on a non-existent directory, an error message “No such file or directory” is returned. When using rmdir on zarya.t, no output is returned, indicating successful loading of the kernel module.

A second command is v, which is used to get the version of the running rootkit.

ubuntu-rk:~$ rmdir zarya.v
rmdir: failed to remove '240513': No such file or directory
```

**YARA signature for PUMAKIT components**

```
rule Linux_Trojan_Pumakit {
 meta:
 author = "Elastic Security"
 creation_date = "2024-12-09"
 last_modified = "2024-12-09"
 os = "Linux"
 arch = "x86, arm64"
 threat_name = "Linux.Trojan.Pumakit"

 strings:
 $str1 = "PUMA %s"
 $str2 = "Kitsune PID %ld"
 $str3 = "/usr/share/zov_f"
 $str4 = "zarya"
 $str5 = ".puma-config"
 $str6 = "ping_interval_s"
 $str7 = "session_timeout_s"
 $str8 = "c2_timeout_s"
 $str9 = "LD_PRELOAD=/lib64/libs.so"
 $str10 = "kit_so_len"
 $str11 = "opsecurity1.art"
 $str12 = "89.23.113.204"
 
 condition:
 4 of them
}
```

