Update 13th April 2013: This now works on Mountain Lion (10.8.3). Thanks to a tip, I have now worked around the problem of write-protected memory pages. See the GitHub repository for the working code.
Note: From the title of this post you will see that I ultimately failed to get the
pt_deny_attach kernel module to work in Mountain Lion. This was due to write protected memory in the kernel. However, I did manage to work around the issues presented by Kernel Address Space Layout Randomisation (KASLR). The journey that I took to my ultimate failure may be useful and interesting to others.
I recently answered a question on Stack Overflow that led me to investigate the
P_LNOATTACH process flag in Mountain Lion (OS X 10.8).
P_LNOATTACH flag is a process flag which stops other processes (debuggers, DTrace etc) attaching to the flagged process. The flag is set by calling
ptrace(PT_DENY_ATTACH). This feature was almost certainly added to OS X to stop/hinder reverse engineering the DRM used by iTunes. iTunes is certainly the oft-cited example of the use of
In order to circumvent the protection provided by
P_LNOATTACH a kernel module was created which intercepts the call to
trace with the
PT_DENY_ATTACH parameter. The most recent version of this module is for Lion (OS X 10.7.1) and is available on GitHub. The project page on GitHub says Snow Leopard but the code has been updated to Lion (10.7.1) as can be seen from the following code snippet from
1 2 3 4 5 6 7 8 9 10 11
The comment suggests a “bug in the kext loading code” stops you linking against
com.apple.kernel. I believe this is intentional in order to make kernel hacks like this one more difficult.
The Lion module was based on an earlier version for Leopard. The above link provides additional background information about the
ptrace flag and the kernel module.
The obvious first step to updating this kernel module to Mountain Lion was to download the version for Lion and load it into the kernel. This resulted in the first of many kernel panics. This was unsurprising as the layout of the kernel would almost certainly have changed between 10.7.1 (the version the module was targeted at) and 10.8.2 (the version I am currently running).
How does the Lion version work
Before we can start to update the kernel module to Mountain Lion we should really understand what the module is trying to do. Basically, the module is using pre-discovered offsets to find the
sysent data structure which contains the function pointers to all of the syscall functions in the kernel. Once it has found the
sysent table it changes the pointer to the
ptrace function to point to a function,
our_ptrace, supplied by the kernel module which checks to see if the parameter is
1 2 3 4 5 6 7 8 9 10
If the parameter is
PT_DENY_ATTACH it does nothing and returns success,
return (0), otherwise it calls the original
ptrace function passing the supplied parameters. This causes
PT_DENY_ATTACH to be ignored but other calls to
ptrace continue to function as expected.
In order to make this more difficult in Lion, Apple don’t export the location of the
sysent table. In order to find the
sysent table the module has the location of the
nsysent variable hard-coded.
nsysent contains the number of entries in the
sysent table and is stored close to the
sysent table. As the comment above suggests, it is possible to find the memory address of the
nsysent variable using the command
nm -g /mach_kernel | grep _nsysent. On 10.8.2 this gives:
1 2 3
If you try to find the
sysent table directly you will get results but not the address of
1 2 3 4 5 6 7
So, having found the 10.8.2 location of
nsysent the next obvious step is to update the module with the new location and reload it… kernel panic!
The kernel module makes an assumption about the location of the
sysent table. It assumes that the
sysent table is immediately before the
nsysent variable. It uses the value of
nsysent and the
sizeof(struct sysent) to calculate the size of the
table_size, and then subtracts this from the memory address of
nsysent to give a pointer to
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Obviously some things have changed within the kernel. Simply updating the location of
nsysent doesn’t fix the problem.
Getting it to work on Mountain Lion
After some experimentation (and many kernel panics) it became obvious that I hadn’t actually found the correct location of
nsysent in the kernel address space. Simply trying to read the value of
nsysent (which should contain a count of sys calls) was causing a kernel panic.
After some Internet research it became clear that the major difference between the Lion and Mountain Lion kernel was the introduction of Kernel Address Space Layout Randomisation (KASLR). When the kernel is loaded into memory KASLR causes the addresses of all the symbols (variables) to be randomised. The
nm command shows the location of the
nsysent variable before the randomisation has taken place. Simply changing the value of
_NSYSENT_OSX_10_7_1_ is not going to work.
KASLR works by generating a random number and then sliding the symbol addresses in memory by this random value. Basically the kernel slide value is added to all the addresses in the kernel when it is loaded into memory. The value of the kernel slide can be accessed by a privileged process using a new
syscall on OS X (this syscall was briefly available in iOS 6 betas but was removed before release). Check out kextstat_kaslr for more details.
The value of the slide is stored in the kernel in a variable called
vm_kernel_slide, we can see this in the kernel source code. The kernel source is open source and available at http://opensource.apple.com/source/xnu/xnu-2050.18.24/.
Unfortunately, Apple have not exported this variable for use by kernel modules. We are also in a chicken and egg situation because
vm_kernel_slide has also been moved by the kernel slide. Everything I tried failed to give me the value of the kernel slide. It is ironic that a user process running as
root can access this value using the
kas_info syscall but a kernel module running in the kernel doesn’t seem to be able to. (It is entirely possible that I missed something obvious at this point.)
After pondering the problem it occurred to me that the value of
vm_kernel_slide can be inferred by a kernel module. We can use
nm to get the address of an exported function in the kernel. Our kernel module can get the function pointer of a function in the running kernel. If we subtract one from the other we get the value of the kernel slide.
I updated the kernel module with code to do this. The module contains the hard-coded location of
printf which was found using
nm. The module then gets a pointer to the
printf function and uses this to calculate the slide.
1 2 3 4 5 6 7 8 9
Using the calculated slide value it is now possible to calculate the correct location of
nsysent using the following code:
1 2 3 4 5 6 7
Reading the value of
_nsysent at this point gave the correct value for the number of syscalls that Mountain Lion provides. Finally we are getting somewhere.
The next step is to find the
sysent table based on the location of
nsysent. This took a little bit of hunting as it has moved quite a way in Mountain Lion. I simply searched the memory around
nsysent checking it using the sanity checks from the Lion version of the module. I eventually found it after a few more kernel panics with an offset of
Having found the location of
sysent it was easy to confirm that the location of the
ptrace function in the syscall table matched the location returned by
nm (plus the kernel slide).
Updating the kernel module and loading it into the kernel finally… caused a kernel panic.
This is were I am now stuck and I can’t see an easy way around the problem. As far as I can tell the
sysent table is now located in a read-only memory page. Attempting to replace the value of the function pointer to
ptrace with a pointer to
our_ptrace causes a kernel panic.
The kernel source code declares the
sysent table as follows:
1 2 3 4
I assume the fact it is declared
const means that the table has been placed into a protected memory page. Feel free to correct me if I am wrong.
One last try
Given that it doesn’t seem to be possible to modify the
sysent table I wondered if it would be possible to hot-patch the
ptrace function itself. Unfortunately attempting to write to the memory associated with the function causes…
…yes, you guessed it, a kernel panic!
The source code for a kernel module that works up to the point of actually modifying the
sysent table is available on GitHub at https://github.com/mttrb/pt_deny_attach.