Skip to content

Latest commit

 

History

History

Extensible backdoor 3.0-1

Another entry in a seemingly endless series of backdoors that can be extended remotely. If only someone would actually download a plugin or two to my honey pot.

Origin

IP Address 52.64.146.190

It's an Amazon AWS IP address. This is the sole and only time that 52.64.146.190 has accessed my web site. This access arrived with a cookie named "59c3080850e28cdb724eafd61e539b81", value "866fd58d77526c1bda8771b5b21d5b11". That's a typical cookie for WSO web shells. The name of the cookie is the MD5 hash of "www.stratigery.com", the hostname in the URL used to access my web site. The value of the cookie is the MD5 hash of "nhzgrf", a common password for WSO web shell instances. A single access with a pre-set WSO password cookie would seem to indicate programmatic access.

Download

The attacker(s) invoked a URL ending in /wordpress/wp-content/plugins/wp-mobile-detector/cache/db.php. This URL has been invoked 274 times since 2017-12-08, never before from 52.64.146.190.

The attacker(s) apparently thought they were invoking a WSO (Web Shell by oRb) web shell. They set stereotypical WSO HTTP parameters named "a" and "p1". Parameter named "a" had a value of "RC", a well-known WSO function that eval's PHP source code that is the value of parameter "p1". Conveniently, HTTP parameter "p1" arrived full of PHP source.

HTTP Cookies

The attackers set two HTTP cookies along with the download.

Cookie Name Cookie value
59c3080850e28cdb724eafd61e539b81 866fd58d77526c1bda8771b5b21d5b11
7a017e28704d15b0d8efe38606806610 7a017e28704d15b0d8efe38606806610

The cookie name "59c3080850e28cdb724eafd61e539b81" is the MD5 hash of the string "www.stratigery.com", the host name the attackers used to in URLs to access my WordPress honey pot. The value of that cookie, "866fd58d77526c1bda8771b5b21d5b11", is the MD5 hash of "nhzgrf", an extremely common password for WSO web shells.

The second cookie, name and value of "7a017e28704d15b0d8efe38606806610", has much more interest. The string "7a017e28704d15b0d8efe38606806610" is the value of this PHP calculation:

md5(md5('www.stratigery.com') . 'www.stratigery.com' . "salt1I*@#31RTds34+543sf");

This is the kind of cookie that the Vigilante Malware Cleaner injected code uses as a second password on WSO instances it finds. It seems likely that the attacker(s) are the Vigilance Committe behind the Malware Cleaner, or obtained the password and URL from the Vigilance Committee.

Deobfuscation

  1. Hand-edit 52.64.146.190XNSIpgOCAecLqsKUhEDM9gAAAAE.vigilante.scans into a1.php, change "eval" to "print"
  2. php a1.php > a2.php
  3. Hand-edit a2.php, change "eval" to "print".
  4. php a2.php > a3.php

a3.php is pretty much the backdoor source code. It's also had all user functions and variables renamed to 6 to 14 character long strings of randomly-selected lowercase ASCII letters. This makes it hard to understand.

I've done a hand-reverse-engineered version with names of variables and functions taken from v1-02, which arrived without the randomly-selected-variable names, and from the internal, deobfuscated, TdsClient code. I had to choose a few variable and function names, as not every function or variable appears in the TdsClient code, or v1-02 code.

Analysis

Three pieces of code are of interest: the dropper, the backdoor, and an initial set of plugins kept encoded, internally to the backdoor.

Dropper

The code sent to a WSO shell for immediate eval looks like an instance of the Object Oriented Dropper. Previously, my honey pot caught a nearly identical dropper wanting to install a v2-01 instance of this same backdoor. Another interesting coincidence, or does the Vigilance Committee like this dropper?

The dropper uses some reasonably sophisticated code to compose an array of all directory names under Apache's DocumentRoot directory. Looks like it tries to "inject" the extendable backdoor code by creating a randomly-chosen-characters filename with a ".ico" suffix, writing the extendable backdoor code into that file, then adding a PHP include directive in each every index.html or index.php file it can find in the array of dictionaries.

The Backdoor

The hand-reverse-engineered version is very similar to the v2-01 backdoor. Given the goofy, randomly-selected function and variable names, it's pretty hard to sort out exactly how similar it is due to human limitations and fraility.

Differences from v2-01

The object oriented dropper left behind v2-01 backdoor code with variable and function names of 6 to 14 randomly-selected characters, same as for v3-01. I had previously written a PHP cleaner that parsed PHP source code, then pretty-printed it while replacing all variable names with the string variable, function names with the string function, class names with classname, etc etc. The resulting code does not retain the semantics of the original. It probably does not execute correctly, but it does retain the "shape" of the original, obfuscated, PHP code.

Only one block of code makes the v3-01 backdoor different from v2-01 backdoors. This block of code contains a Base64-encoded, serialized block of PHP. After Base64-decoding, and removing the serializing information, I found that the block of PHP contained another layer of obfuscation, similar to the obfuscation used in the backdoor itself. I undid that layer of obfuscation by changed "eval" to "print" and running it, yielding code similar in structure to the extendable backdoor itself, except object oriented.

The v3-01 backdoor Base64-decodes, and deserializes this block of code every time it executes, putting the deserialized executable pieces in its array of plugins. This seems a trifle odd. It carries around code to store and retrieve plugins from its own file, but it also carries an entire PHP class around in Base64-encoded form. Operators can't replace or delete this code as they can the disk-stored plugins can. They lost some functionality when they added this code.

Deserialized Code/built-in plugin

First, yes, "built-in plugin" is a contradiction in terms. Illogically-written software often causes difficulties in terminology for those describing it.

We get to see more of the attacker's software engineering practices in the deserialized code. The backdoor's authors (or integrators) did not obscure the deserialized code any more than PHP is obscure in the first place. We get to see their function and variable naming conventions, and code formatting practices. All of these factors got obscured in the backdoor itself, via code reformatting for obfuscation, and renaming variables and functions for obfuscation.

The deserialized code seems very consistent. private-scope function names begin with an "_" underscore. Public scope functions do not. All functions have snake_case_names. Variable names are all lower-case snake_case. Function names describe an action: _query, get_request_ip, can_process_request, and for the most part, only manipulate instance variables: they have no arguments. A few exceptions exist, but these are almost all private functions, and borrowed from the backdoor's main body of code _decrypt, _decrypt_phrase both take a key and a string of encrypted. The code is indented consistently, with 4 ASCII space characters per level. The code has MS-DOS style 2-byte carriage-return/linefeed end-of-line marks, indicating perhaps a pass through, or written on a Windows text editor. All-in-all, not a bad effort.

The deserialized code consists of a name, "tds", and a definition of a PHP class, class TdsClient. After the class definition, there's about 20 lines of code. It defines another UUID-format key, assigns yet another string in Base64-encoded-format, and calls a few member functions on an instance of class TdsClient. One of the member functions invoked checks to see if an HTTP parameter named "ak" lexically equates to the UUID-format key. This authorization check is idential to that made in the main body of the backdoor. I guess that "ak" abbriviates "Authorization Key" or "Access Key", but why would you check it in the main body of the backdoor, and again if you invoke the plugin named "tds"? In any case, class TdsClient has a "check" function, that prints a block of maybe XML (no DTD exists) where if you call the "tds" plugin, the authorization key you sent in matches, and you sent another HTTP parameters "sa=check", you get

<tds>
8d513cbb-4054-45b6-a276-b668f435ab9a
</tds>

Not exactly valid HTML - no such <tds> tag has ever been specified, but having the code give "access key" back lets you check if it installed correctly, and PHP works.

TdsClient can do so much more. If, due to HTTP parameters, it fails try_process_check(), it does on to call can_process_request(). This is a very odd method.

public function can_process_request()
{
    $tds_config = $this->_get_config();

    eval("function is_acceptable_tds_request(){\n" . $tds_config["tds_filter"] . "\n}");

    if (function_exists("is_acceptable_tds_request"))
    {
        if (!is_acceptable_tds_request())
        {
            return FALSE;
        }
    }

    return TRUE;
}

A strange way of defining a function: retrieving the source code from an embedded, encoded string, constructing PHP source code for a function out of that, and eval'ing.

$this->_get_config(); decodes a block of Base64-encoded bytes using the same Xor-encoding that the dropper used, but with two kyes: once with the string "tmnyrbtvchx5bny", and once with the UUID-like "access key" string "8d513cbb-4054-45b6-a276-b668f435ab9a". It's double-Xor-encoded. I have cut out the code that does this double-Xor, and used it to create a var_dump() of this internal-to-TdsClient config. Good lord, this is convoluted.

My take on !is_acceptable_tds_request() is that it's supposed to weed out requests that aren't made from a human-driven browser. It returns false if the HTTP method isn't GET, if the request doesn't have an HTTP "Accept-Language" header, if the request doesn't have a "Referer" header from the same hostname that TdsClient runs on, or if the User-Agent string indicates a bot, crawler, spider, or search engine.

The URL invoked needs to not contain these substrings:

.css
.swf
.ashx
.docx
.doc
.xls
.xlsx
.xml
.jpg
.pdf
.png
.gif
.ico
.js
.txt
ajax
cron.php
wp-login.php
/wp-includes/
/wp-admin
/admin/
/wp-content/
/administrator/
phpmyadmin (any case)
xmlrpc.php
/feed/

This eliminates almost all image file suffixes, Microsoft document formats, a lot of WordPress PHP files, and a few others.

If the invoking information does seem to come from a human-driven browser, TdsClient calls a method process_request(). This method appears to compose another large HTTP request with most or all of the HTTP headers of the request-being-handled, along with the UUID-style "access key" and a magic word "yor8afx3" (or maybe "799csfik") it calls "route", then send it off to http://188.40.16.29:80/login.php That looks like a disposable hosting company IP address. The IP address of 188.40.16.29 appears in the TdsClient internal config. The TdsClient code uses a default IP address of 77.87.193.14 if no IP address appears in the internal config.

77.87.193.14

This is the default IP address TdsClient might use to send an HTTP request's metadata to.

77.87.193.14 has a DNS name of ef485.mirohost.net

ef485.mirohost.net has an A record of 77.87.193.14

role:           Mirohost Network Coordination Center
address:        50 Gaydara str., Kyiv, Ukraine, 01033
created:        2011-10-05T13:23:46Z
last-modified:  2018-02-15T19:14:00Z
route:          77.87.193.0/24
descr:          Internet Invest Ltd.
descr:          Web hosting, datacenter and domain names registration in Ukraine
descr:          Hosting part
descr:          Kiev, Ukraine
org:            ORG-IIL11-RIPE
origin:         AS28907
created:        2013-12-08T22:56:02Z
last-modified:  2013-12-08T22:56:02Z

188.40.16.29

This is the IP address contained in TdsClient's encoded internal config.

188.40.16.29 → static.29.16.40.188.clients.your-server.de → 188.40.16.29

188.40.16.29 owned by Euro-hosting giant Hetzner

your-server.de appears to be one of Hetzner's internal-use domains.

Analysis Summary

Due to the complex decoding, I'm recapping the analysis:

  1. Dropper with encoded extendable backdoor sent to WSO web shell, RC action
  2. Dropper decodes extendable backdoor, finds a suitable directory, writes backdoor code into a ".ico" suffix file. Modifies index.hmtl and index.php and .htaccess files to include the ".ico" suffix file.
  3. Upon invocation of basically any URL inside the compromised WordPress site, the backdoor code gets executed.
  4. Backdoor code decodes an extra piece of code, TdsClient, which can phone home lots of metadata from the WordPess invocation to a URL at 188.40.16.29, or maybe 77.87.193.14 if I'm misreading the code.
  5. When invoked with the correct HTTP parameters, the backdoor code can do the usual things that an extendable backdoor can do.

Around the Web