Darwin/x86

Mac OS X Binary Protection

I hesitated for a long time to write a page about Apple's binary protection scheme. As I saw it it was one thing for me to know how to get around the protection, quite another to disclose it. But at this point there are countless kernel extensions floating about. The three major ones would be dsmos, r3d3 and AppleDecrypt. Aside from the kernel extensions various patches to xnu have included the binary decryption inside the kernel.

So if you just want to get around Apple's binary protection you have plenty of choices. Go elsewhere because you're not going to find anything ready-made here. On the other hand if you want to learn a bit about how it works, stick around.

Back in October 2006 Amit Singh kicked things off with a basic analysis of how the kernel identifies protected binaries and what the kernel does upon encountering them. This article is entitled "Understanding Apple's Binary Protection in Mac OS X". You can of course see for yourself in the xnu source. The kernel does not do its own decryption and never has. Even the closed source versions from 10.4.4 through 10.4.7 did not do their own binary decryption. Instead the kernel passes it off to a hook function via code in osfmk/kern/page_decrypt.c.

Integrity Data

One curious thing about Amit Singh's page is that the program to print the magic poem does not work on Leopard. Typically it will print nothing. Firing up any program in gdb and examining memory at (-16 * 4096 + 0x1600) typically yields only NUL bytes. So what gives? The answer to this question is staring us right in the face in the open xnu source code. In all 10.4 versions of the source there is a dsmos_page_transform_hook function taking 2 parameters as follows

void
dsmos_page_transform_hook(dsmos_page_transform_hook_t hook,
			  void (*commpage_setup_dsmos_blob)(void**, int))
{
#ifdef i386
	/* finish initializing the commpage here */
	(*commpage_setup_dsmos_blob)(dsmos_blobs, dsmos_blob_count);
#endif

	/* set the hook now - new callers will run with it */
	dsmos_hook = hook;
}

However, in all 10.5 versions of the xnu source it looks like this:

void
dsmos_page_transform_hook(dsmos_page_transform_hook_t hook)
{
/*	printf("%s\n", __FUNCTION__); */

	/* set the hook now - new callers will run with it */
	dsmos_hook = hook;
}

As you can see, Apple removed the second parameter. Both parameters are function pointers. The first is stored in a global variable and the second is called immediately with some parameters. It is the second parameter that is involved in the commpage stuffing. The Tiger kernel code calls it with the parameters dsmos_blobs and dsmos_blob_count. Both globals are external to page_decrypt.c and are actually declared in osfmk/i386/commpage/commpage.c.

The dsmos_blobs variable is simply an array of void pointers and the dsmos_blob_count is simply an integer count of the number of used entries in dsmos_blobs. Also in commpage.c we can see how the kernel fills these in. At the end of commpage_populate_one the code stores an address into the next available slot and increments dsmos_blob_count. On a 32-bit system the eventual result is that dsmos_blob_count will be one and the first entry in dsmos_blobs will point to the kernel's address for the "system integrity data" for 32-bit processes. On a 64-bit system dsmos_blob_count will be two with the first entry pointing to the integrity data for 32-bit processes and the second one pointing to the integrity data for 64-bit processes.

It is clear simply by looking at Apple's open source code and examining the eventual result from a normal user-space program that the integrity data is eventually supposed to be present at these two locations. This is what the commpage_setup_dsmos_blob function pointer parameter does. The kernel immediately calls it and the kernel extension supplying the function pointer immediately stuffs the data into either 1 or 2 commpages.

Or at least that's what's supposed to happen. Funny thing about the hackintoshers. A number of them don't seem to be too bright. About a year ago now (summer of 2007) I decided to analyze the dsmos.kext that was floating around the internet. Sure enough it does not do this right. In fact, what it does is actually rather queer. The correct code would be to iterate over the blob pointers in the array and copy the blob to the memory pointed to by each one. It would look something like this:

static void commpage_setup(void **blobs, int count)
{
    for(int i=0; i<count; ++i)
    {
        memcpy(blobs[i], karma, INTEGRITY_BLOB_SIZE);
    }
}

Assume for that code that karma is an array of characters containing the magic poem and that INTEGRITY_BLOB_SIZE is 256 because the magic poem is in fact 256 bytes of data. The 256th and last byte (e.g. karma[255]) is a space (hex 0x20). Thus if you were to declare karma as a C string you would actually wind up with 257 bytes of string with the 257th byte (karma[256]) being a 0 byte. That byte is not to be copied to the commpage.

Well, dsmos doesn't exactly do that. Instead what it does is this:

static void commpage_setup(void **blobs, int count)
{
    memcpy(blobs[count], karma, 255);
}

What!? Yeah, it doesn't make any sense to me either. The biggest mistake is that blobs[count] is clearly accessing the array beyond its bounds since in C its bounds are 0 through count-1. If you looked at the dsmos_blobs definition in commpage.c though you'd note that dsmos_blobs has three members. So what is the third member for? Good question. The kernel itself does not appear to use it. Furthermore, blobs[count] should always be zero because dsmos_blobs is global uninitialized data which would put it in a section that should be cleared to zero in early kernel startup.

The second mistake of course is to copy only 255 instead of 256 bytes. It's possibly just a misunderstanding that the integrity data is truly 256 bytes long and does not include a NUL terminator.

Pondering the magic poem is all well and good but ultimately it's a giant red herring. The magic poem appears to be checked by some Apple binaries. After all, I think that is the whole point of it. That way, even if somebody decrypts the binaries there is still a chance for the code to check for the magic poem in the commpage and start behaving oddly if the integrity data is missing or corrupt.

The conspiracy theorist in me thinks that Apple may have even intentionally released the broken dsmos code just to throw people off. I wonder though why they removed the integrity data from the Leopard releases. All I can figure is that it outlived its usefulness.

Decryption

If the magic poem doesn't have anything to do with the encryption, then what does? Looking back in page_decrypt.c we can see that the first parameter (only parameter on Leopard) to dsmos_page_transform_hook is stored in the dsmos_hook global. That global is initialized to &_dsmos_wait_for_callback which is a function located in the same file. What the function does is poll the value of dsmos_hook until it's something other than itself or NULL.

The reason it does this is that the kernel extension that calls dsmos_page_transform_hook is usually loaded by kextd which is run by launchd. With this scheme protected binaries will simply hang waiting for the decryption engine to be installed. As soon as the engine is installed the processes will resume.

The encryption itself is just basic AES-256. At the end of January 2008 Amit Singh finally posted a new article about the binary protection. This one is titled "TPM DRM" In Mac OS X: A Myth That Won't Die. In it, Singh posts some code which illustrates how one can retrieve the AES-256 keys from a genuine Mac and how one can then use those keys to encrypt his own binaries.

Of course, Singh didn't give up anything that wasn't already pretty well known. What is notable is that he's a respected software engineer who finally published what a lot of people already knew.

To be honest I think the TPM DRM myth persists because of the mystique of it. I mean, who's gonna believe that Apple's super-secret binary protection is really just two AES-256 keys stored in the clear on their hardware and used in a particular manner by regular old code supplied in a kernel extension? It's not like they even tried to hide it. Apple's decryption engine is hiding in plain sight, fully contained within Dont Steal Mac OS X.kext.

Poor-man's Decryption

One of the early methods used in various distributions of OS X was to pre-decrypt the protected binaries. This is surprisingly easy to accomplish. Recall that the decryption process occurrs as the executable is paged into memory by the kernel's execve function. That means that the running image is thus already decrypted. If you simply load a protected binary with GDB and break extremely early in process startup before dyld has a chance to modify the image you can just use GDB commands to dump regions of memory to a file.

You know which regions to dump because the Mach executable header tells you. Once you have dumped those regions you simply overwrite the encrypted portions of the binary with the decrypted portions and remove the encrypted flag from the header. You can do all of this using only gdb, dd, and otool.

Thus using only tools provided to you by Apple and a tiny bit of know-how you can circumvent Apple's binary decryption. Of course now that the specific decryption method is widely known it would be silly to waste time doing this. And it should also be noted that even if you do this you still don't have the integrity data in the commpage which may or may not be required depending on which OS release you are using.

Virtualization

If you are working on running OS X in a virtualizer you'd probably rather provide virtual hardware similar to Apple's real hardware and allow the real Apple decryption engine to do the work instead of supplying your own decryption engine. In this case you simply need to virtualize enough of the Apple SMC chip such that it can return the values for the 'OSK0' and 'OSK1' keys. On the open source front, Alex Graf has written a series of patches for QEMU which do this.

The advantage of this method is that you do not have to supply the key material nor the decryption engine. On the host side simply retrieve OSK0 and OSK1 from the genuine Mac host and on the guest side simply virtualize the SMC to at least return the values for those two keys.

Cat and Mouse

Occasionally I wonder if Apple could do anything to break OS X on non-Mac machines without breaking it on Mac machines. Some people speculate that Apple may begin using the TPM even though they do not now. That is unlikely.

Ultimately any binary protection relies on encrypting the binaries and hoping that it takes people a while to figure out how to decrypt them. Apple could change the encryption method. Perhaps they would use the same keys but something other than AES-256. Perhaps they would rotate the keys by a certain number of bits before passing them into the AES-256 decryption routine. Perhaps they would swap the two keys so the first is used for the second half of the page and the second for the first half of the page.

In the end it's a lot of work for a lot of nothing. And so far the trend is to remove what is known to be broken. For example, the Leopard releases no longer require integrity data in the commpage. It may be the case that by the time Snow Leopard is released the encryption will be gone altogether.

Or it may be the case that Apple leaves the encryption in. The simple fact that you have to do something to break the encryption is enough to convince a large number of people that they'd be better off just buying a Mac from Apple.

Final Thoughts

I think it's safe to say that Apple's "hard work" wasn't "guarded" particularly well. Regardless I suggest you give Apple the courtesy of adhering to the seond part of their message: "Please don't steal!"