This codebase attempts to load a mach-o from memory and registers it into the dyld codebase so one can inject code without having to recompile the program. It also exposes the primary API, void* dlopen_register_memory(void *mmap_mem) This can be useful for debugging with lldb and symbol referencing, dlclose, and dlsym functions.
This is a deviates from https://blog.xpnsec.com/restoring-dyld-memory-loading/ which many of the ideas appear to be no longer usable and primarily for the macOS.
This has been tested on:
- iOS 16.7
- iOS 17.something
- iOS 18.1
- iOS 26b1
Make sure your computer and your iOS device is on the same network. Navigate to the Settings on the iOS device, find the network and your devices IP address.
Compile a basic test iOS file like so:
#include <Foundation/Foundation.h>
@interface HHIUHIHIHI: NSObject
@end
@implementation HHIUHIHIHI
+ (void)load {
NSLog(@"%s:%d: objc +load command called", __PRETTY_FUNCTION__, __LINE__);
}
@end
__attribute__((constructor)) static void oninit(void) {
NSLog(@"%s:%d: constructor called", __PRETTY_FUNCTION__, __LINE__);
}
__attribute__((destructor)) static void ondeinit(void) {
NSLog(@"%s:%d: destructor called", __PRETTY_FUNCTION__, __LINE__);
}Compile:
> xcrun -sdk iphoneos clang -fobjc-arc -fmodules /tmp/yo.m -shared -o /tmp/yo.dylib -g -mios-version-min=15.0Optionally codesign (see below):
>codesigning -f -s ${YOUR_ID_HERE} /tmp/yo.dylibTurn on device log monitoring for the dlopenPOC process:
> idevicesyslog -p dlopenPOCUpload the dylib to the device, while keeping an eye on the logs:
nc 192.168.86.222 3501 < /tmp/yo.dylibJun 18 23:37:22 dlopenPOC[3195] <Notice>: Connected!
Jun 18 23:37:22 dlopenPOC[3195] <Notice>: Got a connection.
Jun 18 23:37:22 dlopenPOC[3195] <Notice>: Sending reply...
Jun 18 23:37:22 dlopenPOC[3195] <Notice>: Receiving command...
Jun 18 23:37:22 dlopenPOC[3195] <Notice>: bulk image update start! ***********************
Jun 18 23:37:22 dlopenPOC[3195] <Notice>: 1063a0000 [MEMORY]/0x1092d0000//tmp/yo.dylib
Jun 18 23:37:22 dlopenPOC[3195] <Notice>: bulk image update end ***************************
Jun 18 23:37:22 dlopenPOC(yo.dylib)[3195] <Notice>: +[HHIUHIHIHI load]:7: objc +load command called
Jun 18 23:37:22 dlopenPOC(yo.dylib)[3195] <Notice>: void oninit(void):12: constructor called
Jun 18 23:37:22 dlopenPOC[3195] <Notice>: dlopen success! handle: 0x8223a890Example done on iOS 26b1 ^
In addition, the UI will reflect all loaded dylibs that dyld recognizes as loaded into the process
Although it works for all these versions, there are limitations especially for iOS 26b1 (and maybe the future). Even when a process has get-task-allow and is being debugged, it is not allowed to patch it's own memory nor is it allowed to create it's own RX memory. As a result, a breakpoint is created and the debugserver must actually patch the memory. The debugging command to write to RX memory is the following for this project:
br set -n lldb_callout -G1 -C "script e = lldb.SBError(); content = lldb.process.ReadMemory(`$arg2`, `$arg3`, e); lldb.process.WriteMemory(`$arg1`, content, e) ; e"
In the iOS Xcode project, a shared symbolic breakpoint is created so you don't have to do anything. If however, you delete all GUI Xcode breakpoints, the functionality will be lost and you'll have to recreate the symbolic breakpoint, or execucte the above command before memory patching occurs
For all other iOS versions, if you are running the iOS application, dlopenPOC, under the debugger great, you don't need to worry about anything. However, if you are running it without a debugger, you'll need to worry about codesigning. Code validation doesn't happen when your process is being debugged. If you attempt to load a mach-o that isn't codesigned with the same signature that created the dlopenPOC executable without being debugged, the code will refuse to load and crash. Here's an example of looking at the build product and figuring out how to properly sign an executable
After building dlopenPOC in Xcode, select Products, Show Build Folder in Finder, then in Terminal...
> cd cd /Users/feet/Library/Developer/Xcode/DerivedData/dyldplus-frdkvakfgvrcdjajztsfrvspwobt/Build/Products/Debug-iphoneos/dlopenPOC.app
> codesign -d --verbose=10 dlopenPOC 2>&1 | grep Authority
Authority=Apple Development: im Apple (9AABCDCA9F)
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
# you can find all your valid identities with this command
> security find-identity
Policy: X.509 Basic
Matching identities
1) 0000000000000000000000000000000000000000 "Apple Development: im Apple (9AABCDCA9F)"
1 identities found
Valid identities only
1) 0000000000000000000000000000000000000000 "Apple Development: im Apple(9AABCDCA9F)"
1 valid identities found
# now codesign a newly built shared library with the same signature
codesigning -f -s "Tim Apple (9AABCDCA9F)" /tmp/some_executable.dylib
