FreeBSD ptrace RFI and vm_map PROT_EXEC bypass (PS5 ์‚ฌ๋ก€ ์—ฐ๊ตฌ)

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๋ฅผ ์ฐพ๋Š”๋‹ค:

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:

  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:

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

์ฐธ๊ณ ์ž๋ฃŒ

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 ์ง€์›ํ•˜๊ธฐ