Heads up... You're reading this book for free, with parts of this chapter shown beyond this point astext.
If you’ve developed any type of Apple GUI software, you’ve definitely used dynamic frameworks in your day-to-day development.
A dynamic framework is a bundle of code loaded into an executable at runtime, instead of at compile time. Examples in iOS include
UIKit and the
Foundation frameworks. Frameworks such as these contain a dynamic library and optionally assets, such as images.
There are numerous advantages in electing to use dynamic frameworks instead of static frameworks. The most obvious advantage is you can make updates to the framework without having to recompile the executable that depends on the framework.
Imagine if, for every major or minor release of iOS, Apple said, “Hey y’all, we need to update
UIKit so if you could go ahead and update your app as well, that would be grrrreat.” There would be blood in the streets and the only competition would be Android vs. Windows Phone!
Why dynamic frameworks?
In addition to the positives of using dynamic frameworks, the kernel can map the dynamic framework to multiple processes that depend on the framework. Take
CFNetwork, for example: it would be stupid and a waste of disk space if each running iOS app kept a unique copy of
CFNetwork resident in memory. Furthermore, there could be different versions of
CFNetwork compiled into each app, making it incredibly difficult to track down bugs.
As of iOS 8, Apple decided to lift the dynamic library restriction and allow third-party dynamic libraries to be included in your app. The most obvious advantage was that developers could share frameworks across different iOS extensions, such as the Today Extension and Action Extensions.
Today, all Apple platforms allow third party dynamic frameworks to be included without rejection in the ever-so-lovely Apple Review process.
With dynamic frameworks comes a very interesting aspect of learning, debugging, and reverse engineering. Since you’ve the ability to load the framework at runtime, you can use LLDB to explore and execute code at runtime, which is great for spelunking in both public and private frameworks.
Statically inspecting an executable’s frameworks
Compiled into each executable is a list of dynamic libraries (most often, frameworks), expected to be loaded at runtime. This can be further broken down into a list of required frameworks and a list of optional frameworks. The loading of these dynamic libraries into memory is done using a special framework called the dynamic loader, or
otool -L /Users/derekselander/Library/Developer/Xcode/DerivedData/DeleteMe-fqycokvgjilklcejwonxhuyxqlej/Build/Products/Debug-iphonesimulator/DeleteMe.app/DeleteMe
/System/Library/Frameworks/CallKit.framework/CallKit (compatibility version 1.0.0, current version 1.0.0) /System/Library/Frameworks/CoreBluetooth.framework/CoreBluetooth (compatibility version 1.0.0, current version 1.0.0) /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1556.0.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.200.5) /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 61000.0.0)
otool -l /Users/derekselander/Library/Developer/Xcode/DerivedData/DeleteMe-fqycokvgjilklcejwonxhuyxqlej/Build/Products/Debug-iphonesimulator/DeleteMe.app/DeleteMe
Load command 12 cmd LC_LOAD_WEAK_DYLIB cmdsize 80 name /System/Library/Frameworks/CallKit.framework/CallKit (offset 24) time stamp 2 Wed Dec 31 17:00:02 1969 current version 1.0.0 compatibility version 1.0.0
Load command 13 cmd LC_LOAD_DYLIB cmdsize 96 name /System/Library/Frameworks/CoreBluetooth.framework/CoreBluetooth (offset 24) time stamp 2 Wed Dec 31 17:00:02 1969 current version 1.0.0 compatibility version 1.0.0
Modifying the load commands
There’s a nice little command that lets you augment and add the framework load commands named
(lldb) image list CallKit
[ 0] 0484D8BA-5CB8-3DD3-8136-D8A96FB7E15B 0x0000000102d10000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/CallKit.framework/CallKit
pgrep -fl DeleteMe
install_name_tool -change "$CK" "$NC" "$app"
otool -L "$app"
/System/Library/Frameworks/NotificationCenter.framework/NotificationCenter (compatibility version 1.0.0, current version 1.0.0) /System/Library/Frameworks/CoreBluetooth.framework/CoreBluetooth (compatibility version 1.0.0, current version 1.0.0) /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1556.0.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.200.5) /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 61000.0.0)
lldb -n DeleteMe
(lldb) image list CallKit
error: no modules found that match 'CallKit'
(lldb) image list NotificationCenter
[ 0] 0FCE1DF5-7BAC-3195-94CB-C6100116FF99 0x000000010b8c7000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/NotificationCenter.framework/NotificationCenter
Loading frameworks at runtime
Before you get into the fun of learning how to load and explore commands at runtime, let me give you a command to help explore directories using LLDB. Start by adding the following to your
command regex ls 's/(.+)/po @import Foundation; [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"%1" error:nil]/'
(lldb) command source ~/.lldbinit
(lldb) image list -d UIKit
[ 0] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/Library/Frameworks/UIKit.framework
(lldb) ls /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/Library/Frameworks/
(lldb) process load /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/Library/Frameworks/Speech.framework/Speech
(lldb) process load MessageUI.framework/MessageUI
Loading "MessageUI.framework/MessageUI"...ok Image 1 loaded.
One of the foundations of reverse engineering is exploring dynamic frameworks. Since a dynamic framework requires the code to compile the binary into a position independent executable, you can still query a significant amount of information in the dynamic framework — even when the compiler strips the framework of debugging symbols. The binary needs to use position-independent code because the compiler doesn’t know exactly where the code will reside in memory once
dyld has done its business.
command regex dump_stuff "s/(.+)/image lookup -rn '\+\[\w+(\(\w+\))?\ \w+\]$' %1 /"
(lldb) command source ~/.lldbinit (lldb) dump_stuff CoreBluetooth
command regex ivars 's/(.+)/expression -lobjc -O -- [%1 _ivarDescription]/'
command regex methods 's/(.+)/expression -lobjc -O -- [%1 _shortMethodDescription]/'
command regex lmethods 's/(.+)/expression -lobjc -O -- [%1 _methodDescription]/'
(lldb) process load PhotosUI.framework/PhotosUI
(lldb) dump_stuff PhotosUI
(lldb) ivars [PUScrubberSettings sharedInstance] <PUScrubberSettings: 0x7ffb12818fb0>: in PUScrubberSettings: _usePreviewScrubberMargins (BOOL): NO _useTrianglePositionIndicator (BOOL): NO _useSmoothingAnimation (BOOL): NO _dynamicSeekTolerance (BOOL): YES _previewInteractiveLoupeBehavior (unsigned long): 2 _interactiveLoupeBehavior (unsigned long): 0 _tapAnimationDuration (double): 0.5 ...
(lldb) methods PUScrubberSettings <PUScrubberSettings: 0x11f80fc48>: in PUScrubberSettings: Class Methods: + (id) sharedInstance; (0x11f57092b) + (id) settingsControllerModule; (0x11f570be8) Properties: @property (nonatomic) unsigned long previewInteractiveLoupeBehavior; (@synthesize previewInteractiveLoupeBehavior = _previewInteractiveLoupeBehavior;) @property (nonatomic) BOOL usePreviewScrubberMargins; (@synthesize usePreviewScrubberMargins = _usePreviewScrubberMargins;)
(lldb) lmethods PUScrubberSettings
Loading frameworks on an actual iOS device
If you have a valid iOS developer account, an application you’ve written, and a device, you can do the same thing you did on the simulator but on the device. The only difference is the location of the System/Library path. If you’re running an app on the simulator, the public frameworks directory will be located at the following location:
(lldb) ls /
(lldb) ls /System/Library/
Where to go from here?
That /System/Library directory is really something. You can spend a lot of time exploring the different contents in that subdirectory. If you have an iOS device, go explore it!