On MacOS 10.7 dyld randomization

First article for the blog, let’s talk about something I had in mind for a while.

There has been a lot of talk about the introduced full ASLR on MacOS X Lion, so as soon as I had my hands on the OS I wanted to check which were the changes introduced.

Let’s start from the very beginning, Mach-O. In order to understand what are the differences introduced in Lion, we need to first give a look at a Mach-O built on two different OSes, we will take as a reference Snow Leopard. Let’s build this simple code for test:

1int main()
2{
3    while (1) {}
4    return 0;
5}

If we compile that code on Lion, no specific option passed to gcc, we will notice a difference from the very same code compiled on Snow Leopard. The difference is the presence of the flag MH_PIE (Position Independent):

/usr/include/mach-o/loader.h

1/* When this bit is set, the OS will
2load the main executable at a
3random address. Only used in
4MH_EXECUTE filetypes. */
5
6#define MH_PIE 0x200000

The MH_PIE flag has been sitting there since MacOS 10.5 and the linker on Lion is now defaulting to it.

The funny thing is that with MH_PIE enabled, the image base will always be at a fixed offset from dyld, 32bit or 64bit, no difference (just a bigger displacement).

Let’s give a look at the base addresses for the main executable and dyld on both cases [Lion, SL]:

NOTE: If you execute the binary through gdb you will have to set disable-aslr off (in gdb, before run) in order to enable dyld randomization, but even then you won’t have PIE enabled. That’s why I have added that otherwise inexplicable while (1) {}

Compiled on Lion (64bit), executed on Lion:

10x000000010b397f44 in main ()
2
3(gdb) info shared
4The DYLD shared library state has not yet been initialized.
5Requested State Current State
6Num Basename Type Address Reason | | Source
7|   |        |    |       |      | | |
81 lion  0x10b397000 exec Y Y /tmp/lion at 0x10b397000 (offset 0xb397000)
92 dyld  0x7fff6af97000 dyld Y Y /usr/lib/dyld at 0x7fff6af97000 (offset 0xb397000) with prefix __dyld__;

Compiled on SL (64bit), executed on Lion:

 10x0000000100000f3c in main ()
 2
 3(gdb) info shared
 4
 5The DYLD shared library state has not yet been initialized.
 6Requested State Current State
 7Num Basename Type Address Reason | | Source
 8|   |        |    |       |      | | |
 9
101 snow  0x100000000 exec Y Y /tmp/snow (offset 0x0)
112 dyld  0x7fff66058000 dyld Y Y /usr/lib/dyld at 0x7fff66058000 (offset 0x6458000) with prefix __dyld__;

After hundreds of executions you can notice that dyld will always be at 0x1000 from the image base on 32bit, and 0x400000 on 64bit:

1image base: 0x10b397000
2dyld : 0x7fff6af97000
3
4image base 0xFFFFFFF = 0xb397000
5dyld_base 0xFFFFFFF = 0xaf97000
60xb397000 0xaf97000 = 0x400000

Since image bases are page aligned we can remove 1 byte and a nibble from the address, this leaves us with 2 bytes randomization on dyld (in this case 0xaf97). We’re actually working on 64bit! On 32bit we have, uhm, the incredible amount of 1 byte.

1(gdb) info shared
2The DYLD shared library state has not yet been initialized.
3Requested State Current State
4Num Basename Type Address Reason | | Source
5|   |        |    |       |      | | |
61 lion32  0x65000 exec Y Y /tmp/lion32 at 0x65000 (offset 0x64000)
72 dyld  0x8fe64000 dyld Y Y /usr/lib/dyld at 0x8fe64000 (offset 0x64000) with prefix __dyld__;

On Lion the userspace has been changed quite a bit, now there’s no big fat libSystem anymore (there still is but it’s not fat :)), instead there are several different libsystem_something located at /usr/lib/system. This ensures logical separation and randomization (libraries were already randomized in 10.5). Now there is also a libdyld.dylib which has a lot of symbols that were once in /usr/lib/dyld.

The problem with dyld was that it was loaded at a fixed address providing loads of go-go-gadgets for ROP exploitation. Now they say it’s randomized. And several different symbols have been moved to libdyld which is randomized.

1(gdb) info symbol _dyld_image_count
2_dyld_image_count in section LC_SEGMENT.__TEXT.__text of /usr/lib/system/libdyld.dylib
3
4(gdb) info symbol _dyld_get_image_header
5_dyld_get_image_header in section LC_SEGMENT.__TEXT.__text of /usr/lib/system/libdyld.dylib

Funny thing is that /usr/lib/dyld is still working as it was. dlsym() will resolve symbols in libdyld, but if you resolve symbols on the mapped dyld image (e.g. with your own implementation of dlsym()) you’ll see that everything works as it was in the past.

In conclusion, it looks like the big changes introduced in Lion (regarding dyld randomization) were actually a 2 bytes randomization on 64bit and 1 byte randomization on 32bit, always at a fixed offset from the main executable image base (with MH_PIE flag set).

Well, that’s it 🙂 Hope you guys enjoyed and stay tuned!