In mid-June, the fight against coronavirus in Kazakhstan was in full swing. Alarmed by the increase in the number of cases (then even former President Nursultan Nazarbayev became infected), the local authorities decided to close again all shopping and entertainment centers, chain stores, markets and bazaars. At that moment, cybercriminals took advantage of the situation by sending malicious mailings to Russian and international companies.
Dangerous letters disguised as an appeal of the Minister of Health of the Republic of Kazakhstan were intercepted by the Threat Detection System (TDS) Group-IB. The attachment contained documents that, when launched, installed a malicious program from the Loki PWS (Password Stealer) family, designed to steal logins and passwords from an infected computer. In the future, attackers can use them to gain access to email accounts for financial fraud, espionage, or sell them on hacker forums.
In this article, Nikita Karpov, analyst at CERT-GIB , examines an instance of one of the most popular Data Stealers now - Loki.
Today we will consider one of the popular versions of the bot - 1.8. It is actively sold, and the admin panel can even be found in the public domain: here .
Example admin panel:
Loki is written in C ++ and is one of the most popular malware used to steal user information from an infected computer. Like the scourge of our time - ransomware viruses - Data Stealer, after being hit on the victim's computer, performs the task at a very high speed - it does not need to gain a foothold and increase its privileges in the system, it practically leaves no time to defend against an attack. Therefore, in the events with malware that steals user data, the main role is played by the investigation of the incident.
Unpacking and getting a workable malware dump
Distribution in most cases occurs through attachments in mailing lists. The user, disguised as a legitimate file, downloads and opens the attachment, launching the malware.
The injection marker suggests the presence of a Loader.
With the help of DIE we get information that the source file is written in VB6.
The entropy graph indicates a large amount of encrypted data.
When launched, the first process creates a child process, injects it and exits. The second process is responsible for the work of the malware. After a short period of time, we stop the process and save the memory dump. To confirm that Loki is inside the dump, look inside the command center url, which in most cases ends in fre.php .
We dump the memory fragment containing the Loki and correct the PE header.
We will check the performance of the dump using the TDS Huntbox system.
Bot functionality
In the process of examining the decompiled malware code, we find a part containing four functions that go immediately after the initialization of the libraries necessary for the operation. Having disassembled each of them inside, we determine their purpose and functionality of our malware.
Function names have been renamed to be more descriptive for convenience.
The bot functionality is determined by two main functions:
- Data Stealer is the first function responsible for stealing data from 101 applications and sending it to the server.
- Downloader - a request from CnC (Command & Control) commands for execution.
For convenience, the table below lists all the applications from which the Loki instance being examined tries to steal data.
Function ID | application | Function ID | application | Function ID | application |
---|---|---|---|---|---|
1 | Mozilla Firefox | 35 | FTPInfo | 69 | ClassicFTP |
2 | Comodo IceDragon | 36 | LinasFTP | 70 | PuTTY / KiTTY |
3 | Apple Safari | 37 | FileZilla | 71 | Thunderbird |
4 | K-Meleon | 38 | Staff-FTP | 72 | Foxmail |
five | SeaMonkey | 39 | BlazeFtp | 73 | Pocomail |
6 | Flock | 40 | NETFile | 74 | IncrediMail |
7 | NETGATE BlackHawk | 41 | GoFTP | 75 | Gmail notifier pro |
8 | Lunascape | 42 | ALFTP | 76 | Checkmail |
nine | Google chrome | 43 | DeluxeFTP | 77 | WinFtp |
ten | Opera | 44 | Total Commander | 78 | Martin Prikryl |
eleven | QTWeb Browser | 45 | FTPGetter | 79 | 32BitFtp |
12 | QupZilla | 46 | WS_FTP | 80 | FTP Navigator |
thirteen | Internet Explorer | 47 | Mail Client configuration files | 81 | Mailing
(softwarenetz) |
fourteen | Opera 2 | 48 | Full tilt poker | 82 | Opera Mail |
fifteen | Cyberfox | 49 | Pokerstars | 83 | Postbox |
sixteen | Pale moon | 50 | ExpanDrive | 84 | FossaMail |
17 | Waterfox | 51 | Steed | 85 | Becky! |
eighteen | Pidgin | 52 | FlashFXP | 86 | POP3 |
19 | SuperPutty | 53 | NovaFTP | 87 | Outlook |
20 | FTPShell | 54 | NetDrive | 88 | Ymail2 |
21 | NppFTP | 55 | Total Commander 2 | 89 | Trojitá |
22 | MyFTP | 56 | SmartFTP | 90 | TrulyMail |
23 | FTPBox | 57 | FAR Manager | 91 | .spn Files |
24 | sherrod FTP | 58 | Bitvise | 92 | To-Do Desklist |
25 | FTP Now | 59 | RealVNC
TightVNC |
93 | Stickies |
26 | NexusFile | 60 | mSecure Wallet | 94 | NoteFly |
27 | Xftp | 61 | Syncovery | 95 | NoteZilla |
28 | EasyFTP | 62 | FreshFTP | 96 | Sticky notes |
29 | SftpNetDrive | 63 | BitKinex | 97 | KeePass |
thirty | AbleFTP | 64 | UltraFXP | 98 | Enpass |
31 | JaSFtp | 65 | FTP Now 2 | 99 | My RoboForm |
32 | Automize | 66 | Vandyk SecureFX | 100 | 1Password |
33 | Cyberduck | 67 | Odin Secure FTP Expert | 101 | Mikrotik WinBox |
34 | Fullsync | 68 | Fling |
Networking
There are two problems that need to be addressed to record network communications:
- The Command Center is only available at the time of the attack.
- Wireshark does not record bot communications in the loopback, so you need to use other means.
The simplest solution is to forward the CnC address that Loki will communicate with to localhost. For the bot, the server is now available at any time, although it does not respond, but it is not necessary to record the bot's communications. To solve the second problem, we will use the RawCap utility, which allows us to write the communications we need to pcap. Next, we will parse the recorded pcap in Wireshark.
Before each communication, the bot checks the availability of CnC and, if available, opens socket. All network communications take place at the transport level using the TCP protocol, and at the application level, HTTP is used.
The table below shows the packet headers that Loki uses as standard.
Field | Value | Description |
---|---|---|
User-agent | Mozilla / 4.08 (Charon; Inferno) | A typical user agent for Loki |
Accept | * / * | |
Content-Type | application / octet-stream | |
Content-Encoding | binary | |
Content-Key | 7DE968CC | Hashing result of previous headers (hashing is done by a custom CRC algorithm with polynomial 0xE8677835) |
Connection | close |
- The structure of the recorded data depends on the version of the bot, and in earlier versions there are no fields that are responsible for the encryption and compression options.
- The server determines by the type of request how to process the received data. There are 7 types of data that the server can read:
- 0x26 Stolen wallet data
- 0x27 Stolen Application Data
- 0x28 Command request from server
- 0x29 Unloading a stolen file
- 0x2A POS
- 0x2B Keylogger data
- 0x2C Screenshot
- In the examined instance, only 0x27, 0x28 and 0x2B were present.
- Each request contains general information about the bot and the infected system, according to which the server identifies all reports for one machine, and then there is information that depends on the type of request.
- In the latest version of the bot, only data compression is implemented, and encrypted fields are prepared for the future and are not processed by the server.
- The open source APLib library is used to compress data.
When forming a request with stolen data, the bot allocates a buffer of size 0x1388 (5000 bytes). The structure of 0x27 requests is shown in the table below:
Bias | The size | Value | Description |
---|---|---|---|
0x0 | 0x2 | 0x0012 | Bot version |
0x2 | 0x2 | 0x0027 | Request type (send stolen data) |
0x4 | 0xD | ckav.ru | Binary ID (XXXXX11111 also occurs) |
0x11 | 0x10 | - | Username |
0x21 | 0x12 | - | Computer name |
0x33 | 0x12 | - | Computer domain name |
0x45 | 0x4 | - | Screen resolution (width and height)
|
0x49 | 0x4 | - | |
0x4D | 0x2 | 0x0001 | User rights flag (1 if administrator) |
0x4F | 0x2 | 0x0001 | SID flag (1 if set) |
0x51 | 0x2 | 0x0001 | System bitness flag (1 if x64) |
0x53 | 0x2 | 0x0006 | Windows version (major version number) |
0x55 | 0x2 | 0x0001 | Windows version (minor version number) |
0x57 | 0x2 | 0x0001 | Additional system information (1 = VER_NT_WORKSTATION) |
0x59 | 0x2 | - | |
0x5B | 0x2 | 0x0000 | Was the stolen data sent |
0x5D | 0x2 | 0x0001 | Was data compression used |
0x5F | 0x2 | 0x0000 | Compression type |
0x61 | 0x2 | 0x0000 | Was data encryption used |
0x63 | 0x2 | 0x0000 | Encryption type |
0x65 | 0x36 | - | MD5 from MachineGuid register value |
0x9B | - | - | Compressed stolen data |
Buffer size: 0x2BC (700 bytes)
Bias | The size | Value | Description |
---|---|---|---|
0x0 | 0x2 | 0x0012 | Bot version |
0x2 | 0x2 | 0x0028 | Request type (command request from command center) |
0x4 | 0xD | ckav.ru | Binary ID (XXXXX11111 also occurs) |
0x11 | 0x10 | - | Username |
0x21 | 0x12 | - | Computer name |
0x33 | 0x12 | - | Computer domain name |
0x45 | 0x4 | - | Screen resolution (width and height) |
0x49 | 0x4 | - | |
0x4D | 0x2 | 0x0001 | User rights flag (1 if administrator) |
0x4F | 0x2 | 0x0001 | SID flag (1 if set) |
0x51 | 0x2 | 0x0001 | System bitness flag (1 if x64) |
0x53 | 0x2 | 0x0006 | Windows version (major version number) |
0x55 | 0x2 | 0x0001 | Windows version (minor version number) |
0x57 | 0x2 | 0x0001 | Additional system information (1 = VER_NT_WORKSTATION) |
0x59 | 0x2 | 0xFED0 | |
0x5B | 0x36 | - | MD5 from MachineGuid register value |
Buffer size: 0x10 (16 bytes) + 0x10 (16 bytes) for each command in the packet.
HTTP header (start of data) | \ r \ n \ r \ n | [0D 0A 0D 0A] | 4 bytes | ||
- | - | 4 | |||
2 | [00 00 00 02] | 4 | |||
4 |
4 |
4 |
4 |
() |
|
---|---|---|---|---|---|
#0
EXE- |
[00 00 00 00] | [00 00 00 00] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.exe |
#1
DLL |
[00 00 00 00] | [00 00 00 01] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.dll |
#2
EXE- |
[00 00 00 00] | [00 00 00 02] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.exe |
#8
(HDB file) |
[00 00 00 00] | [00 00 00 08] | [00 00 00 00] | [00 00 00 00] | - |
#9
|
[00 00 00 00] | [00 00 00 09] | [00 00 00 00] | [00 00 00 00] | - |
#10
|
[00 00 00 00] | [00 00 00 0A] | [00 00 00 00] | [00 00 00 00] | - |
#14
Loki |
[00 00 00 00] | [00 00 00 0E] | [00 00 00 00] | [00 00 00 00] | - |
#15
Loki |
[00 00 00 00] | [00 00 00 0F] | [00 00 00 00] | [00 00 00 23] | www.notsogood.site/malicious.exe |
# 16
Change the frequency of checking the response from the server |
[00 00 00 00] | [00 00 00 10] | [00 00 00 00] | [00 00 00 01] | five |
# 17
Remove Loki and quit |
[00 00 00 00] | [00 00 00 11] | [00 00 00 00] | [00 00 00 00] | - |
Network traffic parser
Thanks to this analysis, we have all the information we need to parse Loki's network interactions.
The parser is implemented in Python, receives a pcap file as input and finds all communications belonging to Loki in it.
First, let's use the dkpt library to find all TCP packets. To receive only http packets, let's put a filter on the used port. Among the received http packets, we select those that contain the well-known Loki headers, and get communications that need to be parsed in order to extract information from them in a readable form.
for ts, buf in pcap:
eth = dpkt.ethernet.Ethernet(buf)
if not isinstance(eth.data, dpkt.ip.IP):
ip = dpkt.ip.IP(buf)
else:
ip = eth.data
if isinstance(ip.data, dpkt.tcp.TCP):
tcp = ip.data
try:
if tcp.dport == 80 and len(tcp.data) > 0: # HTTP REQUEST
if str(tcp.data).find('POST') != -1:
http += 1
httpheader = tcp.data
continue
else:
if httpheader != "":
print('Request information:')
pkt = httpheader + tcp.data
httpheader = ""
if debug:
print(pkt)
req += 1
request = dpkt.http.Request(pkt)
uri = request.headers['host'] + request.uri
parsed_payload['Network']['Source IP'] = socket.inet_ntoa(ip.src)
parsed_payload['Network']['Destination IP'] = socket.inet_ntoa(ip.dst)
parsed_payload_same['Network']['CnC'] = uri
parsed_payload['Network']['HTTP Method'] = request.method
if uri.find("fre.php"):
print("Loki detected!")
pt = parseLokicontent(tcp.data, debug)
parsed_payload_same['Malware Artifacts/IOCs']['User-Agent String'] = request.headers['user-agent']
print(json.dumps(parsed_payload, ensure_ascii=False, sort_keys=False, indent=4))
parsed_payload['Network'].clear()
parsed_payload['Compromised Host/User Data'].clear()
parsed_payload['Malware Artifacts/IOCs'].clear()
print("----------------------")
if tcp.sport == 80 and len(tcp.data) > 0: # HTTP RESPONCE
resp += 1
if pt == 40:
print('Responce information:')
parseC2commands(tcp.data, debug)
print("----------------------")
pt = 0
except(dpkt.dpkt.NeedData, dpkt.dpkt.UnpackError):
continue
In all Loki requests, the first 4 bytes are responsible for the bot version and the request type. Using these two parameters, we determine how we will process the data.
def parseLokicontent(data, debug):
index = 0
botV = int.from_bytes(data[0:2], byteorder=sys.byteorder)
parsed_payload_same['Malware Artifacts/IOCs']['Loki-Bot Version'] = botV
payloadtype = int.from_bytes(data[2:4], byteorder=sys.byteorder)
index = 4
print("Payload type: : %s" % payloadtype)
if payloadtype == 39:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Application/Credential Data"
parse_type27(data, debug)
elif payloadtype == 40:
parsed_payload['Network']['Traffic Purpose'] = "Get C2 Commands"
parse_type28(data, debug)
elif payloadtype == 43:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Keylogger Data"
parse_type2b(lb_payload)
elif payloadtype == 38:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Cryptocurrency Wallet"
elif payloadtype == 41:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Files"
elif payloadtype == 42:
parsed_payload['Network'].['Traffic Purpose'] = "Exfiltrate POS Data"
elif payloadtype == 44:
parsed_payload['Network']['Traffic Purpose'] = "Exfiltrate Screenshots"
return payloadtype
The next in line will be to parse the response from the server. To read only useful information, look for the \ r \ n \ r \ n sequence , which defines the end of the packet headers and the beginning of commands from the server.
def parseC2commands(data, debug):
word = 2
dword = 4
end = data.find(b'\r\n\r\n')
if end != -1:
index = end + 4
if (str(data).find('<html>')) == -1:
if debug:
print(data)
fullsize = getDWord(data, index)
print("Body size: : %s" % fullsize)
index += dword
count = getDWord(data, index)
print("Commands: : %s" % count)
if count == 0:
print('No commands received')
else:
index += dword
for i in range(count):
print("Command: %s" % (i + 1))
id = getDWord(data, index)
print("Command ID: %s" % id)
index += dword
type = getDWord(data, index)
print("Command type: %s" % type)
index += dword
timelimit = getDWord(data, index)
print("Command timelimit: %s" % timelimit)
index += dword
datalen = getDWord(data, index)
index += dword
command_data = getString(data, index, datalen)
print("Command data: %s" % command_data)
index += datalen
else:
print('No commands received')
return None
This concludes the analysis of the main part of the parser's algorithm and move on to the result that we get at the output. All information is displayed in json format.
Below are images of the result of the parser's work, obtained from the communications of various bots, with different CnC and recorded in different environments.
Request information:
Loki detected!
Payload type: 39
Decompressed data:
{'Module': {'Mozilla Firefox'}, 'Version': {0}, 'Data': {'domain': {'https://accounts.google.com'}, 'username': {'none@gmail.com'}, 'password': {'test'}}}
{'Module': {'NppFTP'}, 'Version': {0}, 'Data': {b'<?xml version="1.0" encoding="UTF-8" ?>\r\n<NppFTP defaultCache="%CONFIGDIR%\\Cache\\%USERNAME%@%HOSTNAME%" outputShown="0" windowRatio="0.5" clearCache="0" clearCachePermanent="0">\r\n <Profiles />\r\n</NppFTP>\r\n'}}
{
"Network": {
"Source IP": "-",
"Destination IP": "185.141.27.187",
"HTTP Method": "POST",
"Traffic Purpose": "Exfiltrate Application/Credential Data",
"First Transmission": true
},
"Compromised Host/User Data": {},
"Malware Artifacts/IOCs": {}
}
Above is an example of a request to server 0x27 (uploading application data). For testing, accounts were created in three applications: Mozilla Firefox, NppFTP and FileZilla. Loki has three options for recording application data:
- In the form of a SQL database (the parser saves the database and displays all available rows in it).
- In open form, as in Firefox in the example.
- As an xml file like NppFTP and FileZilla.
Request information:
Loki detected!
Payload type: 39
No data stolen
{
"Network": {
"Source IP": "-",
"Destination IP": "185.141.27.187",
"HTTP Method": "POST",
"Traffic Purpose": "Exfiltrate Application/Credential Data",
"First Transmission": false
},
"Compromised Host/User Data": {},
"Malware Artifacts/IOCs": {}
}
The second request is of type 0x28 and requests commands from the server.
Responce information:
Body size: 26
Commands: 1
Command: 1
Command ID: 0
Command type: 9
Command timelimit: 0
Command data: 35
An example of a response from CnC, which sent one command in response to start the keylogger. And the subsequent unloading of keylogger data.
Request information:
Loki detected!
Payload type: : 43
{
"Network": {
"Source IP": "-",
"Destination IP": "185.141.27.187",
"HTTP Method": "POST",
"Traffic Purpose": "Exfiltrate Keylogger Data"
},
"Compromised Host/User Data": {},
"Malware Artifacts/IOCs": {}
}
At the end of the work, the parser outputs the information contained in each request from the bot (information about the bot and the system), and the number of requests and responses associated with Loki in the pcap file.
General information:
{
"Network": {
"CnC": "nganyin-my.com/chief6/five/fre.php"
},
"Compromised Host/User Description": {
"User Name": "-",
"Hostname": "-",
"Domain Hostname": "-",
"Screen Resolution": "1024x768",
"Local Admin": true,
"Built-In Admin": true,
"64bit OS": false,
"Operating System": "Windows 7 Workstation"
},
"Malware Artifacts/IOCs": {
"Loki-Bot Version": 18,
"Binary ID": "ckav.ru",
"MD5 from GUID": "-",
"User-Agent String": "Mozilla/4.08 (Charon; Inferno)"
}
}
Requests: 3
Responces: 3
The full parser code is available at: github.com/Group-IB/LokiParser
Conclusion
In this article, we took a closer look at Loki malware, disassembled its functionality and implemented a network traffic parser that will greatly simplify the incident analysis process and help us understand what exactly was stolen from the infected computer. While Loki development is still ongoing, only version 1.8 (and earlier) has been leaked, which is the version security professionals encounter every day.
In the next article, we will analyze another popular Data Stealer, Pony, and compare these malware.
Indicator of Compromise (IOCs):
Urls:
- nganyin-my.com/chief6/five/fre.php
- wardia.com.pe/wp-includes/texts/five/fre.php
- broken2.cf/Work2/fre.php
- 185.141.27.187/danielsden/ver.php
- MD5 hash: B0C33B1EF30110C424BABD66126017E5
- User-Agent String: «Mozilla/4.08 (Charon; Inferno)»
- Binary ID: «ckav.ru»