Exploring macOS private frameworks
TL;DR: This article describes a series of approaches for reverse engineering macOS private frameworks
Apple develops a growing amount of frameworks. As an
application developer, you are probably familiar with public
frameworks such as AppKit
and Core
Data. These frameworks are well documented, and you will
find lots of tutorials and examples at developer.apple.com
or in the Xcode developer documentation (at
Help -> Developer Documentation
):
Apart from public frameworks, macOS ships with over 1000 private frameworks that are used by system services or as dependencies of public frameworks. Due to their private nature, these frameworks are not documented at all.
This article presents a series of non-exclusive approaches for digging into private frameworks, using the Disk Utility built-in application as an example. Whether you are a security researcher or want a deeper understanding of how macOS software works, being able to peek into these private frameworks is a great tool to keep in your tool belt.
The credit from this post goes to Wojciech Reguła, from whom I learnt all of these techniques (and more!)
Approach 1: otool
Xcode comes with a command-line program called
otool(1)
. This is a general-purpose tool for
interacting with Mach-O Apple
binary files. One of its convenient features is to list the
shared libraries a Mach-O executable links to (using the
-L
option). With it, we can identify the private
frameworks that Apple applications or services link to.
The Disk Utility built-in application we will be
looking into in this article lives in
/System/Applications/Utilities/Disk Utility.app
.
Following the structure of macOS
application bundles and its Info.plist
, we can
determine its main executable is
Contents/MacOS/Disk Utility
.
Every private macOS framework resides in
/System/Library/PrivateFrameworks
, so we can filter
otool(1)
results by PrivateFrameworks
using grep(1)
:
$ otool -L /System/Applications/Utilities/Disk\ Utility.app/Contents/MacOS/Disk\ Utility | grep PrivateFrameworks
/System/Library/PrivateFrameworks/Restore.framework/Versions/A/Restore (compatibility version 1.0.0, current version 615.0.0)
/System/Library/PrivateFrameworks/DiskManagement.framework/Versions/A/DiskManagement (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/StorageKit.framework/Versions/A/StorageKit (compatibility version 1.0.0, current version 53.0.0)
/System/Library/PrivateFrameworks/DiskImages.framework/Versions/A/DiskImages (compatibility version 1.0.8, current version 649.0.0)
/System/Library/PrivateFrameworks/IASUtilities.framework/Versions/A/IASUtilities (compatibility version 1.0.0, current version 119.0.0)
/System/Library/PrivateFrameworks/LocalAuthenticationRecoveryUI.framework/Versions/A/LocalAuthenticationRecoveryUI (compatibility version 1.0.0, current version 1394.40.33)
/System/Library/PrivateFrameworks/MobileObliteration.framework/Versions/A/MobileObliteration (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/LoginUIKit.framework/Versions/A/LoginUIKit (compatibility version 1.0.0, current version 357.1.0)
/System/Library/PrivateFrameworks/FindMyDeviceUI.framework/Versions/A/FindMyDeviceUI (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/apfs_boot_mount.framework/Versions/A/apfs_boot_mount (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight (compatibility version 64.0.0, current version 600.0.0)
An interesting private framework we can explore further, out
of these 11 results, is
/System/Library/PrivateFrameworks/DiskManagement.framework
.
But how can we learn more about it if it doesn’t come with any
documentation?
Approach 2: RuntimeBrowser
RuntimeBrowser is an open-source macOS application that will list every available public and private framework image and generate Objective-C declarations out of them. RuntimeBrowser browser is not available on Homebrew, so you will need to either build it from source (using Xcode) or download the (unsigned) pre-built version from the official GitHub repository.
In the previous section, we identified
/System/Library/PrivateFrameworks/DiskManagement.framework
as a potentially interesting private framework to explore. Using
RuntimeBrowser, we can locate the DiskManagement
image, list the classes it declares, choose a potentially
interesting one, and explore its Objective-C interface.
In the above example, we are inspecting the
DMPartitionDisk
class, which has interesting
Objective-C methods like
addPartitionFollowingPartition
. If you have SIP
(System Integrity Protection) disabled, you can attach LLDB to
Disk Utility and add breakpoints on these methods to
explore them further.
You can also import the generated interfaces to an Objective-C project that links to the relevant private frameworks and attempt to call these classes yourself. But how can we better understand these methods to know how to we should use them?
Approach 3: Hopper Disassembler
Hopper is a popular disassembler for reverse engineering macOS frameworks. It requires a paid license to enable its full set of features, but the trial is enough for simple cases like the ones in this article.
In the previous sections, we explored the
/System/Library/PrivateFrameworks/DiskManagement.framework
private framework that the Disk Utility application
links to. If you try to open this framework using Hopper, you
will realize that while such framework directory exists on the
file system, it doesn’t contain the actual library. In fact, the
symbolic link at the top-level of the framework bundle is
broken:
$ file /System/Library/PrivateFrameworks/DiskManagement.framework/DiskManagement
/System/Library/PrivateFrameworks/DiskManagement.framework/DiskManagement: broken symbolic link to Versions/Current/DiskManagement
Starting from Big Sur, macOS caches
all built-in libraries into a single file for startup
performance reasons. Instead of shipping each shared library
into their respective locations, macOS now maintains a shared
cache for each architecture it supports in the
/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld
directory. On my system, I have caches for arm64e
and x86_64
:
$ file /System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e
/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e: Dyld shared cache version 1 arm64e
$ file /System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_x86_64
/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_x86_64: Dyld shared cache version 1 x86_64
When an application launches, dyld
(the open-source Apple dynamic linker), will load the required
frameworks from the cached location (referred to as the
dyld
shared cache) without incurring additional
I/O. This is great for performance, but not so great for the
purposes of reverse engineering.
The good news is that Hopper is capable of inspecting Mach-O
binaries directly from within the dyld
shared cache. Let’s use the Hopper CLI to open the
arm64e
shared cache:
$ hopper --executable /System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e
When the Hopper application launches, it will let you browse the cache for the frameworks that you are interested in using its built-in DYLD Shared Cache loader.
Once we select DiskManagement.framework
, we can
use Hopper to find the
addPartitionFollowingPartition
method (using the
Labels section) we saw before in RuntimeBrowser, and
select the Pseudo-code mode to see a disassembled
version of it.
Finally, you can also export an Objective-C header file out
of a framework, similar to how RuntimeBrowser
does
it, by selecting
File -> Export Objective-C Header File...
.
If you want to generate Objective-C headers out of a Mach-O
binary from the command-line, a tool worth looking into is class-dump
.
Approach 4:
dyld-shared-cache-extractor
While Hopper can select frameworks out of the
dyld
shared cache for individual inspection, often
you want to perform searches (or other operations) across the
entire set of private frameworks. However, this is inconvenient
to do on the shared cache monolith file.
Luckily, there is an open-source tool called dyld-shared-cache-extractor
,
which as its name implies, can extract the dyld
shared cache into individual files. You can install this program
using the author’s Homebrew
Tap or build it from source (its a single file of C
code).
Once you have it, you can run it by passing as arguments the
dyld
shared cache you want to extract and the
output location. For example, I can extract the
arm64e
shared cache into
$HOME/Projects/dyld-cache-arm64e
as follows:
$ dyld-shared-cache-extractor \
\
/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e $HOME/Projects/dyld-cache-arm64e
After a few seconds, your output directory will be populated
with every entry of the shared cache, including
DiskManagement.framework
:
$ file ~/Projects/dyld-cache-arm64e/System/Library/PrivateFrameworks/DiskManagement.framework/Versions/A/DiskManagement
/Users/jviotti/Projects/dyld-cache-arm64e/System/Library/PrivateFrameworks/DiskManagement.framework/Versions/A/DiskManagement: Mach-O 64-bit dynamically linked shared library arm64e
On my system, the arm64e
extracted shared cache
is 3.5 GB and contains 1535 private frameworks:
$ du -sh ~/Projects/dyld-cache-arm64e
3.5G /Users/jviotti/Projects/dyld-cache-arm64e
$ ls -1 ~/Projects/dyld-cache-arm64e/System/Library/PrivateFrameworks | wc -l
1534
Now you can more conveniently open the framework you want to
explore in Hopper, but you can also perform searches across the
entire cache using grep(1)
. For example, we can
find every private framework that mentions the FileVault
disk encryption service as follows:
$ grep --recursive FileVault ~/Projects/dyld-cache-arm64e/System/Library/PrivateFrameworks
Binary file System/Library/PrivateFrameworks/ConfigurationEngineModel.framework/Versions/A/ConfigurationEngineModel matches
Binary file System/Library/PrivateFrameworks/DiskManagement.framework/Versions/A/DiskManagement matches
Binary file System/Library/PrivateFrameworks/Install.framework/Versions/A/Install matches
Binary file System/Library/PrivateFrameworks/Install.framework/Frameworks/DistributionKit.framework/Versions/A/DistributionKit matches
Binary file System/Library/PrivateFrameworks/ConfigurationProfiles.framework/Versions/A/ConfigurationProfiles matches
Binary file System/Library/PrivateFrameworks/CoreUtilsExtras.framework/Versions/A/CoreUtilsExtras matches
Binary file System/Library/PrivateFrameworks/DistributedEvaluation.framework/Versions/A/DistributedEvaluation matches
Binary file System/Library/PrivateFrameworks/LoginUIKit.framework/Versions/A/LoginUIKit matches
Binary file System/Library/PrivateFrameworks/StoreFoundation.framework/Versions/A/StoreFoundation matches
Binary file System/Library/PrivateFrameworks/SpotlightServices.framework/Versions/A/SpotlightServices matches
Binary file System/Library/PrivateFrameworks/SystemMigration.framework/Versions/A/SystemMigration matches
Binary file System/Library/PrivateFrameworks/CoreServicesInternal.framework/Versions/A/CoreServicesInternal matches
Binary file System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS matches
Binary file System/Library/PrivateFrameworks/SetupAssistantSupport.framework/Versions/A/SetupAssistantSupport matches
Binary file System/Library/PrivateFrameworks/QuickLookIosmac.framework/Versions/A/QuickLookIosmac matches
Binary file System/Library/PrivateFrameworks/SystemAdministration.framework/Versions/A/SystemAdministration matches
Binary file System/Library/PrivateFrameworks/RemoteManagementModel.framework/Versions/A/RemoteManagementModel matches
Binary file System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit matches
Binary file System/Library/PrivateFrameworks/OSUpdate.framework/Versions/A/OSUpdate matches
Binary file System/Library/PrivateFrameworks/QuickLookThumbnailingDaemon.framework/Versions/A/QuickLookThumbnailingDaemon matches
Binary file System/Library/PrivateFrameworks/DeviceManagementTools.framework/Versions/A/DeviceManagementTools matches
As you would expect, DiskManagement.framework
appears within the occurrences (its the second result).
Approach 5:
dylibtree
dylibtree
is an open-source third-party utility (from the same author as
dyld-shared-cache-extractor
) to get a tree-view of
the frameworks used by the application. Similar to
dyld-shared-cache-extractor
, you can install this
program using the author’s Homebrew
Tap or build it from source.
This tool relies on the dyld
shared cache file
we saw on previous sections, and it is convenient if want to
better understand the dependency chain of applications and their
frameworks without having to scan one by one using
otool(1)
.
For example, you can list the dependencies of
DiskManagement.framework
up to 3 levels deep using
the following command:
$ dylibtree --depth 3 \
--shared-cache-path /System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e \
~/Projects/dyld-cache-arm64e/System/Library/PrivateFrameworks/DiskManagement.framework/Versions/A/DiskManagement/Users/jviotti/Projects/dyld-cache-arm64e/System/Library/PrivateFrameworks/DiskManagement.framework/Versions/A/DiskManagement:
/System/Library/PrivateFrameworks/DiskManagement.framework/Versions/A/DiskManagement:
/usr/lib/libcsfde.dylib:
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation:
/System/Library/PrivateFrameworks/ProtectedCloudStorage.framework/Versions/A/ProtectedCloudStorage:
/usr/lib/libCoreStorage.dylib:
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit:
/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices:
/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration:
/System/Library/Frameworks/Security.framework/Versions/A/Security:
/System/Library/PrivateFrameworks/EFILogin.framework/Versions/A/EFILogin:
/usr/lib/libc++.1.dylib:
/usr/lib/libSystem.B.dylib:
/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork:
/usr/lib/libCoreStorage.dylib
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit
/System/Library/Frameworks/Security.framework/Versions/A/Security
/System/Library/PrivateFrameworks/MediaKit.framework/Versions/A/MediaKit:
/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS:
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
/usr/lib/libz.1.dylib:
/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration:
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit
/System/Library/Frameworks/Security.framework/Versions/A/Security
/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/DiscRecording.framework/Versions/A/DiscRecording:
/usr/lib/libz.1.dylib
/usr/lib/libobjc.A.dylib:
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices
/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation:
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit
/System/Library/Frameworks/Security.framework/Versions/A/Security
/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox:
/usr/lib/libc++.1.dylib
/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork
/System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics:
/System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce:
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
/usr/lib/libobjc.A.dylib
/usr/lib/libc++.1.dylib
/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
/usr/lib/libobjc.A.dylib
/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
/usr/lib/libcsfde.dylib
/usr/lib/libCoreStorage.dylib
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit
/System/Library/Frameworks/Security.framework/Versions/A/Security
/System/Library/PrivateFrameworks/MediaKit.framework/Versions/A/MediaKit
/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration
/System/Library/Frameworks/DiscRecording.framework/Versions/A/DiscRecording
/System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics
/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
/usr/lib/libobjc.A.dylib
/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
As an engineer working on macOS, you hopefully find these approaches useful!