Skip to content

CoreDevice Injection Research

Date: 2026-04-06 Status: iPhone VISIBLE IN XCODE via CoreDeviceService injection

Summary

We can inject arbitrary ObjC/C code into CoreDeviceService via LC_LOAD_DYLIB after re-signing the binary to remove platform binary status. The injected code runs in-process and can see all CoreDevice Swift classes. Next step: call Swift functions via mangled symbols to register a device.

Prerequisites (on SIP-disabled VM)

  1. Re-sign CoreDeviceService to remove platform binary flag:

    # Backup original
    cp .../CoreDeviceService .../CoreDeviceService.bak
    # Ad-hoc re-sign (removes platform bit)
    codesign -s - -f --deep .../CoreDeviceService.xpc
    

  2. Build injection dylib (ObjC, ad-hoc signed)

  3. Insert LC_LOAD_DYLIB into CoreDeviceService binary:

    # Build insert_dylib tool
    git clone https://github.com/tyilo/insert_dylib.git
    clang -o insert_dylib_bin insert_dylib/main.c -framework Foundation
    # Insert
    insert_dylib_bin --all-yes /path/to/inject.dylib .../CoreDeviceService
    mv CoreDeviceService_patched CoreDeviceService
    # Re-sign after modification
    codesign -s - -f --deep .../CoreDeviceService.xpc
    

What Works

Dylib loads in CoreDeviceService

[com.iosmux.inject:dev] INJECT OK proc=CoreDeviceService

All CoreDevice classes visible from injected code

CoreDevice.ServiceDeviceRepresentation     (0 ObjC methods - pure Swift)
CoreDevice.ServiceDeviceManager            (0 ObjC methods - pure Swift)
CoreDevice.DeviceManager                   (0 ObjC methods - pure Swift)
CoreDeviceService.RemotePairingDeviceRepresentationBrowser  (0 ObjC methods)
CoreDeviceService.RemotePairingDeviceRepresentation         (0 ObjC methods)
CoreDeviceService.RestorableDeviceRefDeviceRepresentationBrowser (0 ObjC methods)
CoreDeviceService.RestorableDeviceRefDeviceRepresentation       (0 ObjC methods)
CoreDeviceService.StaticDeviceRepresentationBrowser             (0 ObjC methods)

16731 total classes loaded in CoreDeviceService process

What Doesn't Work

Plugin approach (protocol witness table mismatch)

Synthetic .swiftinterface files for CoreDevice/CoreDeviceProtocols compiled but crashed at runtime: protocol witness tables from fake interfaces don't match real CoreDevice binary's layout. Error: GPF in PluginProtocol.init() witness.

DYLD_INSERT_LIBRARIES for XPC services

launchctl setenv DYLD_INSERT_LIBRARIES loads into ALL processes, not targeted. XPC services don't inherit per-process env vars. Need LC_LOAD_DYLIB injection.

ObjC runtime manipulation of Swift classes

All key CoreDevice classes are pure Swift (0 ObjC methods visible to class_copyMethodList). Cannot use method_exchangeImplementations or objc_msgSend to interact with them.

Swift ABI Calling Convention (x86_64)

Critical finding from LLDB disassembly: Swift uses RAX for indirect struct return, NOT RDI like the C sret convention.

DeviceInfo.init(deviceIdentifier:):
  RAX = sret output buffer (952 bytes, allocated by caller)
  RDI = pointer to DeviceIdentifier enum value (33 bytes)

DeviceInfo.init(identifier: UUID):
  RAX = inner sret buffer (passed through to init(deviceIdentifier:))
  RDI = outer caller's sret buffer
  (This is a wrapper — constructs DeviceIdentifier then calls init(deviceIdentifier:))

Type Sizes (confirmed at runtime)

Type Size Stride Kind
DeviceInfo 952 952 struct (V)
DeviceIdentifier 33 40 enum (O)
ServiceDeviceRepresentation ptr ptr class (C)

Key Symbols (found via dlsym at runtime)

Important: Use WITHOUT leading underscore for dlsym.

$s19CoreDeviceProtocols0B4InfoV10identifierAC10Foundation4UUIDV_tcfC
  → DeviceInfo.init(identifier: UUID) [wrapper]

$s19CoreDeviceProtocols0B4InfoV16deviceIdentifierAC0aB00bF0O_tcfC
  → DeviceInfo.init(deviceIdentifier: DeviceIdentifier) [real init]

$s10CoreDevice07ServiceB14RepresentationC...tcfC
  → ServiceDeviceRepresentation.__allocating_init(...)

$s19CoreDeviceProtocols0B4InfoVMa → DeviceInfo type metadata accessor
$s10CoreDevice0B10IdentifierOMa  → DeviceIdentifier type metadata accessor
$s10CoreDevice10CapabilityVMa    → Capability type metadata accessor

DeviceInfo Creation: CONFIRMED WORKING

Successfully called DeviceInfo.init(identifier: UUID) from injected code:

=== v8 RAX=sret RDI=uuid_ptr ===
DeviceInfo size=952
init found
Calling init (RAX=sret, RDI=uuid_ptr)...
init returned: 0x00007ff1474040a1
out[0:64]: e8 a1 90 dd 64 f5 44 a4 8d 57 28 e9 9e 31 6d 60 ...
UUID found at offset 0!
=== v8 done ===

The UUID E8A190DD-64F5-44A4-8D57-28E99E316D60 appears at offset 0 in the 952-byte struct. Other fields are initialized to defaults by Swift.

Working C Wrapper for Swift sret (naked function)

// Swift x86_64: sret in RAX, first arg in RDI
__attribute__((naked))
void *iosmux_call_init_rax_sret(void *fn_ptr, void *sret_buf, void *uuid_ptr) {
    __asm__(
        "pushq  %rbp\n\t"
        "movq   %rsp, %rbp\n\t"
        "movq   %rdi, %r10\n\t"     // r10 = fn_ptr
        "movq   %rsi, %rax\n\t"     // rax = sret_buf
        "movq   %rdx, %rdi\n\t"     // rdi = uuid_ptr
        "callq  *%r10\n\t"          // call fn(rax=sret, rdi=uuid)
        "popq   %rbp\n\t"
        "retq\n\t"
    );
}

ServiceDeviceRepresentation Creation: CONFIRMED WORKING

Three bugs fixed: 1. Empty dict = _swiftEmptyDictionarySingleton (not null) 2. Stack 16-byte aligned (extra pushq %r12 in asm wrapper) 3. (Earlier) sret in RAX not RDI

SDR init calling convention (confirmed by LLDB + ABI docs):

RDI = DeviceIdentifier* (indirect, 33 bytes)
RSI = DeviceInfo* (indirect, 952 bytes)  
RDX = Dictionary value (direct, _swiftEmptyDictionarySingleton for empty)
ECX = Bool (direct, 0=false)
R13 = type metatype (out-of-band, NOT in standard arg registers)
Returns: RAX = SDR object pointer (class reference)

Heap Scan: ServiceDeviceManager and RPDRB Found

Used malloc zone enumeration to find instances by isa pointer matching: - ServiceDeviceManager instance found ✅ - RemotePairingDeviceRepresentationBrowser instance found ✅ - ClientManager reference at SDM+32 ✅ - ClientManager.publish(event:) symbol found ✅

SDM._state dict is empty (_swiftEmptyDictionarySingleton at ManagedBuffer+24).

Device Registration: CONFIRMED WORKING

Called handleDiscoveredSDR (offset 0x27e850 in CoreDevice.framework) with: - RDI = our SDR object - R13 = SDM instance (found via heap scan)

Address calculated as: install(browser:) address + 0x1080

Result: iPhone appeared in devicectl list devices!

Name   Hostname                                                Identifier                             State
----   -----------------------------------------------------   ------------------------------------   -----------
       E8A190DD-64F5-44A4-8D57-28E99E316D60.coredevice.local   E8A190DD-64F5-44A4-8D57-28E99E316D60   unavailable

The function launches an async Swift task that updates SDM._state and publishes the device to all connected clients. State is "unavailable" because DeviceInfo has no properties set yet (name, model, tunnel address, etc.)

Full Working Pipeline

1. Inject dylib into CoreDeviceService (LC_LOAD_DYLIB + re-sign)
2. ObjC loader → dlopen Swift helper → dispatch_after 2s
3. Create DeviceInfo via Swift ABI (sret in RAX)
4. Create DeviceIdentifier (33-byte enum with proper String)
5. Create SDR via __allocating_init (empty dict singleton + stack alignment)
6. Find SDM via heap scan (malloc zone enumeration, isa pointer match)
7. Calculate handleDiscoveredSDR = install(browser:) + 0x1080
8. Call handleDiscoveredSDR(rdi=SDR, r13=SDM)
9. Device appears in devicectl list devices!

Attempted Approaches Log

# Approach Result
1 Synthetic .swiftinterface + Swift plugin Crashes: witness table mismatch
2 DYLD_INSERT_LIBRARIES (launchctl setenv) Loads in ALL processes, not targeted
3 XPC plist EnvironmentVariables Ignored for XPC services
4 LC_LOAD_DYLIB via insert_dylib WORKS after re-signing CoreDeviceService
5 ObjC runtime class scanning Classes visible but 0 ObjC methods (pure Swift)

Files on Mac VM

  • /Library/Developer/CoreDevice/iosmux_inject.dylib — current injection dylib
  • /Library/Developer/PrivateFrameworks/CoreDevice.framework/.../CoreDeviceService.bak — original backup
  • /tmp/insert_dylib_bin — insert_dylib tool
  • /tmp/iosmux_inject*.m — dylib source iterations