Listen to this Post

Introduction:
In the cat-and-mouse game of Linux security, attackers have long manipulated what users see—renaming processes to blend in with legitimate system threads. While `exec -a` can fake `argv
` and `prctl(PR_SET_NAME)` can rewrite the `comm` field itself, these are merely cosmetic changes. What truly defines a kernel thread is not its name but its identity: no on-disk binary, an ancestry tracing back to PID 2 (kthreadd), and the kernel-internal `PF_KTHREAD` flag. This article dissects the three immutable signals that separate genuine kernel threads from impostors, providing defenders with actionable detection strategies. <h2 style="color: yellow;">Learning Objectives:</h2> <ul> <li>Understand the difference between user-space process naming (<code>argv[bash]</code>, <code>comm</code>) and kernel-assigned identity (<code>PF_KTHREAD</code>, executable path, parent PID).</li> <li>Learn to detect process name stomping via `prctl(PR_SET_NAME)` using auditd and Elastic detection rules.</li> <li>Master the three core indicators—empty executable, kthreadd ancestry, and `PF_KTHREAD` flag—for identifying masqueraded kernel threads.</li> <li>Implement practical hunting commands and detection engineering workflows for Linux environments.</li> </ul> <h2 style="color: yellow;">You Should Know:</h2> <ol> <li>The Illusion of Names: How Attackers Fake Kernel Threads</li> </ol> A userspace process can rename itself to look like a kernel thread using two primary methods. The first is <code>exec -a</code>, which sets <code>argv[bash]</code>—the first argument displayed in tools like <code>ps</code>. The second, more deceptive method is <code>prctl(PR_SET_NAME)</code>, a system call that rewrites the kernel's `comm` field, the name shown in `/proc/[bash]/comm` and <code>top</code>. This second method is particularly insidious because it changes what the kernel itself reports as the process name, fooling even seasoned administrators who glance at process lists. However, the rename is merely a syscall. While `prctl(PR_SET_NAME)` is common in legitimate software (threads often name themselves for debugging), the signal for defenders is not the call itself but a process in a suspicious path making it. With auditd watching <code>prctl</code>, this triggers alerts for "Potential Process Name Stomping with Prctl". The Elastic detection rule, for instance, monitors for `prctl` invocations with the `PR_SET_NAME` argument (<code>a0 == "f"</code>) and flags those originating from suspicious directories like <code>/tmp/</code>, <code>/dev/shm/</code>, or <code>/var/tmp/</code>. <h2 style="color: yellow;">Step-by-Step Guide: Detecting `prctl` Abuse with Auditd</h2> <ol> <li>Install and Configure Auditd: Ensure the `auditd` daemon is installed and running on your Linux system.</li> <li>Add a Custom Audit Rule: To monitor `prctl` syscalls, add the following rule to your audit configuration (e.g., in <code>/etc/audit/rules.d/audit.rules</code>): [bash] -a exit,always -F arch=b64 -S prctl -k prctl_detection
This rule logs all `prctl` system calls on 64-bit architectures with the key prctl_detection.
sudo systemctl restart auditd
grep:
sudo ausearch -k prctl_detection | grep -A 10 "a0=f"
Alternatively, you can use `grep` directly on the audit log:
sudo grep "prctl" /var/log/audit/audit.log | grep "a0=f"
process where host.os.type == "linux" and auditd.data.syscall == "prctl" and auditd.data.a0 == "f" and process.executable like ("/tmp/", "/dev/shm/", "/var/tmp/", ...)
This query triggers alerts when a `prctl` name change originates from a suspicious directory.
2. The Executable Test: Empty vs. Present
The most reliable giveaway of a masqueraded kernel thread is the presence of an on-disk executable. A genuine kernel thread is created entirely within the kernel; it has no associated binary file on the filesystem. The kernel marks it with the `PF_KTHREAD` flag, and its `process.executable` field is empty.
When an attacker renames a userspace process (e.g., /usr/bin/python3.10) to [kworker/0:2], the executable behind it remains /usr/bin/python3.10. Hunting for a `kworker` or `kthreadd` name with a real executable behind it immediately exposes the fake. Elastic’s “Executable Masquerading as Kernel Process” rule keys on exactly this anomaly.
Step-by-Step Guide: Hunting for Executable Masquerading
- Manual Inspection with
ps: List all processes and inspect their command-line arguments. Kernel threads typically show their name in square brackets and have no command-line arguments.ps -ef | grep -E "kworker|kthreadd"
Look for entries that don’t have square brackets or show a full path (e.g.,
/usr/bin/python). - Check
/proc//exe</code>: For any suspicious process, check the symbolic link to its executable. [bash] ls -l /proc/<PID>/exe
For a true kernel thread, this link will point to nothing (or be broken). For a masqueraded process, it will point to a real binary.
- Inspect
/proc//maps</code>: Kernel threads have an empty memory map for user space. [bash] cat /proc/<PID>/maps
If this shows memory regions, it's a userspace process.
- Automated Detection with Elastic: Deploy the "Executable Masquerading as Kernel Process" rule. Its EQL query is:
process where host.os.type == "linux" and event.type == "start" and event.action in ("exec", "exec_event") and process.name : ("kworker", "kthread") and process.executable != nullThis rule triggers when a process with a kernel-thread name has a non-1ull executable path.
3. The Lineage Test: Following the Family Tree
Identity does not move with the name. A genuine kernel thread descends from `kthreadd` (PID 2), which itself is a child of PID 0 (the swapper process). Every kernel thread on a standard Linux system has `kthreadd` as its parent. A process launched from a shell, no matter how cleverly renamed, simply does not share this ancestry.
Attackers sometimes attempt to flip this by executing under the real `kthreadd` to borrow its trust—an "Unusual Execution from Kernel Thread (kthreadd) Parent" scenario. However, this is exceptionally rare and itself a strong indicator of compromise.
Step-by-Step Guide: Tracing Process Lineage
- View the Process Tree: Use `pstree` to visualize the entire process hierarchy.
pstree -p
Look for any process with a kernel-like name (e.g.,
kworker) that is not a direct child of `kthreadd` (PID 2). - Check Parent PID (PPID): For a specific suspicious process, check its PPID in
/proc.cat /proc/<PID>/status | grep PPid
Or use `ps`:
ps -o pid,ppid,comm -p <PID>
If the PPID is not 2 (or 0 for `kthreadd` itself), the process is not a true kernel thread.
3. Hunt for Anomalous Parent-Child Relationships: Use a combination of `ps` and `awk` to find processes named like kernel threads but with a PPID other than 2.
ps -eo pid,ppid,comm | awk '$3 ~ /kworker|kthreadd/ && $2 != 2 && $2 != 0'
This command lists any process with `kworker` or `kthreadd` in its name that is not a child of PID 2 or 0.
4. Deep Dive: The `PF_KTHREAD` Flag
The kernel's internal `task_struct` contains a `flags` field, and one of these flags is PF_KTHREAD. This flag is set when a kernel thread is created and is immutable from userspace. It is the definitive, programmatic way to distinguish a kernel thread from a userspace process. While not directly exposed in a simple `ps` output, it is available in /proc/
/stat</code>.
<h2 style="color: yellow;">Step-by-Step Guide: Checking the `PF_KTHREAD` Flag</h2>
<ol>
<li>Find the `flags` Value: The 9th field in `/proc/[bash]/stat` contains the task flags in decimal.
[bash]
awk '{print $9}' /proc/<PID>/stat
PF_KTHREAD: The `PF_KTHREAD` flag is defined as `0x00200000` (2097152 in decimal). To check if the flag is set, perform a bitwise AND operation. A simple shell script can do this:
flags=$(awk '{print $9}' /proc/<PID>/stat)
if [ $((flags & 2097152)) -1e 0 ]; then
echo "Process is a kernel thread"
else
echo "Process is a userspace process"
fi
PF_KTHREAD, you can use it to filter processes based on flags. The `flags` field is available via the `flags` output specifier, though this is less common.
ps -eo pid,comm,flags | awk '$3 ~ /00200000/'
5. Building a Detection Engineering Pipeline
Effective defense against kernel thread masquerading requires a multi-layered approach that correlates these three immutable signals. A single alert (e.g., a `prctl` syscall) is not definitive; it's the combination of signals that reveals the truth.
Step-by-Step Guide: Correlating Detection Signals
- Ingest Telemetry: Centralize logs from auditd, Elastic Defend, and system process monitoring tools.
- Create Correlation Rules: Develop detection rules that look for the co-occurrence of suspicious activity. For example:
- Alert on a `prctl(PR_SET_NAME)` call followed by a process with a kernel-thread name and a non-1ull executable.
- Alert on any process with a kernel-thread name that has a PPID other than 2.
3. Example EQL Correlation (Conceptual):
sequence by process.pid
[process where auditd.data.syscall == "prctl" and auditd.data.a0 == "f"]
[process where process.name : ("kworker", "kthread") and process.executable != null]
4. Triage and Investigate: When an alert fires, follow these steps:
- Review the process details, focusing on the `process.executable` path.
- Check the process's parent process to understand the execution context.
- Investigate the file at the specified path; verify its hash against known malware databases.
- Examine the command-line arguments for unusual parameters.
- Review system logs around the time of the event.
- Leverage threat intelligence for known indicators of compromise.
What Undercode Say:
- Key Takeaway 1: Name is a Liar, Identity is Truth. The core principle is that attackers can manipulate names (
argv[bash],comm) but cannot forge the kernel-assigned identity (PF_KTHREAD, empty executable, `kthreadd` ancestry). Defenders must shift their focus from what a process calls itself to what it is. - Key Takeaway 2: Detection is a Triangulation. No single signal is sufficient. A `prctl` call is normal in many applications. An empty executable is the hallmark of a kernel thread. It is the combination of a suspicious name change and a non-empty executable and an incorrect parent PID that creates a high-fidelity detection signal. This multi-faceted approach turns a potential false positive into a definitive threat indicator.
Analysis:
Ruben Groenewoud's insight cuts to the heart of a fundamental weakness in host-based detection: the over-reliance on superficial attributes. By demonstrating that a userspace process can perfectly mimic the name of a kernel thread but never its essence, he provides a clear, actionable framework for defenders. The three signals—executable path, parent PID, and the `PF_KTHREAD` flag—are not just academic; they are the basis for production-grade detection rules in tools like Elastic Security. This approach represents a maturation of detection engineering, moving from simple pattern matching (e.g., "alert on process named 'kworker'") to behavioral and identity-based analysis. The challenge for defenders is no longer what to look for, but how to efficiently collect and correlate these signals across their entire infrastructure. The adoption of EDR solutions that expose these kernel-level attributes is crucial. Furthermore, the mention of attackers potentially executing under the real `kthreadd` highlights an advanced tradecraft that defenders must be aware of, pushing the cat-and-mouse game to even deeper levels of the operating system.
Prediction:
- +1 As detection engineering matures, we will see a proliferation of rules that focus on process identity attributes (
PF_KTHREAD, parent PID, executable path) rather than process names. This will significantly increase the cost for attackers attempting to masquerade as system processes. - -1 Attackers will respond by developing more sophisticated techniques to manipulate kernel-level attributes, potentially exploiting kernel vulnerabilities to set `PF_KTHREAD` on a userspace process or to forge parent PID information. This will lead to an arms race in kernel-level detection and anti-detection.
- +1 The principles outlined in this post will be extended to other operating systems, leading to cross-platform detection frameworks that focus on immutable process identities rather than mutable names.
- -1 The increasing complexity of detection engineering will widen the gap between well-resourced security teams and those with limited capabilities. Smaller organizations may struggle to implement and maintain these advanced detection pipelines, leaving them more vulnerable to stealthy attacks.
- +1 The integration of these detection signals into SIEM and EDR platforms will become more seamless, with vendors providing out-of-the-box correlation rules that reduce the burden on security analysts and improve overall detection coverage.
▶️ Related Video (82% Match):
🎯Let’s Practice For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
IT/Security Reporter URL:
Reported By: Ruben Groenewoud - Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅


