> For the complete documentation index, see [llms.txt](https://docs.hex-rays.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hex-rays.com/ida-9.5/core/disassembler/concepts/driverkit.md).

# Apple DriverKit

This page covers how IDA analyses Apple DriverKit drivers: the type libraries it applies to them, the C++ classes it recovers from them, and the DriverKit shared cache that holds the frameworks they link against.

A DriverKit driver, or **DEXT**, is an IOKit driver that runs outside the kernel. It keeps the kernel's object model: its classes derive from `OSObject`, register through `OSMetaClass` and override `IOService::Start`. But it is compiled into a user-space library and runs in a process of its own, so IDA loads it as an ordinary Mach-O. Its classes get the same treatment as those of the KEXTs in an [Apple Kernelcache](/ida-9.5/core/disassembler/concepts/kc-workflow.md).

## Opening a driver

A DEXT ships as a `.dext` bundle, usually inside the app that installs it, under `Contents/Library/SystemExtensions/`. Open the executable inside the bundle. IDA picks the Mach-O loader, and everything this page describes happens at load time, with nothing to configure.

DriverKit binaries are 64-bit only, for x86-64 and arm64/arm64e. For a universal binary, pick the slice as for any [fat Mach-O](/ida-9.5/core/disassembler/concepts/loaders.md#archives-and-containers).

## The DriverKit shared cache

A driver does not link against the system's ordinary dylibs, but against DriverKit's own runtime, installed under `/System/DriverKit/`. Its frameworks — `DriverKit` itself, public families such as `USBDriverKit` or `HIDDriverKit`, and Apple-private ones such as `IO80211DriverKit` — ship in a dyld shared cache of their own, named `dyld_shared_cache_<arch>` like the system's, with its `.symbols` file beside it.

It opens like any other shared cache, through the [Dyld Shared Cache](/ida-9.5/core/disassembler/concepts/dsc-workflow.md) workflow: the [DSC Index](/ida-9.5/core/disassembler/concepts/dsc-workflow.md#the-dsc-index), on-demand loading, and the locators. The classes of an image are recovered when you load it.

<figure><img src="https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-29272fd8aba71109e8564603b791f02945268243%2Fdk_index.png?alt=media" alt="DSC Index on the DriverKit shared cache"><figcaption><p>The DriverKit shared cache in the DSC Index, with the Apple-private <code>IO80211DriverKit</code> loaded</p></figcaption></figure>

Many of the classes in the cache come from Apple-private frameworks and appear in no SDK. For those, the [instance structs](#instance-structs) IDA builds from the cache are the only layout available.

## Type libraries

DriverKit has an SDK of its own, separate from the macOS SDK. Its `IOService` shares a name with the kernel's, but is a different class with a different layout, and some of its classes, such as `IOUSBHostDevice`, share a name with unrelated types of the macOS user-space frameworks.

IDA ships type libraries built from the DriverKit SDK, family frameworks included, one per SDK major version:

| Architecture | Type libraries                             |
| ------------ | ------------------------------------------ |
| x86-64       | `driverkit19_x64` to `driverkit25_x64`     |
| arm64        | `driverkit20_arm64` to `driverkit25_arm64` |

IDA reads the DriverKit version from the binary's `LC_BUILD_VERSION`, or from the header of a shared cache, and loads the matching library, or the nearest one shipped. The version matters because family classes change size between SDKs.

The DriverKit library is loaded after the macOS one, which still supplies the base system types. Where the two define a class of the same name, the database gets DriverKit's layout.

## Class recovery

A KEXT registers its classes by running code, which IDA [reads back from the constructor calls](/ida-9.5/core/disassembler/concepts/kc-workflow.md#the-osmetaclass-class-hierarchy). A DEXT lists them instead: its `__DATA_CONST,__osclassinfo` section points to one record per class. The record gives the class's name, superclass, instance size and allocator, and points to a description of the methods the class exposes by remote call. IDA reads every class from its record rather than inferring it, so the classes are recovered even from a stripped driver.

For every class `X`, IDA:

* **types the record and its description**, and names the record `X_Class`;
* **types the slot that holds the class's metaclass**, `gXMetaClass`, and the allocator, `X_New`;
* **synthesizes an instance struct** derived from its superclass's, unless Local Types already holds a layout for the class;
* **types both vtables**, the object's and the metaclass's;
* **names the remote methods**, as described in [Remote methods](#remote-methods).

IDA only names what has no name yet: in a binary with symbols, the record and the metaclass slot keep their symbol names, `_X_Class` and `_gXMetaClass`.

<figure><img src="https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-1b8340b488b05beb2e335a91fccc1dbf91211415%2Fdk_record.png?alt=media" alt="A typed __osclassinfo record"><figcaption><p>The record of <code>IO80211APIUserClient</code>, typed: it points to the class's description, metaclass slot and allocator, and gives its instance size</p></figcaption></figure>

### Instance structs

The record gives each class's size and superclass, so even a class that no SDK declares gets a struct with its superclass as base:

<figure><img src="https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-653065e2bf8d510c81d35e4e57d41319f3e95d72%2Fdk_struct.png?alt=media" alt="Instance structs in Local Types"><figcaption><p>Structs IDA built for classes no SDK declares: <code>IO80211Controller</code> with an <code>XInterface</code> member, <code>IO80211APIUserClient</code> with only <code>ivars</code></p></figcaption></figure>

IDA names the members a DriverKit object carries past its superclass:

* **`XInterface`**, the vtable pointer of the class's `XInterface` mixin, when that mixin has virtual methods;
* **`ivars`**, a pointer to the class's private state, `X_IVars`. That layout belongs to the driver and is in no SDK, so the member is a `void *` unless a type library declares `X_IVars`. Declare the struct in **Local Types** as you work it out, and retype the member to point to it.

Any other remainder is left as a gap.

When a class already has a layout, typically one imported from a type library, and its size differs from what the binary allocates, IDA keeps the existing fields and reports the difference in the Output window:

```
<class>: the type library lays out <size> bytes, this binary allocates <size>; using the type library's fields anyway
```

Usually the type library declares only the part of the class that its SDK makes public. The message can also mean that the type library is for a different SDK than the binary. A similar message reports a superclass that the type library lays out larger than the whole class; that class is then laid out without a base.

### Vtables

Each class has two vtables, its own and its metaclass's. IDA types them as `X_vtbl` and `XMetaClass_vtbl`, named `X::vtable` and `XMetaClass::vtable`. Every slot inherited from a class IDA has a type for carries that method's name and prototype.

<figure><img src="https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-2d979c6b8cc23bd1ca84a8a2952795e552687439%2Fdk_vtbl.png?alt=media" alt="Vtable structs in Local Types"><figcaption><p><code>IO80211APIUserClient_vtbl</code> between two metaclass vtables; the slots it inherits from <code>IOUserClient</code> carry their names and prototypes</p></figcaption></figure>

The vtables are found through their symbols, `vtable for X`. Drivers export them, so stripping usually leaves them in place, but a binary stripped of them gets no typed vtables.

## Remote methods

Methods that can be called from outside a driver's process go through remote procedure calls. Each has a 64-bit message ID, and each class has a `_Dispatch` function that routes an incoming message to the method its ID names.

The type libraries define every message ID of the DriverKit SDK, as the enum `IORPCMessageID` with members named `<Class>_<Method>_ID`. IDA copies the enum into Local Types when it opens a driver, and adds the driver's own methods to it under the same scheme: from each class's description, and from the symbols of the code that sends and answers them. IDA then:

* comments each class's description with its remote methods, the queue each runs on and its message ID, and marks those whose calls go to the kernel; the description's own list of IDs reads as the enum;
* reads message IDs in code as the enum, so a `_Dispatch` compares the incoming ID against `IOService_Start_ID` rather than a 64-bit number. An operand you have already formatted, as hexadecimal for instance, keeps your format;
* names the 16-byte message headers in read-only data, the message ID and its flags that a method's proxy sends and its `_Invoke` answers with: `<Class>_<Method>_Request` and `<Class>_<Method>_Reply`. Any other read-only slot holding a known message ID is named `<Class>_<Method>_ID`;
* gives the static `X::_Dispatch` and the virtual `X::Dispatch` their real prototypes, unless you have already typed them, so their arguments are not shifted by a guessed `this`. Like the vtables, they are found through their symbols.

```
mov     rcx, IODispatchSource_CheckForWork_ID
cmp     rax, rcx
jz      short loc_124EB
mov     rcx, IODispatchSource_Cancel_ID
cmp     rax, rcx
jz      short loc_12500
...
movaps  xmm0, cs:IOBufferMemoryDescriptor_SetLength_Request
movups  xmmword ptr [rbx+28h], xmm0
```

<figure><img src="https://492083732-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FFP6ibt8r6WPBHTqekG9u%2Fuploads%2Fgit-blob-4955412cf1d4db295032f3f76efeaeb1bd55b3eb%2Fdk_dispatch.png?alt=media" alt="IOUserClient::_Dispatch, decompiled"><figcaption><p><code>IOUserClient::_Dispatch</code> with its real prototype: <code>self</code> and <code>rpc</code>, no hidden <code>this</code></p></figcaption></figure>

In a cache, the methods of every image loaded in the session join the enum, since images call each other's methods.

### Stripped code

Each method has a proxy, which sends the request and checks the ID of the reply, and an `_Invoke` function, which answers with the reply. Both load the method's message header, so IDA recognises them from their code even without symbols:

| the unnamed function                                                           | is named                  |
| ------------------------------------------------------------------------------ | ------------------------- |
| loads one method's request header and checks the reply's ID                    | `<Class>_<Method>`        |
| loads one method's reply header and checks no ID                               | `<Class>_<Method>_Invoke` |
| handles several methods, as a `_Dispatch` does, or only sends a oneway message | not named                 |

These are automatic names: a symbol or a name you give always wins, and IDA takes its own name back when the code no longer supports it, for instance after you redefine the function's instructions. In a stripped driver, a method whose message ID neither a type library nor a class description gives stays unnamed.

The kernel side of DriverKit uses the same message IDs. In an arm64 [Apple Kernelcache](/ida-9.5/core/disassembler/concepts/kc-workflow.md), the kernel's own DriverKit classes, such as `IOService` or the `AFK` families, get the same names: message headers as `_Request` and `_Reply`, IDs in code as `IORPCMessageID`, and their proxies without symbols as above, such as `IOService_Stop` or `IOUserServer_Exit`.

## See also

* [Apple Kernelcache](/ida-9.5/core/disassembler/concepts/kc-workflow.md) — the same object model in the kernel, recovered from KEXTs' constructor calls.
* [Dyld Shared Cache](/ida-9.5/core/disassembler/concepts/dsc-workflow.md) — the workflow the DriverKit shared cache opens in.
* [Extracting cache images](/ida-9.5/core/disassembler/concepts/extracting-cache-images.md) — writing a framework out of the DriverKit shared cache as a standalone Mach-O.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.hex-rays.com/ida-9.5/core/disassembler/concepts/driverkit.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
