Process injection: breaking all macOS security layers with a single vulnerability
Related CVEs
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.dataandwindows.plistinside the app's own container - Opening an
NSOpenPanelorNSSavePanel
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.