2009-03-12:

OS X vs Write-What-Where Condition

security:macosx:easy
As my readers may know, for some time now I have access to a MacBook with OS X. Finally I found some time to test the standard exploiting techniques on OS X. I must admit that OS X surprised me positively once or even twice. However, this post is about another time, when the surprise was not positive in terms of security, additionally, it was kinda funny (in a hermetic way) ;>

That time I was checking out how to exploit an application with a write-what-where condition - as I understand some readers might not be familiar with this term, so I'll try to explain.

It's a rather simple thing - due to some specific operation the attacker gains the ability to WRITE certain crafted by him DATA to the SPECIFIED target location (the size of the data depends on the error type, and it can be from 1 byte to infinity (infinity is rather close in a 32-bit memory space ;D)). In the terms of source code, it looks like this:

memcpy(DST, SRC, X);

Where DST is a pointer controlled by the attacker, SRC is an address of a buffer, which content is controlled by the attacker, and X is a case-specific number of bytes to be copied.

One must notice that write-what-where condition is NOT a vulnerability type, rather than that, it's an EFFECT of the vulnerability existence. For example, usually a format bug leads to a write-what-where condition.

In the case of this 'effect' a question arouses: what, and with what, should be overwritten to gain arbitrary (as in: shellcode) code execution?

Usually it's simple - we overwrite an address of some function, which is used by some code like call [X] or jmp [X] (or similar). Thanks to that, when the program will want to call that function using the overwritten pointer, we get EIP redirection the location we've pointed (where the shellcode is located for example). However there is a certain problem - the address as to be absolute (not relative!), and it has to be constant in every copy of the same version of the exploited OS/application (a little exception here is a local privilege escalation type of vuln, where the attacker can calculate/check some addresses even if they are not constant) OR relative but allowing to be transformed to absolute value run-time (which does not happen so often). Finding a good absolute address sometimes becomes quite a challenge - the programmers have setup some ASLRs and other randomizing / memory access control mechanics.

In case of Windows, one usually looks in the EXE file or some DLL for a pointer to some function, in the .data secion (since the IAT is usually read-only). On the other hand, on Linux-based OSes the .got table is normally a good target (this IAT-similar being is read-write). And on OS X?

Let's get back to my case. At first I thought it will be very simple - I've executed my application a couple of times, and observed that the layout of memory (vmmap - a great built-in tool!) is constant (except for some not interesting details) - cool, no ASLR. I've rebooted the machine and checked the layout again - nothing changed. But my inner voice told me "it's going to easy, check on google", and so I did only to find that the OS X ASLR performs the randomization only when some important part of the system is updated, meaning that even if my exploit works now, it won't work in a month, and it definitely won't work at another OS X.

However (lucky! or unlucky - it's a perspective thing) it came out that the position of the main executable (as in, the main MACH-O file) is constant, and does not participate in the ASLR schema. So, I've fed the executable to the disassembler, and started looking for some function-pointer.

No luck. I've found some function pointers, and the import table (0x5210 - 0x5220), however, it was read-only:

==== Non-writable regions for process 595
__PAGEZERO             00000000-00001000 [    4K] ---/--- SM=NUL  .../testproj3
__TEXT                 00001000-00002000 [    4K] r-x/rwx SM=COW  .../testproj3
__LINKEDIT             00005000-00006000 [    4K] r--/rwx SM=COW  .../testproj3


Kinda sad I've also checked out the writable memory regions, in hope of finding something interesting that could be used in my case:

==== Writable regions for process 595
__DATA                 00002000-00003000 [    4K] rw-/rwx SM=COW  .../testproj3
__OBJC                 00003000-00004000 [    4K] rw-/rwx SM=COW  .../testproj3
__IMPORT               00004000-00005000 [    4K] rwx/rwx SM=COW  .../testproj3

Huh. An read/write/execute section! Now that it interesting. I thing the developers of OS or the compiler have messed things up a little here. Let's check what's in that part of memory:

jump_table:4000                 jmp     [NSApplicationMain]
jump_table:4004                 jmp     [puts]
jump_table:4008                 jmp     [memcpy]
jump_table:400C                 jmp     [printf]
...

It's GG.

If someone is not yet smiling, allow me to explain: the imported functions are rarely called directly, rather then that, there is a region with "stubs" like jmp [entry_in_the_import_table], and the "call imported function" thing is made by calling that stub, which jumps to the function using it's address from the IAT - it's done this way on OS X and on the Windows family (it depends on the compiler of course, so I'm talking about the tendency, not some internal OS design stuff). The punchline is - this section can be overwritten!

It's kinda funny imho, all these *nix guys yelling for some time now about the W^X schema (which is a good thing!), and here I find a frequently entered by CPU writable code section.

For certainty I've checked a few more system application, and it was done the same way - the stub section was writable.

To sum up, having a write-what-where condition on OS X, find the jump_table in the main executable, and throw some code there. What code? Two ways at least:

1) we target a certain frequently used / used after triggering the vulnerability function, and switch the jmp [func] with jmp [address_of_a_pointer_to_the_shellcode] (it's a 4 byte patch, just the address operand of the jmp); that pointer to a shellcode can be placed for example at the end of the shellcode or something
2) if we can write a lot, we NOP the whole section, and after the last entry we add some code - a jump to the shellcode (some PUSH address_of_shellcode / RET or somthing) or we put the shellcode itself there, if we have enough room that is (it's like killing a fly with a cannon, effective ;D).

A hint for the Mac devs - make the jump_table read-only, it will be a good step to secure your OS (as everybody knows Mac OS X is a very secure OS... well OK, it was secure on PPC... because few people were interested in it... welcome to x86! ;D)

P.S. A few days ago j00ru, a fellow researcher from Hispasec & team Vexillium, has setup himself a blog (english). Imho it's worth adding it to your RSS if you're interested in security or reverse engineering stuff ;>

Add a comment:

Nick:
URL (optional):
Math captcha: 8 ∗ 3 + 7 =