|Ned Batchelder : Blog | Code | Text | Site|
Ad-hoc decoding a backdoor
» Home : Blog : February 2013
My mom's wordpress site has some malware on it, and she sent it to me for a professional opinion. The mystery file was called wp-rss3.php. Looking at it showed that there was source code being encoded in it, so understanding what it did would require decoding the data. I fired up a Python prompt, and started picking away.
Read the file, and take a quick look to see what structure it has:
>>> wprss3 = open('wp-rss3.php').read()
The file is one long line, so let's split it into lines:
>>> wprss3 = wprss3.replace(' ', '\n').replace(';',';\n').splitlines()
OK, six lines, one of which has the bulk of the data. Let's look at them:
The line 0 is uninteresting, but line 1 defines a string using hex escapes. Lots of our steps here will require getting raw data from a string that is the bulk of what we're looking at. Splitting on double-quotes will get us pieces, one of which is the one we want. Rather than counting pieces to find the right one, we know the one we want will be the longest piece. So we can use max() to find the longest piece:
>>> d = max(wprss3.split('"'), key=len)
One of Python's handy-dandy decoders is 'string_escape' which can turn a string with backslash-x sequences into the correct string:
OK, so $_8b7b is "create_function", a PHP function. Let's see what line 2 gives us:
Interesting, now for the bulk of the data, line 3:
Mentally using our definitions of $_8b7b and $_8b7b1f, this is equivalent to:
$_8b7b1f56 = create_function("", base64_decode("JGs9MTQ...Hop0w=="));
BTW, I did not know that PHP would execute function names in strings as simply as $fnname(), but it does not surprise me.
What's in the base64 data?
>>> d = max(wprss3.split('"'), key=len).decode('base64')
The decoded data is 20k long, and visual inspection shows that the middle is just lots of numbers separated by semicolons. The PHP code is decoding those numbers by XORing them with 143, using them as ASCII codepoints, and evaluating the result. So we want to perform the same decoding to see what source code results:
>>> nums = max(d.split('"'), key=len).split(';')
This finally shows us the source of the backdoor which is executed when the page wp-rss3.php is visited in a browser. I've reformatted it here slightly just to break long lines:
error_reporting(E_ERROR | E_WARNING | E_PARSE);
As you can quickly see, this is a nasty piece of work: it takes commands from the client and will execute PHP code, or SQL, or OS shell commands. I don't understand all the back and forth of the forms handling here, but it doesn't matter, it's clearly intended to let a remote attacker have his way on your machine. Bad stuff.
I wonder if a Wordpress installation could be checked for malware by looking for files that are too high a proportion of base64-encoded text?
I told my mom to remove the file, but I suspect there will be more cleaning up to do...