The XNU kernel is written in C. Understanding C at the memory layout level, not just at the syntax level, is a mandatory requirement.


Why You Need Deep C Knowledge

  • Kernel source code (XNU) is C – you will read it daily
  • Exploit development requires precise understanding of the binary layout of structs, unions, and pointers
  • Heap exploitation depends on understanding memory allocator behavior
  • Type confusion exploits leverage differences in how the compiler lays out structs

Key Knowledge

1. Pointers & Memory

// Pointer arithmetic — each +1 jumps sizeof(*ptr) bytes
uint64_t *p = (uint64_t *)0x1000;
p + 1;  // = 0x1008, NOT 0x1001

// Void pointer — no type info, requires manual cast
void *raw = malloc(64);
uint32_t val = *(uint32_t *)((char *)raw + 0x10);  // read 4 bytes at offset 0x10

// Function pointer — the foundation for vtable exploitation
typedef int (*handler_fn)(void *ctx, int cmd);
handler_fn fn = (handler_fn)0xFFFFF00001234000;  // assume a kernel address
fn(ctx, 0);  // indirect call

// Double pointer — commonly seen in kernel APIs
kern_return_t task_for_pid(mach_port_t target, int pid, mach_port_t *task);
// task is an output parameter — the kernel writes to *task

2. Struct Layout & Padding

// The compiler adds padding to align fields
struct example {
    uint8_t  a;      // offset 0x00, size 1
    // 7 bytes padding
    uint64_t b;      // offset 0x08, size 8
    uint32_t c;      // offset 0x10, size 4
    uint16_t d;      // offset 0x14, size 2
    // 2 bytes padding
};  // total size = 0x18 (24 bytes)

// Packed struct — no padding (used in protocol/file format parsing)
struct __attribute__((packed)) header {
    uint8_t  magic;   // offset 0x00
    uint64_t size;    // offset 0x01 (unaligned!)
};  // total size = 9 bytes

Why this matters:

  • Exploits need to know the exact offset of each field in a kernel struct
  • Type confusion exploits replace object A with object B – field X of A must be at the same offset as field Y of B
  • offsetof(struct, field) gives you the exact offset

3. Union – Type Punning

// Unions allow interpreting the same memory in multiple ways
union isa_t {
    uintptr_t bits;
    struct {
        uintptr_t nonpointer     : 1;
        uintptr_t has_assoc      : 1;
        uintptr_t has_cxx_dtor   : 1;
        uintptr_t shiftcls       : 33;
        uintptr_t magic          : 6;
        uintptr_t weakly_referenced : 1;
        // ...
    };
};

// Used in exploitation to reinterpret kernel objects
union {
    uint64_t raw;
    struct {
        uint32_t lo;
        uint32_t hi;
    };
} kaddr;
kaddr.raw = 0xFFFFF00012345678;
// kaddr.lo = 0x12345678, kaddr.hi = 0xFFFFF000

4. Bitwise Operations

// Masking — extract specific bits
uint64_t addr = ptr & 0x0000FFFFFFFFFFFF;  // strip PAC bits (upper 16)
uint64_t page = addr & ~0x3FFF;            // page-align (16KB pages on iOS)
uint64_t offset = addr & 0x3FFF;           // offset within page

// Setting/clearing flags
uint32_t flags = pte;
flags |= PTE_VALID;           // set bit
flags &= ~PTE_READ_ONLY;     // clear bit
flags ^= PTE_ACCESSED;       // toggle bit

// Checking flags
if (pte & PTE_VALID) { ... }               // check single bit
if ((pte & MASK) == EXPECTED) { ... }       // check bit pattern

// Shifts — building addresses, extracting fields
uint64_t phys = (pte & 0x0000FFFFFFFFF000ULL);  // extract physical address from PTE
uint64_t ppn = phys >> 14;                        // physical page number (16KB pages)

5. Memory Management Patterns in the Kernel

// Zone allocation (XNU pattern)
zone_t my_zone = zone_create("my_objects", sizeof(struct my_obj), ZC_NONE);
struct my_obj *obj = zalloc(my_zone);
zfree(my_zone, obj);
// BUG: obj still points to freed memory → UAF

// Kalloc (general purpose)
void *buf = kalloc(256);  // allocated from kalloc.256 zone
kfree(buf, 256);          // must pass the correct size!

// Reference counting pattern
void obj_retain(struct obj *o) { os_ref_retain(&o->ref_count); }
void obj_release(struct obj *o) {
    if (os_ref_release(&o->ref_count) == 0)
        obj_free(o);  // BUG if someone still holds a pointer
}

// Mach port lifecycle — common exploitation target
struct ipc_port {
    struct ipc_object   ip_object;      // header
    struct ipc_mqueue   ip_messages;    // message queue
    union {
        struct ipc_kmsg *data;
        struct task *task;              // for task ports
        struct semaphore *sema;
    } kdata;
    // ...
};

6. Volatile & Memory Barriers

// Volatile — compiler does not optimize away reads/writes
volatile uint32_t *mmio_reg = (volatile uint32_t *)0x200000000;
uint32_t val = *mmio_reg;  // actually reads the hardware register

// Memory barriers — important for multi-core exploitation
__asm__ volatile("dmb sy" ::: "memory");  // Data Memory Barrier
__asm__ volatile("dsb sy" ::: "memory");  // Data Synchronization Barrier
__asm__ volatile("isb" ::: "memory");     // Instruction Synchronization Barrier

XNU Kernel Source Code Patterns to Recognize

// Kernel function error handling pattern
kern_return_t
some_kernel_function(args...)
{
    kern_return_t kr = KERN_SUCCESS;

    // ... validation ...
    if (bad_input) {
        kr = KERN_INVALID_ARGUMENT;
        goto out;
    }

    // ... do work ...

out:
    // cleanup
    return kr;
}

// IOKit C++ pattern (restricted C++)
class IOSurfaceRootUserClient : public IOUserClient {
    virtual IOReturn externalMethod(uint32_t selector,
        IOExternalMethodArguments *args,
        IOExternalMethodDispatch *dispatch,
        OSObject *target,
        void *reference) override;
};

Resources

  • K&R – The C Programming Language (classic, old but provides a solid foundation)
  • Expert C Programming: Deep C Secrets – Peter van der Linden
  • XNU Source Code – read directly
  • Compiler Explorer (godbolt.org) – see C compiled to ARM64 assembly

Exercises

  1. Manually compute offsets for 5 different structs with mixed types, verify with offsetof()
  2. Write type-punning code using unions to extract fields from a 64-bit packed value
  3. Implement a simple zone allocator in C (fixed-size allocations, freelist)
  4. Read XNU source: open osfmk/kern/ipc_kobject.c and trace 1 function call path
  5. Compile C to ARM64, read the assembly output, map each C statement to the corresponding instructions