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

9.3 KiB
Raw Blame History

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ć 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:

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ł:

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

Źródła

{{#include ../banners/hacktricks-training.md}}