'Untranslocating' an App
› apple broke some of my apps - let's show how to fix them!
12/15/2016
I'm going to caveat that first and foremost I'm a security researcher, not a macOS developer. Thus if after reading this blog post you have a better suggestion about how to deal with App Translocation, I'd love to hear it!
Most my writings cover macOS malware, exploits, or new tools. This blog takes a slightly different path, describing how to locally 'undo' one of Apple's recent security mitigations. Why would we ever want to do this? Read on!
Background
Recent versions of OS X/macOS have many security mechanisms baked into the OS. For example, XProtect provides a basic signature-based anti-malware mechanism, System Integrity Protection (SIP) protects OS files and binaries, and GateKeeper blocks the execution of unsigned applications from the internet.
Unfortunately myself and others have found serious weaknesses in all such security mechanisms, which could be trivially abused by malicious adversaries to bypass them. Take for example Gatekeeper. In presentation entitled, "Gatekeeper Exposed" I detailed Gatekeeper's internals and illustrated two distinct bypasses (CVE 2015-3715, CVE 2015-7024). Both bypasses allowed unsigned applications or code to be executed from the internet, even with Gatekeeper set to its highest level ('only allow apps from the Mac App store'). Opps!
The first bypass was based on a technique I dubbed, 'dylib hijacking.' As illustrated below, though Gatekeeper does validate the downloaded application that is executed by the user, external content was not validated. Thus, if a signed application statically referenced any external dynamic content relatively, such as dynamic library (i.e. ../../someDylib), that content was not validated, and thus could be unsigned and/or malicious.
Apple did a sufficient job patching this. By statically parsing the Mach-O header (specifically, load commands such as LC_LOAD_DYLIB) of the signed application bundle, they now verify that no 'relatively-external' dylibs are referenced.
CVE 2015-7024, the second Gatekeeper bypass, again abused external content however in a dynamic manner. In short, since Gatekeeper still did not validate nor block external content, any signed application that dynamically executed 'relatively-external' content could be abused:
For example, take ictool, an Apple-signed (and thus Gatekeeper-approved) binary. When run, it looks for binary named ibtoold within the same directory. If found, it blindly executed and not validated by Gatekeeper. This is illustrated in pseudo code below, generated from reversing the ictool binary:
//execute ibtoold
void IBExecDirectly()
{
//build path to ibtoold
ibToolPath = IBCopyServerExecutablePath()
//exec it
execv(ibToolPath, ....)
}
//build path to ibtoold
char* IBCopyServerExecutablePath()
{
//get full path to self (ictool)
icToolPath = IBCopyExecutablePath()
//remove file component
icToolDir = IBCreateDirectoryFromPath(exePath)
//add 'ibtoold'
ibToolPath = IBCreatePathByAppendingPathComponent(icToolDir, "ibtoold");
return ibToolPath
}
Thus, an attacker could create a malicious installer image (or inject it into an insecure download), that contains both the signed instance of ictool and a malicious unsigned piece of malware named ibtoold. Boom; Gatekeeper bypass:
Unfortunately, Apple's attempt to patch CVE 2015-7024 was rather weak, and thus trivial is bypass! Although I clearly articulated the issue and specifically informed them that ictool was only one of many signed apps that could be abused, their patch was to simply blacklist it:
Of course, using any other signed app that executed external content could still be abused. Moreover, neither the patch for CVE 2015-3715 nor CVE 2015-7024 addressed the underlying issue; external code was not validated.
Apple of course knew this, and in parallel, was working on a long-term comprehensive solution.
Background
Introduced at WWDC 2016, 'App Translocation' or 'Gatekeeper Path Randomization' (GPR) is Apple's response to my Gatekeeper flaws:
With the introduction of App Translocation, Apple states:
"Starting in OS X v10.12, you can no longer provide external code or data alongside your code-signed app in a zip archive or unsigned disk image. An app distributed outside the Mac App Store runs from a randomized path when it is launched and so cannot access such external resources."
Apple correctly realized that the issues I found all stemmed from the fact that external content, referenced relatively to the verified application, was not verified. Their solution was (conceptually) simple: when launched, transparently create a read-only DMG image at a randomized location which contains the application bundle (and only the application bundle). This effectively thwarts any bypass that abused external content because:
- only the (cryptographically verifiable) application bundle is translocated
- the translocated mount point is randomized, and read-only
App Translocation is applied to unsigned software packages (i.e. ZIP archives) that have been downloaded from the internet. To determine if something was downloaded (and thus is a candidate for translocation), the OS examines the com.apple.quarantine extended attribute. Using the xattr command, we can dump such attributes:
$ xattr ~/Downloads/TaskExplorer.zip
...
com.apple.quarantine
My previous talks on Gatekeeper, such as the aforementioned "Gatekeeper Exposed" discuss the concepts of extended attributes, specifically, com.apple.quarantine in detail:
So, now imagine an attacker has found an legitimate signed application that attempts to load or execute some relatively external content (i.e outside it's app bundle, but within the same download package). In the past, they could use this to bypass Gatekeeper as such external content was not verified. Now however, when the user double-clicks the application to execute it, the OS intercepts this, and will create a read-only DMG image on the fly for the application bundle, and only the application bundle. This translocated copy is then executed. Here in this new location, it will not be able find the unverified external content (as it was not copied over), and thus the attack fails.
The beauty of this patch or rather rearchitecting of Gatekeeper is that it appears to generically thwarts all Gatekeeper bypasses that abuse relatively external content. Of course to make this all seamlessly work, Apple was forced to re-engineer a lot of OS components. Sorry guys!
If you are interested learning more about App Translocation, check out the following sources:
As a security researcher/hacker, I must give a lot of kudos to Apple for (finally) fixing the underlying issue in comprehensive manner. However as developer, f**kkkkk this broke a lot of stuff.
App Trans-Broke-ation
A quick google of 'App Translocation' reveals a myriad of developers legitimately bemoaning the introduction of this security mechanism.
I'd like to take a moment to sincerely apologize for being, at least partially responsible, for causing you all such pain :(
So why does App Translocation cause issues for legitimate applications? Well, any application that is distributed outside the Mac App Store in a ZIP archive or unsigned DMG is subject to App Translocation. If the application, once executed, attempts modify any of its components (perhaps during an autoupdate) or files (even metadata) this will fail as the application was automatically copied (translocated) to a randomized read-only location.
Personally, this broke several of my tools.
Let's look at how and why, and show that the suggested 'solutions' all proved less than ideal. Thus, I eventually said "screw it" and decided to simply 'untranslocate' my application. Turns out this was rather trivial and allowed my apps to work again, even when translocated.
One of my tools that App Translocation completely broke was TaskExplorer. This tool allows one to visually explore all running tasks (processes). Besides displaying a list of running tasks (processes) it also shows tasks' signature status, loaded dylibs, open files, network connection, VirusTotal detections, and much more:
TaskExplorer is a self-contained application bundle, that is made up of two components; the UI and an XPC service. The UI contains most of the app's logic, save for code that must run at a higher privilege levels. What code is this? Well stuff like getting a remote process's:
- loaded dylibs
- open files
- network connections
- command-line arguments
The first time TaskExplorer is run, it requests that the user authenticate with the OS, so that it can make the XPC-service setuid. Yes yes I know that this is not the recommended way of doing things, but it's simple and works (and btw, only the application can talk to the XPC-service anyways). I'd prefer not to ask the user to authenticate each time the app is run or have the entire app run as root, and using SMJobBless with XPC IMHO makes no sense here. Why not? Well SMJobBless creates a persistent launch item, that is not removed when the application is deleted, requiring an uninstaller such as this. For a stand-alone application that allows one to explore running tasks, creating an installer/uninstaller and/or having the OS create a persistence launch daemon seems like massively overkill!
TaskExplorer worked great until App Translocation came along - and totally broke it. Now when run, Task Explorer can no longer setuid its XPC component as App Translocation translocates it into a read-only filesystem (DMG).
Ok, so how to fix this? Let's look at a few suggested options. Specifically, we'll discuss the following, and illustrate why they are all less than ideal:
- refactoring the application
- moving the application bundle
- creating an installer
- creating a signed DMG
- creating an signed ZIP (XIP)
Before diving in to these options, it's important to understand that the users of my tools are my priority. That is to say, I'm going to be drawn towards solutions that do no require the user to jump thru extra hopes. Moreover, if a solution is in any way 'confusing' or inadvertently allows the user to screw up (resulting in my app failing to run), that's a no go.
› refactoring the application
As mentioned, the 'correct' way to create an application that requires a privileged component is to use the SMJobBless API. While this is compatible with App Translocation it results in the creation of a persistent launch daemon. Yes, not the end of the world, but IMHO, 100% overkill for a simple application who's goal is explore running tasks. Moreover, the launch daemon is not automatically removed when the user deletes the application - meaning an uninstaller would be required and executed by the user. Due to this, I decided against refactoring TaskExplorer, even though it would address the App Translocation issues.
› moving the application bundle
Apple notes that if the user moves the application bundle (for example to /Applications), App Translocation does not come into play. This is due to the fact that a user manually moving the (cryptographically verifiable) application bundle outside the download package (i.e. zip archive) would thwart Gatekeeper bypasses that leverage relatively-external content. So, if I could simply get all users to move TaskExplore.app, say into /Application before running it, that'd 'fix' the App Translocation incompatibility. The issue of course is that there will be some percentage of users that simply unzip the app, then execute it directly without first moving it. As this is what they are used to doing, I'm not faulting them. Unfortunately, if TaskExplorer is not first moved, it will be translocated which breaks the app. And if you are thinking, "Well couldn't TaskExplorer detect that it wasn't moved (and then alert the user to move it, or do so automatically?" ... sure, but the user could choose to download and/or move TaskExplorer to any location. This makes things a little complicated (how can it easily detect that it's been moved if it doesn't know where it was downloaded to). As I'm not going to depend on the user moving the application, nor want to add a ton of extra code to handle the case where it wasn't moved, I opted to look into other solutions.
The better question of course, is to ask "has TaskExplorer been translocated?" We'll answer this in a minute :)
› creating an installer
Another obvious option would be to create an installer for the application. While other tools I've written (such as BlockBlock, which have persistence components) do have installers and such an installer would be compatible with App Translocation, again this seems like overkill. Also, installers inherently introduce some confusion for users. For an application such as TaskExplorer that isn't persistent (i.e. is designed to be run manually by the user vs. automatically), the user would have to be explicitly told where the installer installed the application to. Invariably there still would be questions about where the application was installed to, and also questions about whether an uninstaller was needed (trust me, I get a lot of emails about this!). As such, this solution didn't seem ideal.
› creating a signed DMG
Apple tells us that App translocation does not apply to disk images (DMGs) that are signed. So instead of using a ZIP archive to distribute the application, I looked into using a signed DMG. To create a signed DMG I used the following make file (kindly provided by one of Objective-See's users -mahalo Clayton!!):
VERSION="1.5.0"
PROJECT="TaskExplorer"
CODESIGN_IDENTITY="Developer ID Application: Objective-See, LLC (VBG97UB4TA)"
## dmg
dmg:
rm -f ./${PROJECT}*.dmg
rm -rf /tmp/${PROJECT}-build
mkdir -p /tmp/${PROJECT}-build/
cp -R ./TaskExplorer.app /tmp/${PROJECT}-build
hdiutil create -srcfolder /tmp/${PROJECT}-build -volname "${PROJECT}" -format UDZO
-o ${PROJECT}-${VERSION}.dmg
xattr -rc ${PROJECT}-${VERSION}.dmg
codesign -s ${CODESIGN_IDENTITY} -v ${PROJECT}-${VERSION}.dmg
rm -rf /tmp/${PROJECT}-build
Using another of my tools, WhatsYourSign we can validate the creation of the signed DMG:
While this 'avoids' App Translocation (since the app is now distributed via a signed DMG), this still breaks TaskExplorer since the DMG image is not writeable. Can we simply create a DMG that is writeable? Sure; just change the UDZO flag to UDRW in the makefile. Unfortunately, when a user downloads and double-clicks on a writeable DMG, while it does get mounted this happens in the background and does not automatically open in Finder.app. To the user, it appears that nothing happened :( Honestly, this was even confusing even to me. Uggh, another 'solution' down the drain.
› creating a signed ZIP (XIP)
I figured since signed DMGs are excluded from App Translocation, maybe signed ZIP archives would be as well. Recently, Apple introduced a signed ZIPs, or XIPs. According the XIP man-page (man xip), "a XIP file is an analog to zip(1), but allows for a digital signature to be applied and verified
on the receiving system, before the archive is expanded. When a XIP file is opened (by double-clicking), Archive Utility will automatically expand it (but only if the digital signature is intact)."
Using the xip tool (/usr/bin/xip) it's trivial to create a signed archive for secure distribution.
$ xip --sign Objective-See TaskExplorer.app/ TaskExplorer.xip
xip: using timestamp authority for signature
xip: signing archive with identity "Developer ID Installer: Objective-See, LLC (VBG97UB4TA)" from keychain ~/Library/Keychains/login.keychain-db
xip: adding "~/objective-see/TaskExplorer/TaskExplorer.app"
xip: wrote signed archive to "~/objective-see/TaskExplorer/TaskExplorer.xip"
So far so good! Unfortunately, when attempting to open the XIP (i.e. double-clicking it), the Archive Utility fails to open it:
Looks like the Archive Utility will only open XIPs from Apple. Lame. However this may be a moot point, as Sparkle contributor, @Zorg__ points out that XIPs may not be excluded from App Translocation anyways:
In my humble opinion, all solutions, including those suggested by Apple, are less that ideal or don't work. For a simple stand-alone application such as TaskExplorer, distribution via a ZIP archive is perfect...other than App Translocation.
Untranslocation
So, let's look at how we can trivially 'untranslocate' TaskExplorer. Why would we want to do this? well if we can, the benefits of this approach will be:
- TaskExplorer will work (again!) even if the OS translocates it
- TaskExplorer can still be distributed via a standard ZIP archive
- no installers nor other user interactions are needed
And since TaskExplorer is wholly a self-contained application that doesn't utilize any relatively-external content, this does not introduce any security risks.
In order to 'untranslocate' an application, we only need to do three conceptually simple things:
- determine the original location on the file-system the application was translocated from (e.g. ~/Downloads/TaskExplorer/TaskExplorer.app)
- remove the com.apple.quarantine attribute from the application in the original location
- programmatically re-execute the application in the original location
This all works since App Translocation makes a copy of the application when translocating (meaning the original downloaded application will still be present)...and if we remove the com.apple.quarantine attribute from this original application it will no longer be translocated (since com.apple.quarantine is a prerequisite for App Translocation). Without App Translocation, TaskExplorer will once again work perfectly :)
So let's take a brief look at each of these steps!
First, we, or rather TaskExplorer, needs to determine if it has been translocated. Thanks to some excellent online writeups, this has already been solved.
Turns out, the security framework (/System/Library/Frameworks/Security.framework/Security), which is in charge of performing App Translocating, contains some very interesting APIs such as SecTranslocateIsTranslocatedURL and SecTranslocateCreateOriginalPathForURL.
As its name suggests SecTranslocateIsTranslocatedURL will set a boolean (true/false) to indicate whether or not a file has been translocated. Can TaskExplorer invoke this method to determine if the OS has translocated it? Of course :) Simply load the security framework, resolve the function, and invoke it as such (error checking removed for brevity):
//function def for 'SecTranslocateIsTranslocatedURL'
Boolean (*mySecTranslocateIsTranslocatedURL)(CFURLRef path, bool *isTranslocated, CFErrorRef * __nullable error);
//flag for API request
bool isTranslocated = false;
//handle for security framework
void *handle = NULL;
//open security framework
handle = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY);
//get 'SecTranslocateIsTranslocatedURL' API
mySecTranslocateIsTranslocatedURL = dlsym(handle, "SecTranslocateIsTranslocatedURL");
//invoke it
mySecTranslocateIsTranslocatedURL((__bridge CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]], &isTranslocated, NULL);
//bail if app isn't translocated
if(true != isTranslocated)
{
//bail
goto bail;
}
//ok, app *is* translocated!
The following code will allow TaskExplorer to determine if it has been translocated. Assuming it has been (that is to say, SecTranslocateIsTranslocatedURL sets its second parameter to true), the app needs to determine its original location on the file-system.
This can be determined via the aforementioned SecTranslocateCreateOriginalPathForURL function which return the original URL of a translocated application:
//original URL
NSURL* untranslocatedURL = nil;
//function def for 'SecTranslocateCreateOriginalPathForURL'
CFURLRef __nullable (*mySecTranslocateCreateOriginalPathForURL)(CFURLRef translocatedPath, CFErrorRef * __nullable error);
//get original URL
untranslocatedURL = (__bridge NSURL*)mySecTranslocateCreateOriginalPathForURL((__bridge CFURLRef)appPath, NULL);
If previous two code snippets are added into TaskExplorer, when executed, (with some extra NSLogs()), they'll output the following:
TaskExplorer app is translocated!
TaskExplorer original URL: ~/Downloads/TaskExplorer.app/
TaskExplorer translocated URL: file:///private/var/folders/r3/9nbl60856zn82n6wdtwrxw8w0000gn/T/AppTranslocation/7E2258D4-DD10-4B39-B659-F9C9C1CC7A9F/d/TaskExplorer.app/
Dope! This illustrates that a translocated application can reliably detect that it was indeed translocated, and from where.
With this information the translocated application can programmatically both remove the quarantine attribute and then re-execute the original instance of the application (e.g. in ~/Downloads). The net result of this is that the App Translocation is 'undone' and the application is transparently executed from its original location on a writeable filesystem. This is all TaskExplorer needs to function correctly:
//main interface
// ->contains extra logic to handle app translocation
int main(int argc, char *argv[])
{
//untranslocated URL
NSURL* untranslocatedURL = nil;
//get original url
untranslocatedURL = getUnTranslocatedURL();
if(nil != untranslocatedURL)
{
//remove quarantine attributes of original
execTask(XATTR, @[@"-cr", untranslocatedURL.path]);
//relaunch original
// ->use 'open' as allows two instances of app (this instance is exiting)
execTask(OPEN, @[@"-n", @"-a", untranslocatedURL.path]);
//this instance is done
return 0;
...
Conclusions
App Translocation was introduced by Apple in order to generically thwart various Gatekeeper bypasses that I uncovered. From a security perspective, it's a solid solution! However, as is often the case with security mitigations that attempt to 'fix' core architectural issues, usability was greatly impacted :(
As shown, Apple's recommended workarounds either introduced a decent amount of complexity (that seems like overkill for a standalone app such as TaskExplorer), or depended on the reliability of end-users. Thus, I decided to go another route. Yes, it depends on private APIs (hey, i'm foremost a security researcher, so love such solutions!). But! it provides a reliable solution that works around App Translocation (which broke my apps), while remaining completely transparent to the end user. In my book that's a win-win!
...now back to hunting for macOS 0days ;)