PHP De-Obfuscation

In den letzten Tagen hatte ich viel mit obfusciertem PHP-Quellcode (Frameworks & Schadcode) zu tun, der zumeist über einen langen String definiert war und dann mittels einfacher eval()-Funktion erst zur Laufzeit das ein oder andere dunkle Geheimnis preisgab.

Das erste Hindernis besteht darin, diesen String in lesbaren Quellcode zu formatieren. Nach kurzer Recherche im Web und ein paar Experimenten hatte ich folgendes kleine PHP-Script:

ob_start();
eval("?" . ">" . base64_decode( $code) . "<" . "?php");
$output = ob_get_contents();
ob_end_clean();
echo $output;

1. Die Funktion ob_start() aktiviert die Pufferung von Ausgaben ein. Während die Ausgabe-Pufferung aktiv ist, erfolgt keine Ausgabe an den Client. Stattdessen werden alle Ausgaben in einem internen Puffer gespeichert.

2. Die Funktion eval() wertet einen String als PHP-Code aus. Sie ist nützlich, wenn man bspw. PHP-Code aus einer Datenbank lädt. Für dieses Experiment jedoch wäre sie ungeeignet, da der PHP-Quellcode direkt ausgeführt würde. Wenn der String mit „?>“ (PHP endet) beginnt, wird der Code nicht ausgeführt und man hat eine saubere Ausgabe. Die Funktion base64_decode() war nötig, da der Code base64-kodiert war.

3. Mit der Funktion ob_get_contents() wird der Inhalt des internen Puffers in eine String-Variable kopiert. Die String-Variable enthält sämtliche Ausgaben seit dem Befehl ob_start().

4. Die Funktion ob_end_clean() löscht daraufhin den internen Ausgabepuffer und beendet das Outputbuffering.

5. Der Quellcode steht u.U immer noch in einer Zeile ist jedoch lesbar. Er kann nun angesehen und mit einer leistungsstarken IDE formatiert werden.

Im Quellcode der gegebenen PHP-Datei sind die Texte ASCII-codiert und die Strings deshalb nicht lesbar. Alle Zeichen wurden mit dem entsprechenden ASCII-Code abgebildet (z.B.: „\x73“). Das berechnen des Quellcodes mit eval() hilft hier nur soweit man während der Laufzeit noch auf diese Strings lesbar zugreifen kann. Möchte man jedoch schon den Quellcode lesen können, hilft die Funktion str_replace() weiter:

$output = str_replace("\\x73", "s", $input);

Der doppelte Backslash „\\“ ist nötig, um den Backslash selber zu escapen.

Um das an dieser Stelle zu beschleunigen:

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

$ascii_char = array(
" ", "!", '"', "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
"@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\", "]", "^", "_", "`", "a",
"b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r",
"s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~"
);

$output = str_replace($ascii_hex, $ascii_char, $input);

So hat man schnell den lesbaren Text /PHP-Quellcode.

Sicherlich ist das nicht die eleganteste Lösung und ich lasse mich gerne von schöneren überzeugen!

Ein weiterer Teil der Strings ist wieder base64-kodiert, wie bspw.:

$output = base64_decode("aHR0cDovL3d3dy5kaXJlY3Qtd2Vic29sdXRpb25zLmRlL2Jsb2cvd2ViZGVzaWduL3BocC1kZS1vYmZ1c2NhdGlvbi10ZWlsLTMuaHRtbA==");

Um dieses Problem zu lösen, hilft die PHP-Funktion preg_replace_callback():

function decode($erg) {
    return '"' . base64_decode($erg[1]) . '"';
}
$output = preg_replace_callback("/base64_decode\('([a-z0-9+=]*)'\)/i", 'decode', $input);