
Rootkit Uncovered: How a Kernel Module Hides Files in Plain Sight
Table of Contents
TL;DR
- Rootkits can slip malicious files into a system and make them invisible to ordinary tools.
- A simple Linux kernel module uses ftrace to intercept the sys_getdents64 system call and scrub directory listings.
- Installing the module with insmod hides any file whose name contains the magic word caraxes.
- Removing the module restores visibility; the hidden files reappear.
- Knowing the mechanics lets you detect, audit, and defend against this class of attacks.
Disclaimer
For educational and academic purposes only.
Why this matters
I was in a rush to investigate a suspicious system when the regular ls command started behaving oddly. After a quick inspection, I discovered that the system had hidden files that standard detection tools missed. Rootkits can do this by manipulating kernel functions, giving the attacker a persistent foothold while staying out of sight. For system administrators and security researchers, the ability to detect and neutralise such hidden payloads is critical.
Core concepts
The normal path for ls
When you run ls, the libc wrapper calls the getdents64 system call (the sys_getdents64 function in kernel space). getdents64 fills a buffer with directory entries, and ls formats them for the user. The kernel function lives in user space; the system call dispatcher handles the switch from user to kernel mode.
ftrace and the ftrace thunk
The Linux kernel ships a lightweight tracer called ftrace that can replace any kernel function with a custom callback without modifying the function body. The \u201Cftrace thunk\u201D is a small wrapper that forwards the call to the original function, giving you a hook point that is almost invisible to other code.
How the rootkit hooks sys_getdents64
- The rootkit installs itself as a kernel module via insmod.
- During init it registers a ftrace hook on sys_getdents64.
- The hook first calls the original sys_getdents64 to get the real directory buffer.
- It then runs an “evil function” that scans the buffer and removes any entries whose names contain the magic word caraxes.
- The filtered buffer is returned to user space, and the hidden files never reach ls.
The ftrace hook redirects sys_getdents64 to a malicious hook. The hook calls the original sys_getdents64. The hook passes results to an evil function. The evil function removes files matching a pattern.
Because the hook only alters the data returned to the user, standard file-listing tools see no difference, and security tools that rely on the same system call are fooled.
How to apply it
Below is a step-by-step walkthrough of the Caraxes rootkit, which demonstrates the file-hiding technique. Feel free to adapt the steps to your own lab environment; the code is open source and licensed for educational use.
| Step | Command | What happens |
|---|---|---|
| 1 | git clone https://github.com/ait-aecid/caraxes.git | Fetch the source code. |
| 2 | cd caraxes && make | Compile caraxes.ko. |
| 3 | sudo insmod caraxes.ko | Load the module; the ftrace hook is registered. |
| 4 | touch /tmp/caraxes.hidden | Create a file that will be hidden. |
| 5 | ls /tmp | The file caraxes.hidden does not appear. |
| 6 | sudo rmmod caraxes | Unload the module; the hook is removed. |
| 7 | ls /tmp | The file reappears, proving the hiding worked. |
Detailed command log
$ git clone https://github.com/ait-aecid/caraxes.git
$ cd caraxes
$ make
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
[... compilation output ...]
$ sudo insmod caraxes.ko
$ touch /tmp/caraxes.hidden
$ ls /tmp
# The hidden file is missing
$ sudo rmmod caraxes
$ ls /tmp
caraxes.hidden
The make command compiles the C source into a loadable module. The insmod/rmmod pair installs and removes the module, showing how a rootkit can be toggled without leaving traces on disk.
Pitfalls & edge cases
| Issue | Why it matters | Mitigation |
|---|---|---|
| Kernel updates | New kernel versions may change the symbol layout of sys_getdents64, breaking the hook. | Keep the module patched for the target kernel; test on the exact kernel you plan to protect. |
| Hidden module | The Caraxes source allows the module to unlink itself from /sys/module. When hidden, lsmod no longer lists it. | Use modprobe -c or inspect /proc/modules directly; monitor for missing module entries. |
| ftrace disabled | Some distros compile the kernel with CONFIG_FTRACE disabled, preventing the hook. | Verify /sys/kernel/tracing exists; enable ftrace if necessary for detection or defense. |
| Audit logs | A well-behaved rootkit may suppress audit events. | Enable auditd with -e 1 -f to catch insmod/rmmod events and system call invocations. |
| Detection via buffer size | The hook can be detected by comparing the buffer size returned by sys_getdents64 with the actual file count. | Employ a baseline script that records normal ls output sizes and flags anomalies. |
| Privilege escalation | The module runs in kernel space, giving the attacker full control. | Restrict module loading to trusted users; use signed modules on production systems. |
Quick FAQ
- Q1: What specific pattern does the evil function use to hide files?
A1: It removes any file whose name contains the string caraxes, defined in rootkit.h.
- Q2: How does the rootkit’s kernel module identify the malicious file to hide?
A2: By scanning the directory buffer returned by sys_getdents64 for the magic word in each file name.
- Q3: What are the security implications of using ftrace for malicious hooking?
A3: ftrace hooks run in kernel space, bypassing normal syscall security checks, and can be used to filter or modify data before it reaches user space.
- Q4: How can standard detection tools identify rootkits that hide files at the kernel level?
A4: By inspecting loaded kernel modules (lsmod, /proc/modules), checking for ftrace hooks (/sys/kernel/tracing/traced_functions), and comparing directory listings obtained via raw system calls versus user-space utilities.
- Q5: What are the differences between ftrace thunk and other kernel hooking mechanisms?
A5: ftrace thunk is a lightweight, non-intrusive wrapper that does not modify the original function’s code; other methods (kprobes, inline patching) alter kernel memory and may be more noticeable or unstable.
- Q6: How stable is the rootkit across different kernel versions?
A6: Caraxes is tested up to Linux 6.11. It requires re-building against each kernel, as symbol offsets can shift.
- Q7: Are there ways to detect the presence of the rootkit module without inspecting the filesystem?
A7: Yes – inspect /proc/modules, monitor auditd logs for insmod, and examine ftrace hook registrations in /sys/kernel/tracing/traced_functions.
Conclusion
Rootkits that hide files by intercepting sys_getdents64 are simple yet effective. By understanding how ftrace hooks can be abused, you can implement detection measures:
- Keep kernel and kernel modules signed; use module.sig checks.
- Audit every insmod/rmmod event with auditd.
- Periodically scan /sys/module and /proc/modules for unknown modules.
- Disable ftrace on production systems unless required.
- Use a baseline comparison script that flags anomalous directory listings.
Security researchers and system administrators should add these checks to their routine audits. If you’re a kernel developer, consider adding kernel-space logging for ftrace registrations to help future defenders.
References
- Caraxes GitHub repository – https://github.com/ait-aecid/caraxes
- Getdents64 manual – https://man7.org/linux/man-pages/man2/getdents64.2.html
- ls manual – https://man7.org/linux/man-pages/man1/ls.1.html
- insmod manual – https://man7.org/linux/man-pages/man8/insmod.8.html
- ftrace documentation – https://www.kernel.org/doc/html/latest/trace/ftrace.html
- Linux Kernel Module Programming Guide – https://tldp.org/LDP/lkmpg/
