PS: If anyone still have a copy of the SpyEye builder, send me a copy. Thank you !

Executive Summary

SpyEye is a spyware and banking Trojan that was sold as a kit in dark market forums. During its activity period, it has been widely spread and very dangerous. It presented itself as a competitor to the well-know Zeus. This malware is capable of keylogging, form grabbing, ftp and pop3 credential stealing and input fields injection.

Technical Analysis

Static analysis

Characteristics

File type : 32-bit PE Executable

Hash :

  • MD5: 4FCF540BD465177EE03E6D798AD162F0
  • SHA256: 2cc636f4a1e76bd05ddc3c4cbdc8b2b848424d0114

Interesting Strings

Let’s start first by checking the strings we could extract from the sample. We could easily have a good initial idea as to what functionalities this malware provides.

Strings
__CLEANSWEEP__
LdrLoadDll
4C027B88159814E410BB0F23FB0F7DC6
urlmon.dll
shlwapi.dll
wininet.dll
TranslateMessage
user32.dll
HttpSendRequestW
HttpSendRequestA
InternetCloseHandle
PR_Write
send
wsock32.dll
mpr.dll
ws2_32.dll
msvcrt.dll
nspr4.dll
*%s*
__CLEANSWEEP__
%s%s
LdrLoadDll
NtResumeThread
NtEnumerateValueKey
NtVdmControl
NtQueryDirectoryFile
POST %s HTTP/1.1
Host: %s
Connection: close
Content-Type: multipart/form-data; boundary=%s
Content-Length: %d
55377776816118
--%s--
--%s
Content-Disclass: form-data; name="%s"
User-Agent: 
language_id
os_version
%d.%d.%d
tick_time
timezone
local_time
bot_version
bot_guid
keys
func_data
hooked_func
process_name
CONNECT
OPTIONS
TRACE
DELETE
POST
HEAD
LdrLoadDll
%s!%s!%08X
%08X
User
Admin
ONLINE
ccrc=
cpu=
rep=
tid=
stat=
ver=
guid=
KNOCK-COMPLETE
KNOCK-ERROR
KNOCK
LOAD-COMPLETE
LOAD-ERROR
LOAD
COMPLETE
ERROR
data from server is: %s
ACTIVE
FILL
UPDATE_CONFIG
PATH
UPDATE
A%s%s\%s
%s\ntdll.dll
__CLEANSWEEP_RELOADCFG__
__CLEANSWEEP_UNINSTALL__
TASK IS OK
Empty link
Error: Empty report. Unknown error. 0o
Error: Thread is really sloppy
Error: Cannot create thead. 0o
TASK IS OK : 
%s?page=%s
Software\Microsoft\Internet Explorer
Version
nInternet Explorer
Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2
name
class
style
innerHTML
innerText
*call*event*
call_event
onclick
target
_top
href
American Express
Visa
MasterCard
Discover
Fatal error! Runtime exception!   0_0
Unknown answer. 0_o (title of page: "%s")
Cannot find title on Second page. 0_o
Unknown title: "%s". 0_o
*Order Submitted*
*Order Complete*
body
*Order Error*
text
title
*Continue*
table
*Error *
Cannot find submit stuff on first page. 0_o
Cannot find Copies stuff on first page. 0_o
Copies
Cannot find CardExpire stuff on first page. 0_o
CardExpire
Cannot find CardNumber stuff on first page. 0_o
CardNumber
Cannot find CardType stuff on first page. 0_o
CardType
Cannot find Phone stuff on first page. 0_o
Phone
Cannot find EMail stuff on first page. 0_o
EMail
Cannot find BillingCountry stuff on first page. 0_o
select
BillingCountry
Cannot find BillingPostal stuff on first page. 0_o
BillingPostal
Cannot find BillingState stuff on first page. 0_o
BillingState
Cannot find BillingCity stuff on first page. 0_o
BillingCity
Cannot find BillingAddress stuff on first page. 0_o
BillingAddress
Cannot find Company stuff on first page. 0_o
Company
Cannot find Name stuff on first page. 0_o
input
Name
value
Smth wrong with navigate to BILLING-PAGE. 0_o
Smth wrong with navigate to BILLING-PAGE (err code: %d). 0_o
Smth wrong with navigate to REF-PAGE (err code: %d). 0_o
Cannot increase history
link
Cannot detect card type. 0_o
 "(%s")
Smth wrong with input-data. 0_o
4C027B88159814E410BB0F23FB0F7DC6
4C027B88159814E410BB0F23FB0F7DC6
4C027B88159814E410BB0F23FB0F7DC6
Internet Explorer
config.datUT
(UTC%s%2.2f) %s
\BaseNamedObjects\__CLEANSWEEP_REPALREADYSENDED__
@onbeforeunload
%s\ntdll.dll
cleansweep.exe
%s%s
%s\%s
CONFIG
\??\%s:\%s\%s
config.bin
LdrLoadDll
kernel32.dll
ntdll.
NTDLL.

IAT

The number of imported functions is very suspicious. Considering the number of extracted strings and the regularity of the sections, the binary is not packed. Thus, the very low number of imported functions could be an indicator of dynamic API loading which we’ll discuss in the Detailed Specifics section.

Imported Functions
memset
lstrcmpiA
wcscat
wcscpy
strstr
strlen
_strlwr
strcpy

Behavioural analysis

Initial execution

The entry point of the malware basically gets the OS version and exits if it’s older than Windows 2000. Else, a covert execution of the main function is done using EnumTimeFormatsA.

Entrypoint

Dropper Entrypoint

The main function is pretty simple. It makes sure that only one instance of the malware is running on the victim by checking for a Mutex. If it exists, the function exits. Else, It is created and execution continues. It then starts dropping the main payload which is formed of a stage2 executable and a config.bin configuration file (contained in the .rsrc section).

Main function of the dropper

Main function of the dropper


 
The dropping of stage2 executable

Dropping of the executable

The dropping of the configuration file

Dropping of the configuration file

The stage2 executable and the configuration file are dropped in a newly created directory named cleansweep.exe (which is also the name of the executable ) in the root of the primary volume. The filetime of the dropped directory and its file is set to match the creation time of ntdll.dll.

function used to change dropped files filetime

Filetime change

Stage2 Execution

After dropping all the necessary files to the right directory, the stage1 dropper (Or the injected code if successful) runs the stage2 executable named cleansweep.exe . It first checks as usual for the OS version and exits if it’s older than Windows 2000 . Then, it creates a Mutex to make sure that the victim is only infected once (This is important considering that SpyEye is sold as a kit to prevent attackers infecting the same machine). The implant considers two cases, it’s either ran from the standard directory or not. If not, a new executable is downloaded from the C2 named cleansweepupd.exe which serves as an updater for the implant.

Checking if the executable is ran from its default path and branching according to that condition.

Update or normal behaviour branch

Let’s now consider the normal execution flow ie. the implant being ran from its default location. The initial operations of this main function are simple : It first checks if an updater executable exists and deletes it, parses the config file to extract the C2 server and then, being run from the default location, runs the function that contains the primary malicious functionalities (Using process injection discussed later).

Entrypoint of stage2 executable.

Stage 2 Entrypoint

Starting from this function, things start getting a little more complicated. This is because it hides behind it all the capabilities implemented in this implant and because it’s divided to various threads each one taking care of a certain functionality. As usual, It creates a Mutex but it doesn’t check if it already exists. This is because various injected versions could be ran at the same time and the Mutex is only used to prevent further infection. A thread listening for an uninstall Mutex is also set up to make sure the main Mutex is closed in case of uninstall.

A thread listening for an uninstall Mutex to close the main Mutex

Uninstall listener

Then, a persistence function is ran to make sure the malware survives system shutdown (this is to be discussed later).

Persistence function

Persistence function

Having these details set up, the malware then starts an initialisation sequence that is injected in every possible process. This function is so important as it sets the API hooks and then creates another listening thread that:

  • Listens for a configuration reload Mutex, and if it exists, re-parses the config file considering a new version has been downloaded.
  • As usual, listens for for the uninstall Mutex and if it exists, it exits the loop, closes the concerned Mutex and then unregisters the API hooks. You’ll notice this is a recurring pattern for every thread and Mutex.

A function setting all API hooks

API hooking and uninstall listener for unhooking


 
A thread listening for configuration reload or for uninstall mutex

Configuration reload and uninstall unhooking listener

After having the initialisation function injected in every possible process, main C2 communication is finally established. It initially sends victim metadata and identifying information and then launches an infinite loop that processes server commands. This is also made using the same pattern, the C2 communication is ran in a separate thread and an uninstall listener is created to kill that thread in case of uninstall.

C2 communication function and uninstall listener

C2 communication function


 
The generation of a victim unique id and collection of necessary info

Victim ID generation and initial information collection

 
Some C2 commands

A snippet of some C2 commands


And finally, the malware checks if it’s injected inside explorer.exe, if that’s the case, it creates a thread the attempts to inject the initialisation function in new processes and another uninstall listening thread to terminate it.
An infection loop that tries to infect new processes

Infection loop injected inside explorer.exe

Persistence

For persistence, the malware uses a pretty simple technique. It sets itself as a startup executable using the Run registry key.

Registry Auto-start persistence

Adding cleansweep.exe to the Run key

Capabilities
Form grabbing

One of the most important capabilities of this malware is form grabbing. This is implemented through hooking of nearly all the network functions (even firefox’s PR_Write) usually used by browsers to intercept forms as they’re being sent and grab their content.

Keylogging

By hooking the translateMessageA function, the malware have the ability to log every keystroke before it gets processed by the concerned window procedure. This could result in various vulnerabilities such as credential stealing and personal information leaks.

TranslateMessage hook used for keylogging

TranslateMessage hook used for keylogging

FTP and POP3 Credential stealing

The malware hooks the send API and checks if the protocol used is either ftp or pop3, if that’s the case, it logs the credentials and the URL and sends them back to the C2 server.

send hook checking if the protocol is FTP to log url and credentials

FTP credential stealing in send hook


 
send hook checking if the protocol is POP3 to log url and credentials

POP3 credential stealing in send hook

 

Detailed specifics

Process injection

Process injection is a very important technique for this malware. In fact, various components rely heavily on it. The dropper attempts to use it to run the dropping function and runs it itself if it fails. The stage 2 executable could only execute its main function through injection, else it simply exits. Later on, the initialisation function that contains API hooking and configuration reload listener is injected in every possible process, and this is what allows many capabilities to be feasible. I think the idea is clear, without process injection, this malware is useless in its current format. So, how does it implement it. First, a snapshot of all the system processes is taken. Then, it starts iterating over each process of this snapshot. It checks first if this process is one of 3 system processes or if it’s the malware itself, if that’s the case, it gets ignored. Else, it attempts to open the process using ntOpenProcess first or OpenProcess if the latter fails. If one of these succeeds, the function the attempts injecting the intended function. To simplify it, this is done by allocating a region in the victim memory using VirtualAllocEx, setting the right permissios usinf VirtualProtectEx and then copying the necessary code using VirtualWriteMemory. Finally, the Relocation table is fixed according to the newly injected code. And if this succeeds, a thread running the injected function is created using CreateRemoteThread.

CreateToolhelp32Snapshot

CreateToolhelp32Snapshot

APIs used for process injection being dynmically loaded.

Dynamic loading of needed APIs

Checking for the targeted process against a blacklist.

Blacklist of processes not to be injected in

Opening of the victim process.

Opening of the victim process

Allocation of a page in the victim process and copying of the injected code.

Page allocation and code copy

Fixing of the relocation table

Reloction table fix

Injection of the code and run attempt.

Injection of the code and run attempt if successeful

API Hashing

Just like process injection, this malware relies heavily on API Hashing. In fact, nearly all imported function are dynamically loaded using this method, that’s why the IAT is suspiciously small. Twp API hashing functions are used in this malware : One for loading ntdll and kernel32 functions and the other for loading functions existing in the rest of the listed libraries. Their implementation is not that different and is simple. They both get a hash and a selection number as parameters. The selection number decides what DLL contains the wanted API. If the function is contained within ntdll.dll, the algorithm is directly executed. Else, LdrLoadDll is first dynamically loaded from ntdll.dll to load the needed DLL, and then the algorithm is executed to get the function address. The address of ntdll.dll is taken by parsing the PEB . Now for the algorithm itself : It first takes the handle of the module to load from and the function name hash. Then, it starts iterating over both the name pointer table and the pointer table (which are considered to be parallel arrays). It hashes and compares the name contained in the name pointer table with the supplied parameter, and if it matches, it calculates the function’s address using the ordinal table’s value and returns it. A Ghidra script is attached in the appendix to resolve API loaded by this method.

Getting the address of ntdll using the Process Environment Block

Using PEB to get ntdll’s address

Iterating over the export table to find the needed API.

Iteration over the export table and API name hash comparison

The custom hash function used

The custom hash function used

String Obfuscation

All the noticeable strings used in this malware are obfuscated and dynamically generated at runtime using a simple algorithm. each string is mapped to a value, this value is used to find the index of the string’s length in a predefined array and its obfuscated value in another array. Having both these values, the obfuscated string is retrieved and deobfuscated using a simple algorithm: Each character is subtracted with its preceding byte to retrieve its original value. A Ghidra script is attached in teh appendix to deobfuscate the strings.

String deobfuscation algorithm

String deobfuscation algorithm

API Hooking

Nearly all the capabilities implemented in the stage2 executable are based on function hooking. Keylogging is done by hooking TranslateMessageA, ftp and pop3 credential stealing is done by hooking send, form grabbing is done by hooking PR_Write in nspr4.dll for firefox and many other internet apis. The hooking itself is pretty straightforward, the first 5 bytes of the hooked function are stolen, saved to a trampoline function in the hook and replaced by a jump to the malicious function, after it finishes its execution, the malicious function returns to the stolen bytes using the trampoline to allow the program to continue its normal execution flow. We could see the example of hooking TranslateMEssage in these photos :

The bytes to be stolen

The five first byte are to be stolen


Five first bytes overwritten with hook jump

Five first bytes overwritten with hook jump


Trampoline after byte stealing

Trampoline prepared after byte stealing

Data Exfiltration

Various function hooks rely on data interception and exfiltration. This is made by calling an exfiltration function at the end of the interception that sends the process name, function name, data to be exfiltrated and logged keystrokes back to the C2 server.

The function used to leak data and keystrokes

Function used for data and keystroke leaking

MITRE ATT&CK Matrix

the dropping of the configuration file

SpyEye MITRE ATT&CK matrix

Appendix

Indicators of Compromise

Host-based

TypeIoC
Filecleansweep.exe
Filecleansweepupd.exe
Fileconfig.bin
Directorycleansweep.exe
Registry[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run]   “cleansweep.exe” : “C:\cleansweep.exe\cleansweep.exe”
Mutex__CLEANSWEEP__
Mutex__CLEANSWEEP_RELOADCFG__
Mutex__CLEANSWEEP_UNINSTALL__

Network-based

TypeIoC
Domainwww[.]microsoft-windows-security[.]com
Domainwww[.]secureantibot[.]net
URLhxxp://www[.]microsoft-windows-security[.]com/software/updater3/bt_version_checker.php
URLhxxp://www[.]secureantibot[.]net/software/updater3/bt_version_checker.php
HTTP HeaderContent-Type: multipart/form-data; boundary=55377776816118

Yara rules

Github repo

Ghidra scripts

Github repo