INTRODUCTION
Last week at the
Power of Community 2010 (POC2010) security conference in Seoul Charlie Miller presented his talk about the changes in Snow Leopard security. An important message of his talk was that Apple's failure to load the dynamic linker DYLD at a random base address is a major weakness from a security point of view. Charlie demonstrated how a ROP payload can be build that only uses parts of the not randomized DYLD binary. You can download his slides
here.
At the same conference I presented my research into adding ASLR to jailbroken iPhones which also mentioned the fact that there are several similarities between iOS and Mac OS X Snow Leopard in regards to the DYLD and the dyld_shared_cache. I also mentioned that not rebasing the DYLD binary is a major weakness because it consists enough code to kickstart shellcode with a ROP stub based on DLYD only. Therefore it was pretty straight forward to just apply my research into rebasing the DYLD binary from iOS to Mac OS X Snow Leopard, which is presented here. You can download my slides
here and the antid0te iPhone security tool will be available
here once it is released.
PREPARATION
During my talk I mentioned that Apple is nice enough to ship the DYLD binary with the relocation information not stripped from it. This basically means that all tools able to rebase mach-o files can be used to rebase the dynamic linker. The basic idea is that just rebasing the dynamic linker a single time is sufficient to stop all drive by attacks that just assume an unchanged Mac OS X Snow Leopard. Of course just rebasing is not as secure as a complete implementation of runtime ASLR.
Unfortunately when you try out the rebase program which is part of the ld64 package and ships with Mac OS X Snow Leopard you will realize that it is actually not that simple. You will get the following error message:
$ rebase /usr/lib/dyld
rebase warning: file is not a dylib or bundle for /usr/lib/dyld
rebase warning: file is not a dylib or bundle for /usr/lib/dyld
rebase warning: file is not a dylib or bundle for /usr/lib/dyld
The problem here is that the rebase tool is not supposed to be used for rebasing the dynamic linker of executable files. And even if you change the filetype flag inside the mach-o header to a dylib or bundle the rebased dynamic linker will crash because the original rebase program cannot rebase the program's entrypoint.
Because of this I created a little patch against the ld64 package that adds support for rebasing the dynamic linker to the rebase program. While creating that patch I also realized that the rebase program is not working correctly for x86_64 mach-o files because it is not 64 bit safe. In order to try out the following steps you need the patched rebase program. You can produce this binary yourself by downloading the patch from
here and also downloading the original ld64 package from the
Apple servers. For those to lazy to compile I also provide a compiled version
here.
REBASING DYLD
Once having access to the patched rebase program we can start into rebasing the dynamic linker. I strongly remind you that this is experimental. If you attempt to rebase your own dynamic linker and do this in a wrong way you will break your installation. You will not be able to recover from this without access to a Mac OS X Snow Leopard installation medium that allows you to start a rescue shell.
THEREFORE DO NOT ATTEMPT TO DO THIS IF YOU ARE UNFAMILIAR WITH THE UNIX SHELL AND CONSIDER YOURSELF AN AVERAGE COMPUTER USER. I WILL NOT PROVIDE ANY KIND OF SUPPORT IF YOU BREAK YOUR MAC. ANYTHING YOU DO TO YOUR MAC IS AT YOUR OWN RISK. YOU HAVE BEEN WARNED.
Now that you have continued reading you are either considering yourself to be an expert or you are just very brave :P
Before you rebase the dynamic linker I strongly suggest that you create a backup copy of the dyld binary.
$ cd /usr/lib
$ sudo cp dyld dyld.orig
If you accidently break your dynamic linker you can use a Mac OS X Snow Leopard boot DVD and the recovery shell to restore to the original dyld binary. I also suggest that you perform all the rebasing on a temporary copy before you actually replace the main dyld binary.
Before we go into rebasing dyld I suggest you take a look at the sleep binary and where it loads the dyld binary.
$ gdb /bin/sleep
GNU gdb 6.3.50-20050815 (Apple version gdb-1510) (Wed Sep 22 02:45:02 UTC 2010)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin"...Reading symbols for shared libraries .. done
(gdb) run 1000
Starting program: /bin/sleep 1000
Reading symbols for shared libraries +. done
^C
Program received signal SIGINT, Interrupt.
0x00007fff872e3fca in __semwait_signal ()
(gdb) info shared
The DYLD shared library state has been initialized from the executable's shared library information.
All symbols should be present, but the addresses of some symbols may move when the program is executed,
as DYLD may relocate library load addresses if necessary.
Requested State Current State
Num Basename Type Address Reason | | Source
| | | | | | | |
1 dyld - 0x7fff5fc00000 dyld Y Y /usr/lib/dyld at 0x7fff5fc00000 (offset 0x0) with prefix "__dyld_"
2 sleep - 0x100000000 exec Y Y /bin/sleep (offset 0x0)
3 libSystem.B.dylib - 0x7fff872a8000 dyld Y Y /usr/lib/libSystem.B.dylib at 0x7fff872a8000 (offset 0x7fff872a8000)
(commpage objfile is) /usr/lib/libSystem.B.dylib[LC_SEGMENT.__DATA.__commpage]
4 libmathCommon.A.dylib - 0x7fff82edc000 dyld Y Y /usr/lib/system/libmathCommon.A.dylib at 0x7fff82edc000 (offset 0x7fff82edc000)
(gdb)
You can see that the dyld binary is normally loaded at 0x7fff5fc00000. As demonstrated by Charlie Miller this information is enough for an attacker to kickstart shellcode in an otherwise randomized memory layout. We will now stop this by rebasing our dynamic linker.
$ cd /tmp
$ cp /usr/lib/dyld /tmp/dyld
$ rebase.patched -v -arch i386 -low_address 0x8F123000 dyld
i386 0x8FE00000 -> 0x8F123000 dyld
$ rebase.patched -v -arch x86_64 -low_address 0x7FFF321AC000 dyld
x86_64 0x7FFF5FC00000 -> 0x7FFF321AC000 dyld
Once you have performed the rebasing you can test the new linker by patching the string /usr/lib/dyld into /tmp/dyld inside an arbitrary binary. I did this to the system's sleep binary which can then be used to test the rebased dynamic linker.
$ gdb /tmp/sleep
GNU gdb 6.3.50-20050815 (Apple version gdb-1510) (Wed Sep 22 02:45:02 UTC 2010)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin"...Reading symbols for shared libraries .. done
(gdb) run 1000
Starting program: /private/tmp/sleep 1000
Reading symbols for shared libraries +. done
^C
Program received signal SIGINT, Interrupt.
0x00007fff872e3fca in __semwait_signal ()
(gdb) info shared
The DYLD shared library state has been initialized from the executable's shared library information.
All symbols should be present, but the addresses of some symbols may move when the program is executed,
as DYLD may relocate library load addresses if necessary.
Requested State Current State
Num Basename Type Address Reason | | Source
| | | | | | | |
1 dyld - 0x7fff321ac000 dyld Y Y /private/tmp/dyld at 0x7fff321ac000 (offset 0x0) with prefix "__dyld_"
2 sleep - 0x100000000 exec Y Y /private/tmp/sleep (offset 0x0)
3 libSystem.B.dylib - 0x7fff872a8000 dyld Y Y /usr/lib/libSystem.B.dylib at 0x7fff872a8000 (offset 0x7fff872a8000)
(commpage objfile is) /usr/lib/libSystem.B.dylib[LC_SEGMENT.__DATA.__commpage]
4 libmathCommon.A.dylib - 0x7fff82edc000 dyld Y Y /usr/lib/system/libmathCommon.A.dylib at 0x7fff82edc000 (offset 0x7fff82edc000)
(gdb)
As you can see the dynamic linker works and is rebased. You should try the same with -arch i386 in order to ensure that you did not break the dynamic linker. Afterwards you can install it in the system.
HOWEVER KEEP IN MIND THAT YOU DO THIS AT YOUR OWN RISK. IT MIGHT BREAK YOUR WHOLE SYSTEM.
$ sudo cp /tmp/dyld /usr/lib/dyld
After that the normal system seems to work without obvious problems. However I noticed that GDB has some problems afterwards. Debugging a binary seems broken. However attaching to a running process is still possible without any problems. Maybe one of the Mac OS X wizards can enlight us.
(gdb) run 1000
Starting program: /bin/sleep 1000
Unable to find Mach task port for process-id 20964: (os/kern) failure (0x5).
If you have any comments about this you can reach me at
stefan.esser@sektioneins.de. However any mail asking me for help because you ignored the warning and toasted your Mac OS X installation will go to the trashcan.