Hacktricks-skills freebsd-ptrace-rfi-injection
Guide for FreeBSD/PS5 usermode process injection using ptrace RFI and vm_map PROT_EXEC bypass. Use this skill whenever the user mentions FreeBSD exploitation, PS5 payload injection, ptrace-based RFI, vm_map protection bypass, kernel R/W primitives, process injection, ELF injection, or any scenario involving usermode code execution on FreeBSD-based systems. This skill covers the complete workflow from kernel primitive usage to payload delivery.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/binary-exploitation/freebsd-ptrace-rfi-vm_map-prot_exec-bypass-ps5/SKILL.MDFreeBSD ptrace RFI and vm_map PROT_EXEC Bypass
A practical guide for usermode process injection on FreeBSD-based systems (including PS5) when you have kernel read/write primitives.
What This Skill Does
This skill helps you:
- Patch process credentials to enable ptrace/mdbg debugging authority
- Discover target processes via kernel allproc list traversal
- Bypass PROT_EXEC restrictions by modifying vm_map entries in kernel memory
- Perform Remote Function Invocation (RFI) using ptrace to call functions inside target processes
- Load and execute arbitrary ELF payloads in target processes
- Cleanly detach after payload execution
Prerequisites
Required:
- Kernel read/write primitives (from an exploit chain)
- Knowledge of target firmware/kernel version (offsets are version-specific)
- Access to target system (local or network)
Assumptions:
- You already have kernel R/W capabilities
- Target is FreeBSD-based (PS5, FreeBSD derivatives)
- Hypervisor allows data-only kernel writes (no CR0.WP/CR4.SMEP modifications)
Core Workflow
Step 1: Process Discovery via allproc
FreeBSD maintains a doubly-linked list of processes in kernel
.data at allproc. Use your kernel read primitive to iterate it:
struct proc* find_proc_by_name(const char* proc_name) { uint64_t next = 0; kernel_copyout(KERNEL_ADDRESS_ALLPROC, &next, sizeof(uint64_t)); struct proc* proc = malloc(sizeof(struct proc)); do { kernel_copyout(next, (void*)proc, sizeof(struct proc)); if (!strcmp(proc->p_comm, proc_name)) return proc; kernel_copyout(next, &next, sizeof(uint64_t)); } while (next); free(proc); return NULL; }
Key points:
is firmware-dependentKERNEL_ADDRESS_ALLPROC
is a fixed-size process name fieldp_comm- Consider PID-based lookups if name matching is unreliable
Step 2: Elevate Credentials for Debugging
On PS5,
struct ucred includes an Authority ID field. Writing the debugger Authority ID grants ptrace/mdbg over other processes:
void set_ucred_to_debugger() { struct proc* proc = get_proc_by_pid(getpid()); if (proc) { uintptr_t ptrace_authid = 0x4800000000010003ULL; // debugger Authority ID kernel_copyin(&ptrace_authid, (uintptr_t)proc->p_ucred + 0x58, sizeof(uintptr_t)); free(proc); } }
Important:
- Offset
is PS5 firmware-specific; verify per version0x58 - After this write, your injector can attach to user processes via ptrace/mdbg
Step 3: Bypass PROT_EXEC via vm_map Modification
Userland
mmap may be constrained to PROT_READ|PROT_WRITE. FreeBSD tracks address space in vm_map entries. Modify kernel-owned metadata to add execute permission:
struct vm_map_entry { struct vm_map_entry *prev, *next, *left, *right; vm_offset_t start, end, avail_ssize; vm_size_t adj_free, max_free; union vm_map_object object; vm_ooffset_t offset; vm_eflags_t eflags; vm_prot_t protection; // <-- Modify this vm_prot_t max_protection; // <-- And this if needed vm_inherit_t inheritance; int wired_count; vm_pindex_t lastr; };
Implementation approach:
- Locate the target's
structurevm_map - Walk entries (linear via
or BST vianext
for O(log n))left/right - Find the entry covering your RW region
- Set
entry->protection |= PROT_EXEC - Optionally set
entry->max_protection |= PROT_EXEC
This bypasses userland mmap policy by editing kernel metadata directly.
Step 4: Remote Function Invocation (RFI)
FreeBSD lacks Windows-style
VirtualAllocEx/CreateRemoteThread. Instead, drive the target to call functions on itself under ptrace control:
RFI Sequence:
- Attach to target process (
or PS5-specific mdbg)PTRACE_ATTACH - Select a thread and save its context (registers, PC, SP, flags)
- Write argument registers per ABI (x86_64 SysV or arm64 AAPCS64)
- Set PC to target function address
- Single-step or continue until controlled stop (breakpoint/signal)
- Read return values from registers
- Restore original context and continue
Example - Calling ELF loader:
intptr_t entry = elfldr_load(target_pid, (uint8_t*)elf_in_target); intptr_t args = elfldr_payload_args(target_pid); printf("[+] ELF entrypoint: %#02lx\n[+] Payload Args: %#02lx\n", entry, args);
The loader maps segments, resolves imports, applies relocations, and returns:
- Entry point (often CRT bootstrap)
- Opaque
pointer for your stagerpayload_args
Step 5: Threaded Stager and Clean Detach
Create a minimal stager that spawns a pthread for your payload, then triggers a breakpoint for clean detach:
int __attribute__((section(".stager_shellcode$1"))) stager(SCEFunctions* functions) { pthread_t thread; functions->pthread_create_ptr(&thread, 0, (void*(*)(void*))functions->elf_main, functions->payload_args); asm("int3"); return 0; }
Key points:
/SCEFunctions
provided by loader/SDK gluepayload_args- After breakpoint and detach, payload continues in its own thread
- This prevents deadlock and allows clean injector exit
End-to-End Pipeline
A working implementation typically includes:
Injector Server (NineS example):
- Listens on TCP port (e.g., 9033)
- Receives header with target process name + ELF image
- Handles injection workflow
Client Script:
python3 ./send_injection_elf.py SceShellUI hello_world.elf <PS5_IP>
Hello-world payload:
#include <stdio.h> #include <unistd.h> #include <ps5/klog.h> int main() { klog_printf("Hello from PID %d\n", getpid()); return 0; }
Practical Considerations
Firmware-Specific Offsets
addressallproc
authority offsetucred
layoutvm_map- ptrace/mdbg details
Always verify offsets per firmware version.
Hypervisor Protections
- XOM (execute-only
) prevents reading/writing kernel code.text - Clearing CR0.WP or disabling CR4.SMEP causes vmexit (crash)
- Only data-only kernel writes are viable
Alternative: JIT Memory
Some processes expose PS5 JIT APIs to allocate executable pages. The vm_map protection flip removes the need to rely on JIT/mirroring tricks.
Robustness
- Keep register save/restore robust
- On failure, you can deadlock or crash the target
- Test thoroughly before production use
Public Tooling
| Tool | Purpose | Repository |
|---|---|---|
| PS5 SDK | Dynamic linking, kernel R/W wrappers, vm_map helpers | https://github.com/ps5-payload-dev/sdk |
| elfldr | ELF loader for injection | https://github.com/ps5-payload-dev/elfldr |
| NineS | Injector server | https://github.com/buzzer-re/NineS/ |
| playstation_research_utils | Utilities/vm_map helpers | https://github.com/buzzer-re/playstation_research_utils |
| Mira | Related project | https://github.com/OpenOrbis/mira-project |
| gdbsrv | GDB server | https://github.com/ps5-payload-dev/gdbsrv |
References
When to Use This Skill
Use this skill when:
- You have kernel R/W primitives on FreeBSD/PS5 and need process injection
- You need to bypass PROT_EXEC restrictions on userland mappings
- You want to perform ptrace-based RFI on FreeBSD systems
- You're developing payloads for PS5 or FreeBSD-based systems
- You need to understand vm_map structure and protection bypass techniques
- You're working on post-exploitation after gaining kernel access
Common Pitfalls
- Wrong offsets: Always verify structure offsets for your specific firmware/kernel version
- Hypervisor violations: Don't attempt CR0.WP/CR4.SMEP modifications
- Register corruption: Ensure robust save/restore in RFI sequences
- Thread selection: Choose appropriate threads for injection (avoid critical system threads)
- Memory layout: Verify your RW region exists before attempting PROT_EXEC flip