Evolution of a PHP backdoor evasion

1 comments

I came across a rather clever PHP obfuscation technique today that I thought would be worth sharing.  Every web server administrator knows it's a constant battle to keep the bad guys out and the cat and mouse game continues to escalate.  For example:

Back in the good 'ol days, attackers would use simple code that could be easily detected:

$backdoor = phpinfo();
Antivirus and antimalware scanners caught on quickly and detected these infections with relative ease.  So, the attackers upped the ante:
eval(gzinflate(base64_decode("U0lKTM5Oyc8vUrBVKMgoyMxLy9fQtAYA")));
By obfuscating the payload it became a little more difficult to analyze.  So, the good guys started looking for eval, gzinflate, and base64_encode statements.  In response, the attackers obfuscated those signatures as well:
$g = strrev("etalf"."niz"."g");
$b = "bas"."e64"."_de"."code";
eval($g($b("U0lKTM5Oyc8vUrBVKMgoyMxLy9fQtAYA")));
By breaking up the decoding mechanism, signature-based detection was much more difficult.  Still, the critical eval component was a glaring red flag, so the attackers hid that as well:
preg_replace("/rAnDOm745/e","phpinfo()","rAnDOm745");
By using the eval modifier of the preg_replace function (that "e" on the end of the regexp statement), a string could be substituted and evaluated as PHP code.  Clever, but the attacker lost their payload obfuscation in the process.  So, as a final knockout punch, the attacker obfuscates the payload as well:
preg_replace("/rAnDOm745/e","Vu3HaJMsade30qrvbeMEw9"^"r\x17R\x2b\x0A\x2e\x22\x1c\x13DX\x13\x40\x19\x02\x1f\x0c\x03\x22m\x5e\x02","rAnDOm745");
I know what you're thinking...that's a bizarre mishmash of nonsense.  At first glance it looks like base64 encoding (alphanumeric chars & equal signs) on the left and hexadecimal encoding (backslash x then two chars --> \x##) on the right.  Both guesses would be wrong.  Go ahead and plop it into a php file and see what happens...

Yeah, phpinfo() ran.  How?  It turns out, PHP strings have some really obscure bitwise operators.  If you put a caret (^) between two PHP strings (double-quoted, not single quoted), you can perform a bitwise XOR.  A what?  Let me explain.  Let's say you have a "V" char.  This can be represented in binary as 01010110.  Yeah, so what?  Well, let's say you have a "r" char.  This can be represented in binary as 01110010.  So what, again, right?  Well, let's put these binary values on top of each other:

01010110
01110010

Now, for each column, if the values on top and bottom match, set a 0.  If they don't match, set a 1.  So you end up with something like:

01010110
01110010
__________
00100100

Ok, now what?  Well, take that final result and convert it from binary back into ASCII.  What do you get?  A "$" char.  Weird!  I know.  It turns out, if you echo that "nonsense" in the preg_replace replacement field:
echo "Vu3HaJMsade30qrvbeMEw9"^"r\x17R\x2b\x0A\x2e\x22\x1c\x13DX\x13\x40\x19\x02\x1f\x0c\x03\x22m\x5e\x02"
you get
$backdoor = phpinfo();
Pretty wild, huh?  Now if you put all the pieces together, preg_replace is taking a temporary string and replacing it with a bitwise XOR string and then evaluates that string as PHP.

Of course the actual payload of the "Web Shell by Orb" (WSO) backdoor I found on the server was much more complex than a simple phpinfo call.  Ughh...I hate spammers.

...hope this tutorial didn't bore too many of you but it took me a few hours to break this apart so I thought it might save someone else the headache.

P.S.  It's going to be pretty tough to look for a signature of this kind of attack.  The find/replace string can be totally randomized and the spacing around the two strings and the caret can be random as well (..." ^    "...).  Also, in bitwise XOR, every char can be represented in a number of ways.  So:
"M"^"\x22" = o
"s"^"\x1c" = o
Your best bet is to disable eval and disable //e altogether.