Skip to main content
Back to Research Labs
Critical iOS/macOS

Bringing process injection into view(s): exploiting all macOS apps using nib files

5 April 2024 · 20 min read · DEFION Research Labs

Related CVEs

CVE-2021-30873 CVE-2023-40450

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-software entitlement and macOSPublicBetaAccessUtility.pkg
  • Bypassing SIP's filesystem restrictions via the com.apple.rootless.install.heritable entitlement

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 -draw of the next object
  • NSCustomImageRep — 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

More from Research Labs