Santa is a binary whitelisting/blacklisting system made by Google’s Macintosh Operations Team. While I refer to it as Google’s Santa it is not an official Google product. It is based on a kernel extension and userland components to control the execution of binaries in OS X systems.
It features two interesting modes of execution, monitor and lockdown. The monitor mode is a blacklisting system, where all binaries except those blacklisted can run. The lockdown mode is a whitelisting system, where only the whitelisted binaries can run and everything else will be blocked. This is the mode we want to attack and bypass since it’s the most interesting one from an attacker’s perspective.
The system works by having the kernel extension to notify the userland daemon about every new process that is executed in the system. The kernel extension retrieves this information using the Kernel Authorization (kauth) feature available since OS X 10.4. Essentially a callback is installed and the Santa driver will be notified every time a process is executed. This means that exec() and variants will result in a notification to the driver that will then decide to send the event to userland or not.
The userland component has an associated database that can whitelist or blacklist binaries based on path, certificate, and hashes (I think I’m not wrong on this). The code signature feature is interesting because it allows you to whitelist or blacklist an entire publisher. For example a company could easily restrict all the software that is allowed to run in their systems based on their code signing certificate. By default the lockdown mode install will whitelist Apple’s and Google’s certificates, else the system would enter a deadlock. Assuming we are not tampering with Santa’s binaries and attacking its implementation (if we can run kernel exploit code we can easily disable it) can we bypass the lockdown mode if we want to run our code that is not allowed to run?
Yes we can, and it’s very easy to do it 🙂
What Santa essentially controls and restricts is exec and variants. But that’s not the only possible way to run arbitrary code. There is the obvious way of exploiting something and running our shellcode/ROP payload, and there are also dynamic libraries. Because Santa only controls exec we can run whatever code we want via a dynamic library injected using DYLD_INSERT_LIBRARIES without tampering with any Santa binary. We can go further and instead of putting all our code inside a dynamic library we can use it to run regular binaries that are not authorized to in lockdown mode.
We simply need to piggyback on any Apple signed binary (remember the system deadlock problem) with DYLD_INSERT_LIBRARIES to inject our library. For example any command line utility such as /bin/ls will do the job.
How can we execute other binaries using an injected dynamic library? That’s quite easy using some obscure and deprecated dyld functions. For example NSCreateObjectFileImageFromMemory and NSLinkModule would allow us to load an arbitrary executable into memory and execute it (please refer to Mac Hackers Handbook Chapter 9 and MemoryBasedBundle example by Apple).
The workflow is very simple. We inject our library into a whitelisted binary, load the unauthorized binary with those two dyld functions, and start it by calling its entrypoint (main) function. Because this doesn’t trigger a second exec we just bypass Santa controls. The original process will continue execution in the unauthorized binary and that’s it.
The sample code uses the deprecated APIs, which can be removed anytime (although they have been marked deprecated for quite a few OS X major versions). There’s really no need to use those APIs because we can do all their work ourselves. Most of the work is related to linking, so we could implement ourselves a simplified linker or re implement those functions and have dyld do all the dirty work for ourselves. As long we are able to inject a dynamic library we are able to bypass Santa. The DLL hijacking issue recently presented by Patrick Wardle could be used to plant the library and then execute any APT material.
The easiest way to fix this is to remove the possibility of library injection via DYLD_INSERT_LIBRARIES. The next post contains a kernel extension that implements this by injecting a __RESTRICT segment into certain binaries we want to restrict injection. The real problem is that DYLD_INSERT_LIBRARIES feature should not exist by default and should be instead a system setting disabled by default. Stefan’s SyScan presentation is a good read regarding the problems of default features and unfixed stuff in iOS (and OS X).
Proof of concept code:
Hello Santa Bye Santa – https://github.com/gdbinit/hello_santa_bye_santa
Last time I tested this was a month ago or something. Judging by the commits logs I guess the issue isn’t fixed so it should still work. This is technically a zero day ;-). I wanted to present it a SyScan’s WhiskeyCon but then decided not to, and now I’m disclosing it because the next post about rootpipe fix requires its disclosure :-(.
P.S.: Defense is hard, in particular when working on a minefield such as OS X 😉
Update: This is a very nice blog post talking about white-list systems expectations. This is exactly what’s missing from most security products. Right after their features they should discuss their assumptions, shortcomings, and expected scenarios. You know, most of the times the expectations between who builds the product and who uses it are quite far away. Yes, it’s probably more wishful thinking than anything else. Commercial products will never do this, they are too afraid of losing customers ;-).