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!