Evolution of a PHP backdoor evasion
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:
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:
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:
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:
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:eval(gzinflate(base64_decode("U0lKTM5Oyc8vUrBVKMgoyMxLy9fQtAYA")));
$g = strrev("etalf"."niz"."g");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:
$b = "bas"."e64"."_de"."code";
eval($g($b("U0lKTM5Oyc8vUrBVKMgoyMxLy9fQtAYA")));
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" = oYour best bet is to disable eval and disable //e altogether.
"s"^"\x1c" = o
Excellent post. I was scratching my head with the carrot. I found something similar except the payload was a killer.
ReplyDelete