[Loki-Bot] Stealing Credentials
File Summary
Wiper | |
---|---|
MD5 | 3F4A16B29F2F0532B7CE3E7656799125 |
File Type | PE32 executable |
Driver | |
---|---|
MD5 | 4578b188645f157291b8081faf680a4a |
File Type | PE32 executable |
Loki-Bot | |
---|---|
MD5 | 1d53235d615474053d0520c249adb4d5 |
File Type | PE32 executable |
Analysis Summary
- This sample uses
CVE-2017-11882
which is a buffer overflow in Microsoft Equation Editor (EQNEDT32.EXE) and acts as a downloader.- The downloaded executable uses the process injection technique to inject itself into a process called
vbc.exe
.- It moves itself into a specific folder inside
%AppData%
and then modifies the registry with the folder’s path to establish persistence, it also makes this folder invisible.- It uses
Import By Hash
technique to provide a layer of obfuscation.- It creates a Mutex with a unique name which is the MD5 hash of
MachineGuid
value to avoid reinfecting the machine.- It steals a huge amount of data that victim’s machine stores such as browser credentials, Windows credentials …. etc
- It sends extra data such as OS version, Username, Machine Name ……
Delivery
LokiBot can be delivered via a phishing email that contains a malicious document attachment.
Behavior Analysis
When this document is launched, it will download an executable called regasm.exe
from thdyrusschine2mapanxmenischangednethnbc.ydns[.]eu
Then it will save at C:\Users\Public
and rename it to vbc.exe
and execute it.
Then, It sends some data to the C&C domain http://begadi[.]ga/
. It looks like the username, PC-name of the victim’s machine, and other encrypted data.
Unfortunately, the C2 domain is down but I still could explain some details inside.
Stage 1: Downloader
This sample uses CVE-2017-11882 which is a buffer overflow in Microsoft Equation Editor (EQNEDT32.EXE)
and acts as a downloader. Let’s represent a little explanation of buffer overflow.
When a normal function is called, the EIP
of the next instruction is saved into the stack so after returning to the main function, the program knows where to go next.
This sample uses this technique to change the EIP
to redirect the flow of execution to another way.
This is a simple example of BOF (The program’s flow is redirected to 0x00443015
).
We can add a key named EQNEDT32.EXE
at Image File Execution Options
in the registry to intercept calls to an executable for debugging.
Here on our debugger, it redirects the flow to execute URLDownloadToFileW
function to download the LokiBot and then execute it.
Stage 2: Process Injection
Now, it is packed but we can unpack it easily as it writes the PE file of the unpacked payload into memory. So we just need a breakpoint at WriteProcessMemory
Stage 3: Loki-Bot
Import By Hash
First, it executes a function labeled GetCommandLine
that returns the full command line of the executable. Then, It gets some arguments that this command line has been using GetCommandLineToArgV
.
It doesn’t call these APIs directly but uses Import By Hash
technique so let’s dig a little inside GetCommandLine
. Here it passes 4 arguments to a function labeled Get_DLL_API_From_Hash_Index
. We focus on the first 2 arguments.
Argument | Description |
---|---|
Arg 1 | Index of DLL that contains the function (0 in this example) |
Arg 2 | Hash of API to be called (0x0EEF0D05E ) |
Now let’s dig inside this function. Here it passes the hash and index inside another function labeled Get_API_From_Hash
. Then It calls 2 functions labeled Get_DLL_From_Index
& Search_API_From_Hash
Briefly, It gets the required DLL, hashes every API inside it using its name, and then compares between every hash and desired hash. We will explain that in detail.
Let’s dig inside Get_DLL_From_Index
. Here it builds an array of DLLs dynamically and I noticed that it leaves 2 elements at the beginning of the array.
Here it passes a hash to function labels Get_DLL_By_Hash
, so It gets the first 2 elements by hashing technique. In this case, it will pass 0x0F96AF9CE
to get KERNEL32.dll
.
Now, let’s dig more inside Get_DLL_By_Hash
. First, It gets the address of PEB (Process Environment Block). Then, it moves to offset 0xC
that is called _PEB_LDR_DATA
structure. Finally, it gets the DLL name from a structure called InLoadOrderModuleList
.
The Process Environment Block (PEB) is a user-mode data structure that can be used by applications (and by extend by malware) to get information such as the list of loaded modules, process startup arguments, heap address, check whether the program is being debugged, find image base address of imported DLLs,…etc
For more information here
Here, It loops through all loaded DLLs inside the executable, hashes them and compares them with the passed hash (0x0F96AF9CE), passes DLL name and its length to function labeled Calculate_Hash
, and compares the result with 0x0F96AF9CE
Now, it has KERNEL32.dll
address. It passes 2 arguments to Search_API_From_Hash
.
Argument | Description |
---|---|
Arg 1 | DLL DOS HEADER (DLL Address) |
Arg 2 | Hash of API to be called (0x0EEF0D05E) |
According to PE structure, It gets the offset of Export Table
of DLL by adding 0x3C (e_lfanew offset) and 0x78 (pointer to export table offset) to the DOS HEADER
address.
Export_Table = DOS_HEADER_Address + 0x3C + 0x78
Then, It uses Calculate_Hash
function again to get all API hashes and then compares them with the desired API hash 0x0EEF0D05E
.
Creating Mutex
As we know, lots of Malwares create a Mutex for the first time on the victim’s machine to avoid reinfecting it again. The Mutex name is unique from one machine to another that depends on MachineGuid
value.
Let’s have more details. It executes a function labeled Get_Mutex_Name
at 0x413982
that returns 48 bytes of MD5-hash value of MachineGuid
in Unicode form. Then creates a Mutex using CreateMutexW
.
If this Mutex already exists, the malware terminates itself.
Now, lets examine Get_Mutex_Name
function. It executes function at 0x413DA0
labeled Get_MachineGuid_Hash_unicode
.
Querying Registry
Inside, it executes another function labeled Reg_Open_Query_Close_Key
that opens, queries, closes a key in registry HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography
that has the MachineGuid
value.
More deeper, It uses RegOpenKeyExA
, RegQueryValueExA
, RegCloseKey
.
Generating Hash
Now, It passes the MachineGuid
value and its length to a function labeled Get_MachineGuid_MD5_Hash
at 0x4065D2
to generate the hash value.
First, It calls CryptAcquireContextW
to acquire a handle to a particular key container within a particular cryptographic service provider (CSP).
In Microsoft Windows, a Cryptographic Service Provider (CSP) is a software library that implements the Microsoft CryptoAPI.
Then, it creates an MD5-hashing handle using CryptCreateHash
that will be used in CryptHashData
to hash data.
Finally, it retrieves the actual MD5-hash using CryptGetHashParam
.
Converting To UTF-8
After getting the hash value, it calls convert_Ascii_2_UTF8
at 0x4065E4
that converts the hash value to UTF-8 form
UTF-8 is a variable-width character encoding that represents a character in 8 bits (2 bytes). Unlike ASCII that uses 1 byte only.
Here, it uses MultiByteToWideChar
to convert ASCII to UTF-8.
Final Name
Finally, It gets 48 bytes only of the generated MD5-hash.
mov esi, [Hash] |
mov ecx, 0xC | ===> memcpy(Dst, Hash, 48)
rep movsd |
Stealing Data
Now, the malware has successfully checked for initialization data and created its Mutex, it is time to start doing some bad things. This malware steals a huge amount of data such as browser credentials, Windows credentials …. etc
As shown, each function is responsible for stealing specific application credentials. It passes the function address and its ID to a labeled function execute
.
This is a list of some applications that Loki-Bot steals:
Application | ||||||
---|---|---|---|---|---|---|
Mozilla Firefox | BlackHawk | QupZilla | SuperPutty | FTPNow | Cyberduck | NETFile |
Comodo IceDragon | Lunascape | Internet Explorer | FTPShell | Xftp | fullsync | GoFTP |
Safari | Comodo Dragon | Cyberfox | NppFTP | JaSFtp | LinasFTP | ALFTP |
K-Meleon | OperaOLD | PaleMoon | MyFTP | EasyFTP | FileZilla | DeluxeFTP |
SeaMonkey | OperaNEW | Waterfox | FTPBox | SftpNetDrive | StaffFTP | FTPGetter |
Flock | QtWeb | Pidgin | SherrodFTP | AbleFtp | BlazeFtp | WS_FTP |
ExpandDrive | Steed | FlashFXP | NovaFTP | NetDrive | SmartFTP | BitviseSSH |
Google Chrome | Outlook | 1Password |
I tried on Mozilla Firefox
only so let’s dig into it.
Getting Version
First, it retrieves the Firefox version from the registry using a function labeled Get_Reg_Value0
.
According to this article The Secrets of Firefox Credentials, credentials are stored using different methods depending on Firefox Version. So the malware needs to know its version to get the stored credentials.
Then, it checks for the architecture that Firefox is running on by searching for x64
using StrStrW
, Here, I’m using Windows (32-bit) version.
And now, it uses a function labeled Concatenate_With
to concatenate strings together so here it gets another path inside the registry.
Then, It retrieves a value called Install Directory
to know where firefox is installed using Get_Reg_Value1
.
Getting APIs
After that, it calls a function labeled Get_NSS_APIs
that will be used to load NSS3.DLL
. Let’s dig inside
NSS3.DLL
provides a complete open-source implementation of the crypto libraries used by Firefox. They are used to decrypt passwords stored in Mozilla-based browsers
First, it adds the Firefox Install Directory to the system’s PATH
using GetEnvironmentVariable
and SetEnvironmentVariable
Then, it gets the full path of NSS3.DLL
by concatenating its name and Firefox path using Concatenate_With
and checks if it exists using PathFileExistsW
. Finally, it loads this DLL using LoadLibraryW
.
Now, the malware calls GetProcAddress
to get the addresses of the following APIs:
NSS_Init PK11_GetInternalKeySlot
NSS_Shutdown PK11_CheckUserPassword
PK11_FreeSlot PK11_Authenticate
PK11SDR_Decrypt
These APIs will be used later to decrypt stored credentials.
Getting Profile Path
The malware has all the needed APIs to decrypt credentials, it will extract them from Firefox’s database (Profiles). Let’s dig into Get_Profile_INI_Credentials
.
It gets %APPDATA%
path and then it builds an array of paths/filenames which are related to the profile locations of several different types of Mozilla-based software.
Profile paths | |
---|---|
%s\Mozilla\Firefox\profiles.ini | %s\Flock\Browser\profiles.ini |
%s\Mozilla\Firefox\Profiles\%s | %s\Flock\Browser\Profiles\%s |
%s\Mozilla\SeaMonkey\profiles.ini | %s\Thunderbird\profiles.ini |
%s\Mozilla\SeaMonkey\Profiles\%s | %s\Thunderbird\Profiles\%s |
%s\K-Meleon\profiles.ini | %s\Comodo\IceDragon\profiles.ini |
%s\K-Meleon\%s | %s\Comodo\IceDragon\Profiles\%s |
%s\NETGATE Technologies\BlackHawk\profiles.ini | %s\Postbox\profiles.ini |
%s\NETGATE Technologies\BlackHawk\Profiles\%s | %s\Postbox\Profiles\%s |
%s\8pecxstudios\Cyberfox\profiles.ini | %s\Moonchild Productions\Pale Moon\profiles.ini |
%s\8pecxstudios\Cyberfox\Profiles\%s | %s\Moonchild Productions\Pale Moon\Profiles\% |
%s\FossaMail\profiles.ini | |
%s\FossaMail\Profiles\%s |
Here, it will deals with %s\\Mozilla\\Firefox\\profiles.ini
, %s\\Mozilla\\Firefox\\Profiles\\%s
It calls Concatenate_With
to concatenate %APPDATA%
path and profiles.ini
. Then it will check if it exists using PathFileExistsW
Each profile is actually a folder that contains all credentials inside, so the malware will search for the profile path using the initialization file ini
.
It starts with Profile0
and then passes 3 arguments to Get_INI_ProfileName
that will use GetPrivateProfileStringW
to retrieve the path of profile0.
Argument | Description |
---|---|
arg1 | ini file |
arg2 | Section to be read from |
arg3 | Key name which retrieves a value from |
Then, it will concatenate the result with the firefox Installation Directory to get the full path of the profile.
Getting Browser Credentials
It’s time to get credentials. The malware passes the profile full path to a function labeled Get_ALL_Credentials
at 0x408FF4
. Let’s go deeper inside.
It will search for 2 files and check if they exist (Depending on Firefox Version) as they contain encrypted credentials:
signons.sqlite
logins.json
- Starting in Firefox 32.0, signons.sqlite is no longer used and the file logins.json is used instead.
- The primary difference is the malware makes a
SQL query
first assignons.sqlite
is actually a database. Unlikelogins.json
, it parses its contents and decrypts the credentials directly.
It passes the full path of logins.json
file to another function labeled Get_LoginJSON_Credentials
at 0x40A2AB
.
Getting inside, it parses the contents of logins.json
using a function labeled Get_LoginJSON_File_Contents
Now, it will search for encryptedUsername
and encryptedPassword
and passes them to a function labeled Decrypt_Credential
to decrypt.
It will use NSS3 APIs such as:
PK11_GetInternalKeySlot
,PK11_Authenticate
,PK11SDR_Decrypt
,PK11_FreeSlot
Sending Data
After getting all credentials, It executes a function labeled Get_Data_To_Send
at 0x414325
to send the following to C2 server:
- OS Version
- Username - MachineName
- Screen Resolution
- Verify if Local Admin
- Mutex Name
- Stolen Credentials
Let’s represent a little explanation of how to collect these data:
First, it allocates 5000 bytes of memory using Heap_alloc
and starts with OS Version using a function labeled Get_OS_Ver
Go deeper inside, It executes a function called RtlGetVersion
that gets all information about OS such as Major and Minor version, Build Number and CSDVersion
Next, it gets the Username and machine name via Get_UserName
at 0x4143E3
and GetComputerNameW
at 0x414414
.
I noticed that it uses a function labeled Add_Inf_To_Send
to add this information into the buffer that it’s allocated previously.
Now, it adds the domain name by calling a function labeled Get_Domain_Name
For the Malware to obtain the user’s domain, it first obtains a handle to the current thread or process via calls to either:
-
GetCurrentThread, OpenThreadToken
-
GetCurrentProcess, OpenProcessToken
Then, it gets the domain name via LookupAccountSidW
. My machine is not connected to any domain so the return value will be the Computer Name and added to the buffer using Add_Inf_To_Send
.
It gets the current screen resolution using a function labeled Get_Screen_Resolution
that executes:
- GetDesktopWindow
- GetWindowRect
Now it is time to determine whether the current user is a local administrator via a function labeled Is_Local_Admin
. It gets the current username and passes it to NetUserGetInfo
that determines the privilege level of the current user.
Then it compares the 4th element (privilege level) with 2 that represents Administrator
Finally, returns 1 if it’s admin or 0 if not.
After collecting the required data into a buffer, it passes this buffer and its length to a function labeled Send_And_Recv
at 0x414570
that briefly decodes:
- Hostname
- Port Number
- User-Agent
and then, it sends data to the C2 server.
Getting little details. First, it decrypts the hostname using a function labeled Get_C2_Hostname
at 0x414286
Then, it decrypts the port number and User-Agent using Get_Port_Number
, Get_UserAgent
Finally, it sends 2 packets of data using a function labeled SendData
at 0x414136
and 0x414136
:
- HTTP Header
- Required data (Username - Computer Name - Domain Name - Mutex Name - stolen credentials)
I think this enough for Loki-bot
IOCs
Type | Data |
---|---|
Hash | 4de7a80dd324e365cd1e23ebfc0cd844 |
4578b188645f157291b8081faf680a4a | |
1d53235d615474053d0520c249adb4d5 | |
URL | thdyrusschine2mapanxmenischangednethnbc.ydns[.]eu |
C2 Domain | begadi[.]ga |
File | C:\Users\Public\vbc.exe |
References
Finding DLL Name from the Process Environment Block