Decoding PHP Hack Scripts and Root Shells

Several years ago, I wrote posts about this, and it’s still relevant. Sites still get hacked and still have shells installed onto them, and they are still shrouded the same way. Here are a couple ways to decode them.

Decoding gzinflate base64_decode

Some Drupal and WordPress themes add a copyright notice using a technique also seen in hack scripts. They take the PHP code, and the base64 encode it, and gzip it. (Hack scripts also eval the code.) Below is a snippet of code that will decode the encoded data, and then save it out as ‘some-script.php’.

To use it, first find the code that looks like “eval(gzinflate(base64_decode(‘ponbZ2smNT3Fy6…..W+ebF’)));”, and replace the eval with “$script = (gzinflate” and so on. Then, you run this code on $script.

while(preg_match('/^eval\(gzinflate\(base64_decode\(/', $script)) {
    echo '*bingo*';
    $s = substr($script, 30);
    $s = substr($s, 0, -5);
    $script = gzinflate(base64_decode($s));
}
file_put_contents('some-script.php', $script);

A generic decoder: eval gzunzip base64_encode str_rot13

I got hacked (more than once) and they installed a backdoor php shell. It sucked (and I must suck for allowing it to happen… but anyway) and the upshot of it was, after the cleanup, I wanted to see what the hack was. So, here’s a snippet of code that you can use to decrypt these nasties. To use it, paste the function definition for “dc” below into the hack script. Then replace that first instance of “eval” with “dc”, which calls this function. Then run the script. The output will be the source of the script. View source to read it formatted correctly – and use Save As… to save it. You may need to edit the output to get it working.

function dc($s) {
  // matches strings
  if (preg_match('/^\'(.+)\'$/s', $s, $matches)) {
    return $matches[1];
  }

  // matches function calls
  if (preg_match('/^(\\?><\\? |)([a-z0-9_]+)\\((.+)\\)(; \\?><\\?|)$/s', $s, $matches)) {
    $func = $matches[2];
    $data = $matches[3];
    switch($func) {
      case 'eval':
        $newdata = dc($data);
        echo dc($newdata);
      break;
      case 'gzinflate':
        return gzinflate(dc($data));
      case 'str_rot13':
        return str_rot13(dc($data));
      case 'base64_decode':
        return base64_decode(dc($data));
      default:
        echo 'do not know ' . $func;
        echo $data;
      break;
    }
  }
  else {
    echo $s;
  }
}

What the function does is evaluate the base64_decode, str_rot13, and gzinflate calls, but skips using eval. It handles that thing they do, adding ?><? at the ends of the string.

This may not be enough, because the latest scripts I’ve seen randomize function names, and also refactor the code into functions, making it really hard to read.

Leave a Reply