FreeBSD ptrace RFI and vm_map PROT_EXEC bypass (PS5 case study)
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
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
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:
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:
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 :
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 :
- Attachez-vous à la cible et sélectionnez un thread ; PTRACE_ATTACH ou des flows mdbg spécifiques à PS5 peuvent s’appliquer.
- Sauvegardez le contexte du thread : registers, PC, SP, flags.
- É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.
- 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.
- 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:
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 :
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:
typedef struct __injector_data_t{
char proc_name[MAX_PROC_NAME];
Elf64_Ehdr elf_header;
} injector_data_t;
- Utilisation du client Python:
python3 ./send_injection_elf.py SceShellUI hello_world.elf <PS5_IP>
Exemple de payload Hello-world (enregistre dans klog) :
#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
- 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
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
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
HackTricks

