Analyzing 'Mac File Opener' Persistence
07/23/2016
One of the benefits of writing security tools is that one gets a deeper understanding of the OS. I've been working on small utility that adds a menu item to the control-/right-click menu for files (in Finder.app, the desktop, etc). I'm aiming to complete this tool in the next few weeks in time for BlackHat's Tool Arsenal.
Objective-See was invited, for the second year in a row, to present at
BlackHat's Tool Arsenal. If you're attending BlackHat or DefCon, stop by and say hi :) I'll be presenting on August 4th, from 10:00am - 11:50am. More info
here!
Finder Syncs
Apples states that way to extend Finder.app is via a 'Finder Sync' extension:
"In OS X, the Finder Sync extension point lets you cleanly and safely modify the Finder's user interface to express file synchronization status and control."
Creating a Finder Sync is fairly trivial. Start by creating a (blank) OS X application in Xcode. Then add a new target, selecting the 'Finder Sync Extension':
This will add the necessary files to the Xcode project:
Building the project will compile the Finder Sync bundle and embed in into the application. Easy peasy :)
Ok, so how to install the Finder Sync so that it's persistently integrated with Finder? This is where things get somewhat interesting!
As described in the blog titled, 'NSExtension & PlugInKit - A brief intro' when an application is launched for the first time "pkd picks it up and detects all sorts of metadata within it, including any plugins or extensions it may have." It then "auto-magically registers" any embedded plugins, such as Finder Syncs! This is logged in the system log:
$ tail -f /var/log/system.log
users-Mac-2 pkd[457]: INSTALLED:com.persist.FinderSync com.persist.FinderSync(1.0.0)
<__NSConcreteUUID 0x7fbeaa4425f0> 047C12F2-8E06-4A32-B785-31DB71DB1004 /Users/patrickw/code/FinderSync/persist.appex
Note that pkd ('com.apple.pluginkit.pkd', /System/Library/LaunchAgents/com.apple.pluginkit.pkd.plist) is a launch agent, which is started by launchctl:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN">
<plist version="1.0">
<dict>
<key>EnableTransactions</key>
<false/>
<key>Label</key>
<string>com.apple.pluginkit.pkd</string>
<key>MachServices</key>
<dict>
<key>com.apple.pluginkit.pkd</key>
<true/>
</dict>
<key>Program</key>
<string>/usr/libexec/pkd</string>
<key>POSIXSpawnType</key>
<string>Adaptive</string>
<key>LimitLoadToSessionType</key>
<array>
<string>Aqua</string>
<string>Background</string>
<string>LoginWindow</string>
</array>
<key>EnablePressuredExit</key>
<true/>
</dict>
</plist>
Once registered, a Finder Sync will show up in the 'Extensions' pane of System Preferences:
Note however, that the 'Finder' check box may remain unchecked. It seems that while Finder Syncs are automatically registered (the first time their hosting application is run), they may have to be manually activated by selecting the 'Finder' box.
Finder Syncs can also be manually installed and activated via the pluginkit utility:
$ pluginkit -a /some/path/persist.appex
$ pluginkit -e use -i <finder sync's bundle id>
The first command ('pluginkit -a') adds the Finder Sync the system's plugin database, while the second command ('pluginkit -e use -i') enables it.
Once installed and enabled, the Finder Sync will be automated integrated with Finder.app. Here it can add icon overlays to files, or extend the control/right-click menu. This is of course is exactly the functionality I needed for my simple utility.
Coding up the utility was pretty straight forward, save for an odd issue where my Finder Sync would 'fail' for anything under /Volumes. I've posted this issue as a question stackoverflow, but at this time it remains unsolved.
To make the utility easy to deploy, I wrote an installer application that directly installs and activates the Finder Sync via pluginkit. I quite like that once the Finder Sync bundle has been registered with the system and activated, it will be automatically started by the OS. In other words, it's nicely persisted for us.
This got me thinking, could malware abuse this technique as well to persist in a trivial manner? Spoiler; the answer is yes :)
A 'Malicious' Finder Sync
Essentially all macOS malware seeks to persist in some manner. This ensures that whenever the computer is restarted, it is automatically re-executed by the OS. Common macOS persistence techniques include launch items (agent and daemons), login items, and browser extensions.
By registering itself as a Finder Sync, malware can be automatically persisted. However this persistence technique has a few minor 'downsides.' Most notably, in order to be persistently installed, a malicious Finder Sync has to be sandboxed, and thus signed. If it's not, the system will 'reject' it:
$ tail -f /var/log/system.log
pkd[377]: ignoring mis-configured plug-in at /Users/patrickw/code/FinderSync/persist.appex: plug-ins must be sandboxed
However, as illustrated by recent macOS malware such as Janicab and Piritt which were both signed with 'valid' Apple developer IDs, being signed is not necessarily a show stopper.
And what about being sandboxed? Well, a malicious Finder Sync could carry various 'exception' entitlements such as com.apple.security.temporary-exception.files.absolute-path.read-write in order to function in a fairly unconstrained manner.
Let's talk about some code for a malicious persistent Finder Sync. A Finder Sync is simply a macOS bundle - that doesn't really have to do anything in order to be automatically loaded by the OS. In other words the system will still install it and load it even if doesn't perform any standard Finder Sync actions such as adding a menu item or modifying the Finder.app UI in any way. Of course, for a malicious Finder Sync extension that would like to remain undetected, this is a good thing :)
Generally though, bundles or dynamic libraries (dylibs) are simply loaded and not executed per se, until something calls 'into' them in order to invoke some exported function. For a malicious Finder Sync though, ideally some code execution would occur as soon as its loaded. That is to say, it makes more sense to be self-contained and not rely on some external component in order to execute code within the malicious extension. Luckily we can simply add a constructor to the malicious Finder Sync:
//automatically invoked when bundle is loaded
__attribute__((constructor)) static void gogogo()
{
//dbg msg
syslog(LOG_ERR, "FINDERSYNC: extension (%s) off and running via constructor()", \
[[[NSBundle mainBundle] bundlePath] UTF8String]);
//do anything else!
}
While loading a bundle, the dynamic loader (dyld), will automatically invoke the bundle's constructor. This provides a reliable way to ensure that code within the bundle is executed as soon as the bundle is loaded by OS. Perfect!
Now to persistently install and register the malicious Finder Sync. First, copy the malicious bundle anywhere on the system (preferable somewhere innocuous). Then invoke the following commands:
$ pluginkit -a /some/path/persist.appex
$ pluginkit -e use -i <finder sync's bundle id>
That's it! No need for a wrapper application, nor external plist. And from now on, the malicious extension will be automatically loaded and executed by the OS every time the computer is restarted and the user logs in :)
$ ps aux | grep -i persist
patrickw /Users/patrickw/code/FinderSync/persist.appex/Contents/MacOS/persist
Detection
As previously mentioned, installed Finder Syncs can be viewed via in the 'Extensions' pane of System Preferences. However, this UI view only shows the name of the Finder Sync...not its path, nor signing authority, etc. As such, it's easy to envision a malicious Finder Sync blending in a with name such as 'DropBox', 'Google Drive', etc.
To address this, I've updated KnockKnock to both enumerate and display all installed (and activated) extensions such as Finder Syncs:
As KnockKnock shows the full path, whether its packed or not, and its VirusTotal detection ratio - in theory, it should be able to detect suspicious plugins. For example, the previous image shows KnockKnock displaying a packed extension that is unknown to VirusTotal. Obviously such an extension would warrant closer examination.
If the command-line is more your style, the pluginkit utility can enumerate all extensions via the -vmA flags. However, as it includes all system extensions a little more work is required to uncover suspicious ones:
MacBookPro:~ patrickw$ pluginkit -vmA
+ com.google.GoogleDrive.FinderSyncAPIExtension(1.0) ... /Applications/Google Drive.app/Contents/PlugIns/FinderSyncAPIExtension.appex
+ com.apple.share.System.set-desktop-image(1.0) ... /System/Library/PrivateFrameworks/ShareKit.framework/Versions/A/PlugIns/SystemSetDesktopImage.appex
+ com.apple.share.Twitter.set-profile-image(1.0) ... /System/Library/PrivateFrameworks/ShareKit.framework/Versions/A/PlugIns/TwitterProfilePic.appex
- com.apple.share.Video.upload-Tudou(1.0) ... /System/Library/PrivateFrameworks/ShareKit.framework/Versions/A/PlugIns/Tudou.appex
+ com.apple.share.AirDrop.send(1.0) ... /System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/PlugIns/AirDrop.appex
com.apple.share.System.set-buddy-picture(1.0) ... /System/Library/PrivateFrameworks/ShareKit.framework/Versions/A/PlugIns/SystemSetBuddyPicture.appex
+ com.persist.FinderSync(1.0.0) ... /Users/patrickw/Library/Internet Plug-Ins/persist.appex
+ com.apple.share.System.add-to-safari-reading-list(1.0) ... /System/Library/PrivateFrameworks/ShareKit.framework/Versions/A/PlugIns/SystemAddToReadingList.appex
Conclusions
Finder Syncs are the supported way to 'integrate' with Finder. Installation occurs automatically when an application that contains such an extension is executed for the first time. Or, a Finder Sync can be manual installed via pluginkit.
Since an installed Finder Sync is automatically executed by the OS every time the user logs in, in theory, it could also be (ab)used by malware to achieve persistence. Luckily, KnockKnock can now show all installed and activated extensions :)