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.
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.
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.
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.
- Hand-edit
52.64.146.190XNSIpgOCAecLqsKUhEDM9gAAAAE.vigilante.scans
intoa1.php
, change "eval" to "print" php a1.php > a2.php
- Hand-edit
a2.php
, change "eval" to "print". 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.
Three pieces of code are of interest: the dropper, the backdoor, and an initial set of plugins kept encoded, internally to the backdoor.
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 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.
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.
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.
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
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.
Due to the complex decoding, I'm recapping the analysis:
- Dropper with encoded extendable backdoor sent to WSO web shell, RC action
- Dropper decodes extendable backdoor, finds a suitable directory,
writes backdoor code into a ".ico" suffix file. Modifies
index.hmtl
andindex.php
and.htaccess
files to include the ".ico" suffix file. - Upon invocation of basically any URL inside the compromised WordPress site, the backdoor code gets executed.
- 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.
- When invoked with the correct HTTP parameters, the backdoor code can do the usual things that an extendable backdoor can do.
- Analysis of a real infection
- Another look at TdsClient
- Pastebin from 2018-01-09, somewhat different from this TdsClient.