Step-by-Step Linux Process Injection Guide Part 1 - Overview
Step-by-Step Linux Process Injection Guide Series:
- Step-by-Step Linux Process Injection Guide Part 1 - Overview
1. Introduction
Process injection is a technique where one process forces another already-running process to load and execute arbitrary code. On Windows, the classic approach is CreateRemoteThread() combined with LoadLibrary(). On Linux, the equivalent relies on ptrace(). It’s the same system call that debuggers like GDB use to attach to processes, read and write their memory, and manipulate their registers.
This series walks through a full ptrace-based injection from start to finish. We will cover attaching to a target, setting up a shellcode trampoline, resolving function addresses despite ASLR, loading a shared library into the target’s address space, and restoring the target to its original state. By the end, you should understand every step well enough to read (or write) an injector yourself.
2. Related Works
This guide draws on the following sources:
-
gaffe23/linux-inject – an open-source injector that supports x86, x86_64, and ARM. It compiles a C function (injectSharedLibrary()) into position-independent shellcode, copies the compiled bytes into the target, and uses multi-breakpoint orchestration to call __libc_dlopen_mode().
-
Akamai – The Definitive Guide to Linux Process Injection – a thorough walkthrough of the injection technique with additional coverage on stealth: how to unload the injected library and remap its segments as anonymous memory so it no longer appears in /proc/pid/maps.
3. TLDR
At its core, any ptrace-based injection follows five steps:
-
Attach to the target process – Use ptrace(PTRACE_ATTACH) to pause the target and gain control of its execution.
-
Set the trampoline – Scan /proc/pid/maps for the first executable memory region. Back up the original bytes at that address and the target’s register state. Then overwrite the first few bytes with a small shellcode trampoline that aligns the stack, calls a function whose address is loaded into a register, and traps back to the injector. On x86_64, this is just seven bytes:
Trampoline Shellcode0x48 0x83 0xe4 0xf0 // and rsp, -16 (16-byte stack alignment) 0xff 0xd0 // call rax (call function pointed to by rax) 0xcc // int3 (trap back to injector)
With this trampoline in place, the injector can call any function in the target by loading the function address into rax, setting argument registers (rdi, rsi, rdx, …) per the calling convention, pointing rip at the trampoline, and resuming execution with PTRACE_CONT. The int3 at the end raises SIGTRAP, which pauses the target and returns control to the injector so it can read the return value from rax.
-
Resolve libc function addresses – Both the injector and the target link against the same libc.so.6, so function offsets from the libc base are identical. linworm uses dlsym() and dlinfo(RTLD_DI_LINKMAP) in its own process to get the offset of malloc, free, and dlopen from the libc base, then reads the target’s libc base address from /proc/pid/maps and adds the offset. This works regardless of ASLR.
-
Load the shared library – Using the trampoline, the injector makes a series of remote function calls in the target:
- malloc(path_len) to allocate a heap buffer
- PTRACE_POKETEXT to write the absolute path of the .so into that buffer
- dlopen(path_buf, RTLD_NOW) to load the library (any __attribute__((constructor)) function runs automatically)
- free(path_buf) to release the path buffer
-
Restore and detach – Write the backed-up original bytes back to the injection site, restore the original register state, and call ptrace(PTRACE_DETACH). The target resumes execution from where it was interrupted as if nothing happened. The loaded shared library remains mapped in the target’s address space.
4. Conclusion
Now that you more or less get the big picture, we will do a deeper dive in parts to come. See you in the next one!
5. References
Step-by-Step Linux Process Injection Guide Series:
- Step-by-Step Linux Process Injection Guide Part 1 - Overview