The BSD layer provides POSIX APIs, the process model, credentials, and file systems. Most important for post-exploitation: modifying credentials to escalate privileges (root, unsandbox).


Process Model

struct proc

struct proc {
    pid_t               p_pid;          // Process ID
    struct task         *task;           // Mach task (1:1)
    struct ucred        *p_ucred;       // Credentials (uid, gid)
    struct filedesc     *p_fd;          // File descriptors
    struct pgrp         *p_pgrp;        // Process group
    struct session      *p_session;     // Session
    char                p_comm[17];     // Process name
    uint32_t            p_flag;         // Flags (P_TRACED, P_SYSTEM, ...)
    int                 p_csflags;      // Code signing flags
    // ...
    struct proc         *p_pptr;        // Parent process
    struct vnode        *p_textvp;      // Executable vnode
    uint64_t            p_uniqueid;     // Unique process ID
    void                *p_sandbox;     // Sandbox slot ← target for unsandbox
};

struct ucred (Credentials)

struct ucred {
    u_long      cr_ref;         // Reference count
    uid_t       cr_uid;         // Effective user ID ← target: set to 0 = root
    uid_t       cr_ruid;        // Real user ID
    uid_t       cr_svuid;       // Saved user ID
    gid_t       cr_gid;         // Effective group ID
    gid_t       cr_rgid;        // Real group ID
    gid_t       cr_svgid;       // Saved group ID
    short       cr_ngroups;     // Number of groups
    gid_t       cr_groups[16];  // Groups
    struct label *cr_label;     // MAC label (entitlements, sandbox)
    // ...
};

Post-Exploitation: Get Root

With kernel read/write:
1. Find the current process's proc struct in kernel memory
   - allproc linked list: kernel symbol → iterate
   - Or: current_proc() → p_pid match
2. Read p_ucred pointer
3. Write cr_uid = 0, cr_ruid = 0, cr_svuid = 0
4. Write cr_gid = 0
→ Process now has UID 0 = root!

Post-Exploitation: Unsandbox

With kernel read/write:
1. Find the proc struct
2. Set p_sandbox = NULL (or replace with a permissive profile)
→ Process is no longer sandboxed!

Or:
1. Find ucred → cr_label
2. Modify MAC label slots for sandbox

File Descriptors & VFS

File Descriptor Table

struct filedesc {
    struct fileproc **fd_ofiles;    // Array of file descriptors
    int             fd_nfiles;      // Number of open files
    // ...
};

struct fileproc {
    struct fileglob *fp_glob;       // Global file structure
    // ...
};

struct fileglob {
    int             fg_flag;        // Flags (O_RDONLY, O_WRONLY, ...)
    file_type_t     fg_type;        // DTYPE_VNODE, DTYPE_SOCKET, DTYPE_PIPE, ...
    void            *fg_data;       // Type-specific data (vnode, socket, pipe, ...)
    struct ucred    *fg_cred;       // Credentials at open time
    off_t           fg_offset;      // Current offset
    // ...
};

Pipe-Based Kernel R/W (Classic Technique)

1. pipe(fds) → create pipe pair
   Kernel allocates pipe buffer (struct pipe)

2. Trigger vulnerability → corrupt pipe buffer metadata:
   - Modify buffer address → point to kernel address
   - Modify buffer size → large enough

3. read(pipe_fd) → kernel reads from corrupted buffer address
   → kernel read primitive!

4. write(pipe_fd) → kernel writes to corrupted buffer address
   → kernel write primitive!

Syscall Interface

Syscall Types

Type Entry Number range Examples
BSD syscalls SVC #0x80 Positive read, write, open, fork
Mach traps SVC #0x80 Negative mach_msg, task_for_pid
Machine-dependent Various Platform-specific thread_fast_set_cthread_self

Syscall Table

// bsd/kern/syscalls.master — master syscall definition file
// Format: number type name(args...)

0   AUE_NULL    ALL { int nosys(void); }              // unused
1   AUE_EXIT    ALL { void exit(int rval); }
2   AUE_FORK    ALL { int fork(void); }
3   AUE_NULL    ALL { user_ssize_t read(int fd, ...) }
4   AUE_NULL    ALL { user_ssize_t write(int fd, ...) }
5   AUE_OPEN    ALL { int open(const char *path, int flags, int mode) }
// ...

Networking (Brief)

The kernel networking stack is an attack surface because:

  • Complex protocol implementations (TCP, UDP, ICMP, Multipath TCP)
  • Socket options handling (setsockopt, getsockopt)
  • necp (Network Extension Control Policy) – newer, less audited

Historical examples:

  • Multipath TCP exploit (Keen Lab, iOS 11)
  • Socket options heap overflow (various CVEs)

Resources

  • XNU source: bsd/kern/kern_proc.c, bsd/kern/kern_credential.c
  • XNU source: bsd/kern/syscalls.master (syscall definitions)
  • Jonathan Levin – macOS and iOS Internals Vol. II (BSD chapters)