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)¶
-
Re-sign CoreDeviceService to remove platform binary flag:
-
Build injection dylib (ObjC, ad-hoc signed)
-
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¶
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