Bringing process injection into view(s): exploiting all macOS apps using nib files
Related CVEs
Article content
In a previous blog post we described a process injection vulnerability affecting all AppKit-based macOS applications. This research was presented at Black Hat USA 2022, DEF CON 30 and Objective by the Sea v5. This vulnerability was actually the second universal process injection vulnerability we reported to Apple, but it was fixed earlier than the first. Now that the first vulnerability has been fixed in macOS 13.0 (Ventura) and improved in macOS 14.0 (Sonoma), we can detail the first one and thereby fill in the blanks of the previous post.
This vulnerability was independently found by Adam Chester and written up under the name "DirtyNIB". While the exploit chain demonstrated by Adam shares a lot of similarity to ours, our attacks trigger automatically and do not require a user to click a button, making them a lot more stealthy.
Process injection by replacing resources
Process injection is the ability of one process to execute code as if it is another process. This grants it the permissions and entitlements of that other process. The way code-signing of application bundles on macOS worked prior to macOS 13.0 (Ventura): when an application is not quarantined, only a shallow code-signing check is performed. This means that for an application that is not recently downloaded by a browser, the non-executable resources in the application bundle are not validated.
Electron applications contain part of their code in JavaScript files, therefore these files are not verified by the shallow code-signing check. As this attack was well known, we spotted the MainMenu.nib file hiding in plain sight in many macOS applications — that file can also be swapped and a shallow code-signing check will still pass.
Nib files background
Nib (short for NeXT Interface Builder) files are mainly used to design the user interface of a native macOS application. The nib-loading code instantiates the objects, configures them, and reestablishes any inter-object connections. How it determines the main nib file is by parsing the Info.plist file and looking up the value for the NSMainNibFile key:
...
<key>NSMainNibFile</key>
<string>MainMenu</string>
... Nib deserialisation exploitation
Step 1: take control
A newly created Xcode project uses the macOS "App" template. The main.m file contains:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
return NSApplicationMain(argc, argv);
} The default template contains an AppDelegate class:
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
return YES;
}
@end Step 2: create objects
Objects of almost all classes can be created by including them in a nib file and instantiating the nib file, even "dangerous" classes like NSTask or NSAppleScript.
Step 3: calling zero argument methods
By creating bindings, for example binding with a keypath of launch to an NSTask object, it will call the method -launch as soon as objects are instantiated. The following XML creates a binding in a xib:
<objects>
<customObject id="N6N-4M-Hac" userLabel="the task" customClass="NSTask">
[...]
</customObject>
<window title="Window" id="QvC-M9-y7g">
[...]
<connections>
<binding destination="N6N-4M-Hac" name="title" keyPath="launch" id="cy5-GO-ArU"/>
</connections>
</window>
</objects> Step 5: string constants
Putting everything together, we have a way to execute arbitrary AppleScript in any application. The exploit involves creating an NSAppleScript object, setting its source from a text field, and calling -executeAndReturnError:. Once this nib file is loaded in any application, it runs our custom AppleScript inside that process.
Step 6: AppleScriptObjC.framework
For one of the three exploit paths, we needed to load AppleScriptObjC.framework and call AppleScript with the Objective-C bridge to eventually call Python. Our AppleScriptObjC code:
use framework "Foundation"
use scripting additions
script Stage2
property parent : class "NSObject"
on initialize()
tell current application to NSLog("Hello world from AppleScript!")
current application's NSBundle's alloc's initWithPath_("/System/Library/Frameworks/Python.framework")'s load
current application's Py_Initialize()
-- This starts Python in interactive mode, which means it executes stdin.
tell current application to NSLog("Py_Main: %d", Py_Main(0, reference))
end initialize
on encodeWithCoder_()
end encodeWithCoder_
on initWithCoder_()
end initWithCoder_
end script Impact
This vulnerability could be applied in different ways on macOS Big Sur:
- Stealing the TCC permissions and entitlements of applications (webcam, microphone, geolocation, sensitive files)
- Privilege escalation to root using the
system.install.apple-softwareentitlement andmacOSPublicBetaAccessUtility.pkg - Bypassing SIP's filesystem restrictions via the
com.apple.rootless.install.heritableentitlement
CVE-2021-30873
NSNib, the class representing a nib file itself, is serializable using NSCoding. We only needed a chain of three objects in the serialized data:
NSRuleEditor— setup to bind to-drawof the next objectNSCustomImageRep— configured with the selector-instantiateWithOwner:topLevelObjects:NSNib— using one of the payloads described on this page
The fixes
This vulnerability was fixed in macOS Ventura by performing a deep code-signing check when an application is opened for the first time. A new TCC permission "App Management" was added. macOS Sonoma fixed one of the bypasses for the App Management permission (CVE-2023-40450).
Timeline
- September 28, 2020 — Reported to Apple product-security
- October 21, 2021 — Product Security emails that fixes are scheduled for Spring 2022
- May 26, 2022 — Planned fix led to performance regressions; different fix scheduled for Fall 2022
- June 6, 2022 — macOS Ventura Beta 1 released with App Management permission
- October 24, 2022 — macOS Ventura released with App Management permission and launch constraints
- September 26, 2023 — macOS Sonoma released, fixing bypass CVE-2023-40450