IOKit drivers are the largest attack surface for iOS kernel exploitation. Each driver exposes external methods via IOUserClient that userspace can call – similar to ioctl() on Linux.


IOKit Architecture

Driver Hierarchy

IOService (abstract base)
  └── IORegistryEntry (device tree node)
       └── Concrete driver class (e.g., IOSurface)
            └── IOUserClient subclass (e.g., IOSurfaceRootUserClient)
                 ↑
                 Userspace calls IOConnectCallMethod() here

IOUserClient – The Interface

IOUserClient is the bridge between userspace and the kernel driver:

// Kernel side
class IOSurfaceRootUserClient : public IOUserClient {
    // External method dispatch table
    static const IOExternalMethodDispatch sMethods[] = {
        // selector 0
        { &IOSurfaceRootUserClient::s_create_surface,
          0,                    // scalar input count
          kIOUCVariableStructureSize, // struct input size
          0,                    // scalar output count
          0 },                  // struct output size

        // selector 1
        { &IOSurfaceRootUserClient::s_release_surface,
          1, 0, 0, 0 },

        // ... more methods
    };

    IOReturn externalMethod(uint32_t selector,
        IOExternalMethodArguments *args,
        IOExternalMethodDispatch *dispatch,
        OSObject *target,
        void *reference) override;
};

// Userspace side
io_connect_t conn;
IOServiceOpen(service, mach_task_self(), 0, &conn);
IOConnectCallMethod(conn,
    selector,           // Method index
    scalarInput, scalarInputCount,
    structInput, structInputSize,
    scalarOutput, &scalarOutputCount,
    structOutput, &structOutputSize);

External Method Arguments

struct IOExternalMethodArguments {
    uint32_t    version;
    uint32_t    selector;       // Method selector

    // Scalar (register-like) arguments
    const uint64_t *scalarInput;
    uint32_t    scalarInputCount;
    uint64_t   *scalarOutput;
    uint32_t    scalarOutputCount;

    // Struct (buffer) arguments
    const void *structureInput;
    uint32_t    structureInputSize;
    void       *structureOutput;
    uint32_t    structureOutputSize;

    // Memory descriptor
    IOMemoryDescriptor *structureInputDescriptor;
    IOMemoryDescriptor *structureOutputDescriptor;
};

High-Value Targets

IOSurface / IOSurfaceRootUserClient

  • Role: Manages shared memory surfaces for graphics
  • Why important: Reachable from app sandbox, complex functionality
  • Exploitation history: Many CVEs, used in most modern exploit chains
  • Key methods: create_surface, set_value, get_value, release_surface
  • iOS 16+: IOSurfaceRootUserClient is restricted (duplicate table with fewer methods)

IOSurface kread/kwrite pattern:

1. Create IOSurface
2. Set serialized property values (controlled data in kernel)
3. Trigger vulnerability → overwrite IOSurface internal pointer
4. Get property value → reads from corrupted pointer address (kread)
5. Set property value → writes to corrupted pointer address (kwrite)

AGXAccelerator / IOGPU

  • Role: GPU driver
  • Attack surface: Command buffer parsing, shader compilation
  • iOS 16+: IOGPUDeviceUserClient restricted

AppleAVE2 / AppleAVE2UserClient

  • Role: Video encoding hardware
  • Characteristics: iOS-only, symbols stripped, harder to analyze
  • Exploitation: Accessible before iOS 16 restrictions

AppleSPU

  • Role: Signal Processing Unit
  • Newer target: Less analyzed, potentially less hardened

Reverse Engineering IOKit Drivers

1. Find UserClient Classes

# From kernelcache in IDA/Ghidra:
# Search for strings containing "UserClient"
# Or search for ::externalMethod references

# Using jtool2
jtool2 -d __DATA.__objc_classlist kernelcache | grep UserClient

2. Find the External Method Table

In IDA:
1. Find the class's externalMethod() implementation
2. Look for a dispatch table reference (usually in __DATA_CONST.__const)
3. Table entries are IOExternalMethodDispatch structs:
   struct IOExternalMethodDispatch {
       IOExternalMethodAction function;    // 8 bytes (function pointer)
       uint32_t checkScalarInputCount;     // 4 bytes
       uint32_t checkStructureInputSize;   // 4 bytes
       uint32_t checkScalarOutputCount;    // 4 bytes
       uint32_t checkStructureOutputSize;  // 4 bytes
   };

3. Map the Method Table

From the dispatch table, build a map:
  Selector 0 → create_surface(0 scalar, var struct, 0 scalar out, 0 struct out)
  Selector 1 → release_surface(1 scalar, 0 struct, 0 scalar out, 0 struct out)
  Selector 2 → ...

Each method = potential attack vector

4. Phrack Methodology


iOS 16+ Restrictions

Apple has restricted many IOKit UserClients:

Before iOS 16:
  App sandbox → IOSurfaceRootUserClient (full method table)
  App sandbox → IOGPUDeviceUserClient (full)

After iOS 16:
  App sandbox → IOSurfaceRootUserClient_Restricted (limited methods)
  App sandbox → IOGPUDeviceUserClient_Restricted (limited methods)

Restricted UserClients:
  - Duplicate class inheriting from the original
  - Separate method dispatch table with fewer methods
  - Sensitive methods (e.g., set_value, get_value) removed

Fuzzing IOKit

Manual Fuzzing

// Brute-force selectors with random input
for (uint32_t sel = 0; sel < 256; sel++) {
    uint64_t scalar[16];
    char structbuf[4096];
    arc4random_buf(scalar, sizeof(scalar));
    arc4random_buf(structbuf, sizeof(structbuf));

    IOConnectCallMethod(conn, sel,
        scalar, 16,
        structbuf, sizeof(structbuf),
        NULL, NULL, NULL, NULL);
}

Intelligent Fuzzing

  • Parse the method dispatch table to know the correct input sizes
  • Use Corellium kernel hooks to monitor crashes
  • Coverage-guided fuzzing (more complex but more effective)

Vulnerability Patterns in IOKit

Pattern Description Prevention
Race condition clientClose() concurrent with externalMethod() Locking, sequencing
UAF Object freed but pointer still used Reference counting
Integer overflow Size validation bypassed Safe integer arithmetic
Missing bounds check Buffer overread/overwrite Input validation
Type confusion Object cast to wrong type OSDynamicCast() checks
Double free Object freed twice Reference count tracking

Resources