FreeBSD ptrace RFI と vm_map PROT_EXEC bypass(PS5 ケーススタディ)

Reading time: 11 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) プリミティブを持っている場合の FreeBSD 派生系にも一般化できる。ハイレベルでは以下の通り:

  • 現在のプロセスの資格情報 (ucred) をパッチしてデバッガ権限を付与し、任意のユーザプロセスに対して ptrace/mdbg を可能にする。
  • カーネルの allproc リストを辿ってターゲットプロセスを見つける。
  • カーネルのデータ書き込みでターゲットの vm_map 内の vm_map_entry.protection |= PROT_EXEC を反映させ、PROT_EXEC 制限をバイパスする。
  • ptrace を使って Remote Function Invocation (RFI) を実行する: スレッドを停止し、レジスタを設定してターゲット内の任意関数を呼び出し、再開して戻り値を収集し、状態を復元する。
  • プロセス内 ELF ローダを使ってターゲット内に任意の ELF ペイロードをマップして実行し、専用スレッドを生成してペイロードを実行させ、ブレークポイントを発生させてクリーンにデタッチする。

この手法に関連する PS5 ハイパーバイザ緩和策(コンテキストに即して要点):

  • XOM (execute-only .text) によりカーネル .text の読み書きが制限される。
  • CR0.WP をクリアしたり CR4.SMEP を無効化するとハイパーバイザで vmexit(クラッシュ)が発生する。データ専用のカーネル書き込みのみが実行可能。
  • ユーザーランドの mmap はデフォルトで PROT_READ|PROT_WRITE に制限される。PROT_EXEC を付与するにはカーネルメモリの vm_map エントリを編集する必要がある。

この手法は post-exploitation であり、エクスプロイトチェーンからの kernel R/W primitives を前提としている。公開ペイロードは執筆時点でファームウェア 10.01 までで動作を示している。

カーネルのデータ専用プリミティブ

allproc によるプロセス探索

FreeBSD はカーネルの .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);
}

注意:

  • 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 を介してユーザプロセスにアタッチし、計測/操作できる。

Bypassing RW-only user mappings: vm_map PROT_EXEC flip

Userland mmap may be constrained to PROT_READ|PROT_WRITE. FreeBSD tracks a process’s address space in a vm_map of vm_map_entry nodes (BST plus list). Each entry carries protection and max_protection fields:

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:

  • エントリは next で線形に辿るか、アドレス範囲での検索を O(log n) にするため balanced-tree (left/right) を使って探索します。
  • 自分で制御できる既知の RW 領域(scratch buffer や mapped file)を選び、PROT_EXEC を付与してコードや loader thunks を配置できるようにします。
  • PS5 SDK のコードには map-entry の高速検索や保護切替のためのヘルパーが含まれています。

これはカーネル所有のメタデータを直接編集することで userland の mmap ポリシーをバイパスします。

Remote Function Invocation (RFI) with ptrace

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

  1. ターゲットにアタッチしてスレッドを選択します;PTRACE_ATTACH や PS5 固有の mdbg フローが使える場合があります。
  2. スレッドのコンテキストを保存:registers、PC、SP、flags。
  3. ABI に従って引数レジスタを書き込みます(x86_64 SysV または arm64 AAPCS64)、PC をターゲット関数に設定し、必要に応じて追加の引数やスタックを配置します。
  4. シングルステップまたは続行して制御された停止まで実行(例:software breakpoint や signal)、停止後に regs から戻り値を読み取ります。
  5. 元のコンテキストを復元して続行します。

Use cases:

  • ターゲットメモリに置いた ELF イメージへのポインタを渡して、in-process ELF loader(例:elfldr_load)を呼び出す。
  • ヘルパールーチンを呼んで、返された entrypoints や 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);

loader は segments をマッピングし、imports を解決し、relocations を適用して entry(しばしば CRT bootstrap)と、stager が payload の main() に渡す opaque payload_args ポインタを返す。

スレッド化された stager とクリーンな detach

target 内の最小限の 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 pointers は loader/SDK glue によって提供される。
  • breakpoint と detach の後、payload は独自の thread で処理を継続する。

エンドツーエンドパイプライン (PS5 参照実装)

動作する実装は、小さな TCP injector server と client script として提供される:

  • NineS server は TCP 9033 で待ち受け、ターゲットプロセス名を含むヘッダと、その後に続く ELF image を受信する:
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;
}

実用上の考慮事項

  • オフセットと定数(allproc、ucred authority offset、vm_map layout、ptrace/mdbg details)はファームウェア固有で、リリースごとに更新する必要があります。
  • ハイパーバイザーの保護によりデータのみのカーネル書き込みが強制されます。CR0.WP や CR4.SMEP をパッチしようとしないでください。
  • JIT メモリは代替手段です:一部のプロセスは実行可能ページを割り当てるための PS5 JIT API を公開しています。vm_map protection flip により JIT/ミラーリングのトリックに頼る必要がなくなります。
  • レジスタの保存/復元は堅牢に保ってください。失敗するとターゲットがデッドロックしたりクラッシュしたりします。

公開ツール

  • 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をサポートする