This bug has been fixed in Mojave Beta, but still present in latest High Sierra (10.13.5). It's a logic bug that an entitled binary tries to load an insecure external library controllable by environment variable. To exploit it we need to abuse sandbox, which is interesting that sometimes a mitigation could be turned to an exploit.
CoreSymbolication(/System/Library/PrivateFrameworks/CoreSymbolication.framework) has some private api for symbolication. When demangling swift application symbols, it tries to load external library in following order:
/System/Library/PrivateFrameworks/Swift/libswiftDemangle.dylib/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libswiftDemangle.dylib/usr/lib/libswiftDemangle.dylib${xcselect_get_developer_dir_path()}/Toolchains/XcodeDefault.xctoolchain/usr/lib/libswiftDemangle.dylib
The function xcselect_get_developer_dir_path will return environ variable DEVELOPER_DIR if it's set. Absolutely controllable.
Actually the first libswiftDemangle.dylib candidate does exist. Will it reach the vulnerable branch? I'll talk about it later.
Apple has built-in com.apple.SamplingTools in:
And they are entitled:
With this entitlement, SamplingTools can attach to SIP protected process with task_for_pid, even without root privilege.
LLDB fails even it's root:
So this seems to be like a meta entitlement. With it you can just inject to other entitled process and gain arbitrary entitlement.
Let's just start an application compiled with swift, then run symbols [pid] -printDemangling and it will call CoreSymbolication!demangle, which has potential ability to load insecure code.
But there are two problems. First, the last branch looks impossible to be reached because /System/Library/PrivateFrameworks/Swift/libswiftDemangle.dylib exists.
Actually we can just block them with a sandbox. Yeah, use the security facility to trigger something insecure.
Then, spawn a child process with symbols [pid] -printDemangling to trigger dylib hijack.
Now we have a second problem. It crashes.
com.apple.SamplingTools in latest macOS are code signed with Library Validation flag, so loading unsigned dylib is prohibited.
I just happened to have an El Capitan virtual machine and I looked into it. The previous SamplingTools distribution has valid code signature, but no library validation flag. So just copy to High Sierra and it works.
Although we can now invoke task_for_pid on any restricted process, it still requires the same euid, which means we need a local root privilege escalation exploit as part of the chain.
Now inject into diskmanagementd and you'll have the com.apple.rootless.install.heritable entitlement, which means the privilege to modify /System and spawn a shell without rootless restriction.
The bug has been fixed in Mojave Beta, no more external library, finally.
Update 2019-05-14
This bug be exploited for kernel privilege escalation. Please refer to the slides for my HITB Ams 2019 talk:
It's not XNU who validates code signature for kernel extensions, but those userspace executables that own the entitlement com.apple.rootless.kext-secure-management entitlement. These binaries are kextd, kextutil and kextload on 10.13.x.
Once you own the entitlement, you rule the kernel. The process can invoke kext_request to kindly ask XNU to load an extension:
Parameter request_data is an MKEXT message, serialized in XML format, while response_data is for reading the response back and log_data gives the logs.
This is an example of MKEXT request:
It consists of three parts:
- header
- file entry
- plist
The header defines basic information like packet length, checksum, version and CPU type. File entry has the full binary of the kernel extension. A single MKEXT request can have multiple file entries.
At the end of the packet is the plist metadata. It has the identifier, dependencies, path and part of the Info.plist of the kext bundle.
Since it's the userspace that does the validation, my exploit simply patches the kill-switch of process kextd to allow arbitrary unsigned kext to be loaded.
The service kextd checks the following conditions when we run kextload:
- Signed:
OSKextIsAuthentic - To avoid malicious modification during kext loading, it has a special staging process that the extension must be moved to a SIP-protected location. This process is ensured by function
rootless_check_trusted_class - Finally,
kextdwill ask user's approval by invoking this method-[SPKernelExtensionPolicy canLoadKernelExtensionInCache:error]
All of the functions have a same shortcut that, when csr_check (the syscall that checks the state of SIP) returns false, it will load arbitrary kext and ignore all the requirements. By reusing kextool, we don't have to manually serialize a valid kext_request on our own.
Here's the exploit to load an unsigned kernel extension on macOS 10.13:
