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

Process injection: breaking all macOS security layers with a single vulnerability

12 August 2022 · 25 min read · DEFION Research Labs

Related CVEs

CVE-2021-30873 CVE-2021-30659

Article content

In October 2021, Apple fixed CVE-2021-30873 — a process injection vulnerability affecting essentially all macOS AppKit-based applications. We reported this vulnerability to Apple, along with methods to escape the sandbox, elevate privileges to root and bypass the filesystem restrictions of SIP.

Update: A follow up for this article is available at "Bringing process injection into view(s): exploiting all macOS apps using nib files".

The saved state vulnerability

AppKit saves application state to files each time an application loses focus. A windows.plist file contains a list of all of the application's open windows. The data.data file contains AES-CBC encrypted serialized objects.

Example windows.plist for TextEdit.app:

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<array>
    <dict>
        <key>NSDataKey</key>
        <data>Ay1IqBriwup4bKAanpWcEw==</data>
        <key>NSWindowID</key>
        <integer>1</integer>
    </dict>
    <dict>
        <key>NSDataKey</key>
        <data>5lyzOSsKF24yEcwAKTBSVw==</data>
        <key>NSTitle</key>
        <string>Untitled</string>
        <key>NSWindowID</key>
        <integer>2</integer>
    </dict>
</array>
</plist>

The vulnerability: the encrypted serialized object stored in data.data was not using "secure coding". This means objects of any class implementing NSCoding can be included.

Objective-C serialization

Instead of the unsafe construction:

id obj = [decoder decodeObjectForKey:@"myKey"];
if (![obj isKindOfClass:[MyClass class]]) { /* ...fail... */ }

The following secure version must be used:

id obj = [decoder decodeObjectOfClass:[MyClass class] forKey:@"myKey"];

Creating a malicious serialized object

By decompiling -initWithCoder: methods in AppKit, we found a combination of 2 objects to call arbitrary Objective-C methods on another deserialized object.

NSRuleEditor: The -initWithCoder: method creates a binding to another object from the archive, allowing zero-argument methods to be called:

ID NSRuleEditor::initWithCoder:(ID param_1,SEL param_2,ID unarchiver)
{
    ...
    id arrayOwner = [unarchiver decodeObjectForKey:@"NSRuleEditorBoundArrayOwner"];
    ...
    if (arrayOwner) {
        keyPath = [unarchiver decodeObjectForKey:@"NSRuleEditorBoundArrayKeyPath"];
        [self bind:@"rows" toObject:arrayOwner withKeyPath:keyPath options:nil];
    }
    ...
}

NSCustomImageRep: When -draw is called, it invokes the configured selector on the configured object:

ID NSCustomImageRep::initWithCoder:(ID param_1,SEL param_2,ID unarchiver)
{
    ...
    id drawObject = [unarchiver decodeObjectForKey:@"NSDrawObject"];
    self.drawObject = drawObject;
    id drawMethod = [unarchiver decodeObjectForKey:@"NSDrawMethod"];
    SEL selector = NSSelectorFromString(drawMethod);
    self.drawMethod = selector;
    ...
}

void ___24-[NSCustomImageRep_draw]_block_invoke(long param_1)
{
    ...
    [self.drawObject performSelector:self.drawMethod withObject:self];
    ...
}

Exploitation

Sandbox escape

The com.apple.appkit.xpc.openAndSavePanelService reads its saved state using the bundle ID of the launching app. If that app was open when the user shut down, this path is a symlink to the container. We can escape the sandbox by:

  • Writing malicious data.data and windows.plist inside the app's own container
  • Opening an NSOpenPanel or NSSavePanel

Fixed as CVE-2021-30659 in macOS 11.3.

Privilege escalation

By injecting into an application with the com.apple.private.AuthorizationServices entitlement containing system.install.apple-software — we can install macOSPublicBetaAccessUtility.pkg to a RAM disk containing a malicious script, gaining root code execution.

SIP filesystem bypass

"macOS Update Assistant.app" has the com.apple.rootless.install.heritable entitlement. By injecting into it we can write to all SIP protected locations, modify the TCC database, and persist malware to SIP-protected locations.

The fix

Applications can now opt-in to requiring secure coding for their saved state by returning TRUE from -applicationSupportsSecureRestorableState:. If you write an Objective-C application, please make sure you add this method returning YES and adapt secure coding for all classes used in your saved states.

More from Research Labs