When cleaning websites, one of the most complicated parts of our job is ensuring we find all backdoors. Most of the time, attackers inject code into different locations to increase the chances of reinfecting the site and maintaining access for as long as possible. Our research finds that in 67% of the websites we clean, there is at least one backdoor variant.
Although we have hundreds of posts on backdoors and their effects, today we want to discuss a few techniques and provide in-depth technical knowledge on how to decode an advanced piece of malware.
This particular infection isn’t new but over the past few months, we have seen an increase in attacks against WordPress & Joomla! using a related variant.
The Malware
After compromising a website, attackers may inject backdoors, web shells, add bogus admin users, and more. A very common characteristic is that either one or more of the following techniques are employed to hide their code – encoding, encryption, and obfuscation.
In the following snippet, attackers added pretty much all three techniques. Let’s go step-by-step through the process of decoding it.
Original malware snippet (truncated)
Simplifying the Code
When decoding, it’s very important to have a logical picture of the code. To help with that, we used PHP Beautifier which transforms all the previous code into this:
Although the code is still unreadable, it now has a logic programming structure that allows us to proceed.
The attackers started by declaring a variable called $hc7e1d20 that doesn’t actually have any purpose. From our experience, the number assigned to the variable (406) might be a way for attackers to identify malware variations of the same kind.
<?php $hc7e1d20 = 406; $GLOBALS['be10eb436'] = Array(); global $be10eb436; $be10eb436 = $GLOBALS;
Throughout the code, we find several global variables which are not necessarily needed to understand the code. We’ll eventually get rid of them.
The malware basically relies on character/string manipulation techniques where attackers place hexadecimal characters into a variable and then assemble them into different variables.
The first one is this:
${"\x47\x4c\x4fB\x41\x4c\x53"}['tbb6a'] ="\x2d\x3d\x3e\x35\x4e\x73\x7d\x4f\x65\x77\x59\x41\x5f\x70\x6a \x69\x23\x3b\x51\x21\x57\x74\x4b\x46\xd\x5b\x2b\x20\x38\x76\x68\x2e\x3f \x44\x4d\x34\x67\x6f\x6b\x7c\x64\x7a\x31\x24\x5c\x4c\x40\x3c\x28\x5a\x79 \x2a\x58\x78\x7e\x6c\x63\x43\x71\x49\x33\x47\x54\x36\x53\x75\x27\x5e\x29 \x56\x66\x26\x32\x2c\x62\x30\x2f\x6e\xa\x50\x22\x25\x52\x5d\x45\x60\x42 \x48\x39\x61\x37\x7b\x4a\x9\x55\x3a\x6d\x72";
This piece can be easily translated through bash with one the following commands:
$ echo -e "\x47\x4c\x4fB\x41\x4c\x53" GLOBALS $ php -r 'echo "\x47\x4c\x4fB\x41\x4c\x53";' GLOBALS
The value attributed to ${GLOBALS}[‘tbb6a’] uses some special characters that may seem to break the translation, but they are not used by the malware in the end. For now, we can create a simple PHP script that goes through the string and prints its corresponding value.
Search and Replace
With that list and information in hand, we can start playing with the Find & Replace feature.
The first variable:
$be10eb436[$be10eb436['tbb6a'][55] . $be10eb436['tbb6a'][75] . $be10eb436['tbb6a'][8] . $be10eb436['tbb6a'][63]] = $be10eb436['tbb6a'][56] .$be10eb436['tbb6a'][30] . $be10eb436['tbb6a'][97]; $be10eb436['tbb6a'][55] = ‘l’ $be10eb436['tbb6a'][75] = ‘0’ $be10eb436['tbb6a'][8] = ‘e’ $be10eb436['tbb6a'][63]] = ‘6’ $be10eb436['tbb6a'][56] = ‘c’ $be10eb436['tbb6a'][30] = ‘h’ $be10eb436['tbb6a'][97] = ‘r’
All of which basically translates to:
$be10eb436[‘l . ‘0’ . ‘e’ . ‘6’] = ‘c’ . ‘h’ . ‘r’; ---- $be10eb436[‘l0e6’] = ‘chr’;
To make the code even clearer, we can rename ‘be10eb436’ to ‘arr’ as it’s being declared as an array(); and continue translating the characters.
After a few minutes, we reach a specific code and format:
Things look way much clearer now so we can start working with a few more adjustments.
The first few variables below can be easily replaced.
$arr['l0e6'] = ‘chr’; $arr['ac6c24d1'] = ‘ord’; $arr['s8bb921e'] = ‘strlen’;
Once adjusted in their respective places, the first function looks like this:
function l3f5($nd0f2d, $yaf8a49ab) { global $arr; $wb1a = ""; for ($a5be536 = 0; $a5be536 < strlen($nd0f2d);) { for ($jd82720f = 0; $jd82720f < strlen($yaf8a49ab) && $a5be536 < $arr['s8bb921e']($nd0f2d); $jd82720f++,$a5be536++) { $wb1a.= chr(ord($nd0f2d[$a5be536]) ^ ord($yaf8a49ab[$jd82720f])); } } return $wb1a; }
Malicious Intentions
After replacing all functions and variables, we get the following code which can be easily understood.
The points of interest found in the full malicious code are:
- A function called l3f5() is responsible for performing encryption and decryption through bitwise operations (XOR);
- The q057860() function applies two levels of XOR encryption/decryption.
- The first level key is a predefined constant ($w158 = ‘2cef0f87-62fe-4bb9-a1de-4dc009e818ea’;)
- The second level key comes from either POST parameters or from HTTP cookies.
- This function is used to decode encrypted serialized data that attackers pass to the script via cookies or POST parameters.
- The decoded data can contain either:
- executable PHP code, or
- a command to provide information about the backdoor and PHP versions.
Conclusion
There are many different types of malware out there and not all of them execute commands directly from the script. As evident in this example, there are other pieces of code that receive arbitrary commands through $_POST or $_COOKIE requests, which are not logged by default by the web server.
In order to prevent the website from getting infected, we highly recommend implementing security measures like file integrity monitoring and a website application firewall. It is advisable to constantly monitor your logs for unexpected behavior.