FreeBSD ptrace RFI and vm_map PROT_EXEC bypass (PS5 case study)

Reading time: 8 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Aperçu

Cette page documente une technique pratique d'injection de processus/ELF usermode Unix/BSD sur PlayStation 5 (PS5), basée sur FreeBSD. La méthode se généralise aux dérivés FreeBSD lorsque vous disposez déjà de primitives de lecture/écriture du kernel (R/W). Vue d'ensemble :

  • Patchez les credentials du processus courant (ucred) pour accorder l'autoritĂ© de debugger, permettant ptrace/mdbg sur des processus utilisateur arbitraires.
  • Trouvez les processus cibles en parcourant la liste kernel allproc.
  • Contournez les restrictions PROT_EXEC en basculant vm_map_entry.protection |= PROT_EXEC dans le vm_map de la cible via des Ă©critures de donnĂ©es kernel.
  • Utilisez ptrace pour effectuer Remote Function Invocation (RFI) : suspendre un thread, rĂ©gler les registres pour appeler des fonctions arbitraires dans la cible, reprendre, rĂ©cupĂ©rer les valeurs de retour et restaurer l'Ă©tat.
  • Mappez et exĂ©cutez des payloads ELF arbitraires dans la cible en utilisant un ELF loader in-process, puis crĂ©ez un thread dĂ©diĂ© qui exĂ©cute votre payload et dĂ©clenche un breakpoint pour dĂ©tacher proprement.

Mitigations hyperviseur PS5 à noter (contextualisées pour cette technique) :

  • XOM (execute-only .text) empĂȘche la lecture/Ă©criture du .text kernel.
  • RĂ©initialiser CR0.WP ou dĂ©sactiver CR4.SMEP provoque un vmexit hyperviseur (crash). Seules les Ă©critures kernel affectant uniquement les donnĂ©es sont viables.
  • Le mmap en userland est restreint Ă  PROT_READ|PROT_WRITE par dĂ©faut. L'attribution de PROT_EXEC doit ĂȘtre faite en Ă©ditant les entrĂ©es vm_map dans la mĂ©moire kernel.

Cette technique est post-exploitation : elle suppose l'existence de primitives R/W kernel issues d'une chaßne d'exploit. Des payloads publics démontrent cela jusqu'au firmware 10.01 au moment de la rédaction.

Primitives kernel 'data-only'

Découverte des processus via allproc

FreeBSD maintient une liste doublement chaßnée de processus dans .data du kernel à allproc. Avec une primitive de lecture kernel, itérez-la pour localiser les noms de processus et les PIDs:

c
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);
}

Notes:

  • KERNEL_ADDRESS_ALLPROC dĂ©pend du firmware.
  • p_comm est un nom de taille fixe ; envisagez des recherches pid->proc si nĂ©cessaire.

Élever les credentials pour le dĂ©bogage (ucred)

Sur PS5, struct ucred contient un champ Authority ID accessible via proc->p_ucred. Écrire l'Authority ID du debugger accorde ptrace/mdbg sur d'autres processus:

c
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);
}
}
  • L'offset 0x58 est spĂ©cifique Ă  la famille de firmware PS5 et doit ĂȘtre vĂ©rifiĂ© par version.
  • AprĂšs cette Ă©criture, l'injecteur peut attacher et instrumenter les processus utilisateur via ptrace/mdbg.

Contournement des mappings RW-only utilisateur : vm_map PROT_EXEC flip

Le Userland mmap peut ĂȘtre contraint Ă  PROT_READ|PROT_WRITE. FreeBSD suit l'espace d'adressage d'un processus dans un vm_map de nƓuds vm_map_entry (BST plus liste). Chaque entrĂ©e porte les champs protection et max_protection :

c
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;
};

Avec kernel R/W you can locate the target’s vm_map and set entry->protection |= PROT_EXEC (and, if needed, entry->max_protection). Remarques pratiques d'implĂ©mentation :

  • Parcourez les entrĂ©es soit linĂ©airement via next, soit en utilisant le balanced-tree (left/right) pour une recherche en O(log n) par plage d'adresses.
  • Choisissez une rĂ©gion RW connue que vous contrĂŽlez (scratch buffer ou mapped file) et ajoutez PROT_EXEC pour pouvoir y placer du code ou des loader thunks.
  • Le code PS5 SDK fournit des helpers pour la recherche rapide de map-entry et le basculement des protections.

Cela contourne la politique mmap de l'userland en modifiant directement les métadonnées appartenant au kernel.

Remote Function Invocation (RFI) with ptrace

FreeBSD ne dispose pas des Ă©quivalents Windows VirtualAllocEx/CreateRemoteThread. À la place, forcez la cible Ă  appeler des fonctions sur elle‑mĂȘme sous contrĂŽle ptrace :

  1. Attachez-vous à la cible et sélectionnez un thread ; PTRACE_ATTACH ou des flows mdbg spécifiques à PS5 peuvent s'appliquer.
  2. Sauvegardez le contexte du thread : registers, PC, SP, flags.
  3. Écrivez les argument registers selon l'ABI (x86_64 SysV ou arm64 AAPCS64), rĂ©glez PC sur la fonction cible, et placez optionnellement des arguments/stack supplĂ©mentaires si nĂ©cessaire.
  4. ExĂ©cutez en single-step ou continuez jusqu'Ă  un arrĂȘt contrĂŽlĂ© (par ex. breakpoint logiciel ou signal), puis relisez les valeurs de retour depuis les regs.
  5. Restaurez le contexte original et continuez.

Use cases:

  • Appeler un ELF loader en-processus (par ex. elfldr_load) avec un pointeur vers votre image ELF dans la mĂ©moire cible.
  • Invoquer des routines helper pour rĂ©cupĂ©rer les entrypoints retournĂ©s et les pointeurs payload-args.

Example of driving the ELF loader:

c
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);

Le loader mappe les segments, résout les imports, applique les relocations et retourne l'entry (souvent un CRT bootstrap) ainsi qu'un pointeur opaque payload_args que votre stager passe au main() du payload.

Threaded stager et détachement propre

Un stager minimal à l'intérieur de la cible crée un nouveau pthread qui exécute le main de l'ELF puis déclenche int3 pour signaler à l'injector de se détacher :

c
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;
}
  • Les pointeurs SCEFunctions/payload_args sont fournis par le loader/SDK glue.
  • AprĂšs le breakpoint et le detach, le payload continue dans son propre thread.

Pipeline de bout en bout (implémentation de référence PS5)

Une implémentation fonctionnelle s'accompagne d'un petit serveur injecteur TCP plus d'un script client :

  • Le serveur NineS Ă©coute sur TCP 9033 et reçoit un header contenant le nom du processus cible suivi de l'image ELF:
c
typedef struct __injector_data_t{
char       proc_name[MAX_PROC_NAME];
Elf64_Ehdr elf_header;
} injector_data_t;
  • Utilisation du client Python:
bash
python3 ./send_injection_elf.py SceShellUI hello_world.elf <PS5_IP>

Exemple de payload Hello-world (enregistre dans klog) :

c
#include <stdio.h>
#include <unistd.h>
#include <ps5/klog.h>
int main(){
klog_printf("Hello from PID %d\n", getpid());
return 0;
}

Considérations pratiques

  • Offsets et constantes (allproc, ucred authority offset, vm_map layout, ptrace/mdbg details) sont spĂ©cifiques au firmware et doivent ĂȘtre mis Ă  jour Ă  chaque release.
  • Les protections de l'hyperviseur forcent des data-only kernel writes ; n'essayez pas de patcher CR0.WP ou CR4.SMEP.
  • JIT memory est une alternative : certains processus exposent des PS5 JIT APIs pour allouer des pages exĂ©cutables. L'inversion de la protection vm_map supprime la nĂ©cessitĂ© de s'appuyer sur des astuces JIT/mirroring.
  • Assurez-vous que la sauvegarde/restauration des registres est robuste ; en cas d'Ă©chec, vous pouvez provoquer un deadlock ou un crash de la cible.

Outils publics

  • 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

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks