XPC Exploitation series

Learn XPC exploitation - Part 1: Broken cryptography

Learn XPC exploitation - Part 2: Say no to the PID!

Learn XPC exploitation - Part 3: Code injections


Intro

The last technique I showed in my presentation during Objective by the Sea v3 conference was abusing privileged XPC services using different code injections. In many apps I exploited, I observed that developers are aware that privileged XPC services have to verify incoming connections. Devs usually perform the validation using Apple’s cryptographic APIs - what is good 👍🏻, but… The problem is that these APIs cannot easily detect if there was any malicious code injected to the process. In this post, I’ll show you techniques that may help you exploit a bit more secured XPC helpers. 😉

MacOS != Linux

Before I start the exploitation, I have to mention one more thing. macOS is not Linux. The ptrace syscall is not as powerful as it is in Linux. You cannot write data to the victim’s process memory using the ptrace on macOS. For such purpose you should: 1. get the task of the victim’s app (using for instance the task_for_pid() function); 2. use the mach_vm_write function. I used that technique when I was exploiting iExplorer. You can read more about that here.

Can I inject to other processes on macOS?

Typically if you want to use the above-mentioned task_for_pid() function to retrieve old/poorly secured apps’ tasks, you need to have root permissions. However, there is an exception - if the application possesses a com.apple.security.get-task-allow entitlement set to true, you do not need the root permissions. This problematic entitlement is usually used for debugging purposes. For example, XCode signs apps with this entitlement because it attaches the lldb. Always look for that entitlement during audits. I exploited a few apps because of that left entitlement.

But why I wrote that you do need root to take over the old/poorly secured apps? Well, in the modern/well-secured apps, the Hardened Runtime capability is turned on. If the application was signed with the hardened runtime flag, even if you have root permissions, you are unable to retrieve the app’s task. If you want to debug a hardened app (without the get-task-allow entitlement), you should disable the System Integrity Protection. The hardened runtime feature gives macOS some cross-app isolation.

In all new projects, the hardened runtime flag is turned on by default. In the old projects, it was enforced by Apple in the macOS Catalina. All apps that are downloaded from the Internet (outside of the App Store) are quarantined and thus need to be notarized.

notarization in Catalina

The notarization enforces not only the hardened runtime but also lack of the com.apple.security.get-task-allow entitlement:

notarization in Catalina

How can I verify if the app has the hardened runtime turned on?

Use the following command:

$ codesign -d -vv LuLu.app
Executable=./LuLu.app/Contents/MacOS/LuLu
Identifier=com.objective-see.lulu
Format=app bundle with Mach-O thin (x86_64)
CodeDirectory v=20500 size=1538 flags=0x12300(hard,kill,library-validation,runtime) hashes=39+5 location=embedded
Signature size=8974
Authority=Developer ID Application: Objective-See, LLC (VBG97UB4TA)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=11 Dec 2019 at 23:49:45
Info.plist entries=24
TeamIdentifier=VBG97UB4TA
Runtime Version=10.14.0
Sealed Resources version=2 rules=13 files=12
Internal requirements count=1 size=216

You can see that there is a runtime flag set.

How can I verify if the app has get-task-allow entitlement set?

Also with the codesign command:

$ codesign -d --entitlements :- GetTaskAllowTest.app
Executable=./GetTaskAllowTest.app/Contents/MacOS/GetTaskAllowTest
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.files.user-selected.read-only</key>
    <true/>
    <key>com.apple.security.get-task-allow</key>
    <true/>
</dict>
</plist>

Code injection with the DYLD_INSERT_LIBRARIES

When the process doesn’t have the hardened runtime capability turned on and the com.apple.security.get-task-allow entitlements are not set, the easiest way to inject your code is to use the DYLD_INSERT_LIBRARIES environment variable. There is a great article explaining how it works in details.

To simply exploit such an application, create an exploit.m file with the following content:

#import <Foundation/Foundation.h>

__attribute__((constructor)) static void pwn(int argc, const char **argv) {
    NSLog(@"[+] Dylib injected");
}

Compile it:

gcc -dynamiclib exploit.m -o exploit.dylib -framework Foundation

Inject to a vulnerable app:

DYLD_INSERT_LIBRARIES=exploit.dylib ./App.app/Contents/MacOS/App

What if the hardened runtime is turned on? Well, there is an entitlement that can loose the restrictions and allow the DYLD_INSERT_LIBRARIES - com.apple.security.cs.allow-dyld-environment-variables. But, the runtime will verify if the loaded dylib is signed with the same certificate. In order to be able to inject to a hardened application, it has to possess also the com.apple.security.cs.disable-library-validation entitlement.

Summing up the code injection techniques

Exploiting privileged XPC services, that cryptographically validate incoming connections without the code injection checks, can be done with following tricks:

Exploitation example

An example of an application I was able to exploit using the code injection technique is LuLu - an open-source firewall. While it had the hardened runtime turned on and was verifying the incoming XPC connections cryptographically - it wasn’t detecting code injections. So, I took KextViewr (another Objective-See’s application) that was signed with the same developer certificate but didn’t have the hardened runtime. LuLu’s validation was verifying if the incoming XPC connection was initiated by a process that was signed with a defined certificate. So, I injected a dylib using the DYLD_INSERT_LIBRARIES technique to the KextViewr and established a valid connection with the privileged XPC helper. It allowed me to bypass the firewall by setting my own rule without root permissions:

The fix

LuLu mitigated the vulnerability by also verifying if the bundle identifier belongs to LuLu and if the minimum version is 1.2.0 (in that version, the hardened runtime was turned on).

LuLu fix

BTW I wrote an open-source privileged XPC helper that may help you with securing & abusing XPC apps. 😉 You can find it here.