Loki 1.8: dossier on young and up-and-coming Data Stealer





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:



  1. Data Stealer is the first function responsible for stealing data from 101 applications and sending it to the server.
  2. 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
At this stage, the static analysis of malware is completed, and in the next section we will consider how Loki communicates with the server.



Networking



There are two problems that need to be addressed to record network communications:



  1. The Command Center is only available at the time of the attack.
  2. 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
Let's pay attention to the body of the package:



  1. 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.
  2. 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
  3. In the examined instance, only 0x27, 0x28 and 0x2B were present.
  4. 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.
  5. 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.
  6. 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
The second stage of interaction with the server begins after being fixed in the system. The bot sends a request with the type 0x28, the structure of which is shown below:



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
After the request, the bot expects to receive a response from the server containing the number and the commands themselves. Possible command variants are obtained using static analysis of the decompiled malware code and are presented below.



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:



  1. In the form of a SQL database (the parser saves the database and displays all available rows in it).
  2. In open form, as in Firefox in the example.
  3. 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»



All Articles