Revealing Lamberts/Longhorn malware capabilities using a step-by-step approach (cyberespionage group linked to Vault 7)

Summary

According to an article published by Symantec at https://www.symantec.com/connect/blogs/longhorn-tools-used-cyberespionage-group-linked-vault-7, a group called Longhorn has attacked at least 40 targets in 16 countries across the Middle East, Europe, Asia, and Africa. They mention that the sample they analyzed has similar tactics and techniques described in the Vault 7 documents disclosed by WikiLeaks in 2017. Kaspersky published an awesome article at https://securelist.com/unraveling-the-lamberts-toolkit/77990/, which reveals even more details about “The Lamberts”, such as the fact that there are multiple related malware families: Black Lambert, White Lambert, Blue Lambert, Green Lambert, Pink Lambert and Gray Lambert. Our analysis is focused on a sample belonging to Green Lambert family with the “APE ESCAPE(Version 3.0.2)” code name.

The malware comes up with an encrypted configuration that is decrypted using a custom algorithm. The file is made persistent by creating a new service, and also a new entry under the Run registry key is created. An unusual action performed by the malicious process is creating a new desktop by calling the CreateDesktopA API. The configuration buffer contains 2 C2 servers (one IP address and one domain). The threat actor uses a technique called “timestomping” in order to modify the created, last accessed, and last modified times of the malicious binary. All data sent to the C2 servers is encrypted using a custom algorithm presented in the technical analysis section. The malware can perform multiple functions such as creating processes, exfiltrating data, downloading files/configuration, modifying registry keys, executing commands received from the C2 servers. The binary can run with 3 different parameters, for example, the “-remove” parameter can be used to uninstall the malware and also delete all traces (service, registry keys, files and so on).

Note: Our visibility was limited because the C2 domains are no longer available, and we couldn’t obtain files/configurations that were intended to be downloaded by the malware. The internet has been emulated using FakeNet, and the following analysis represents the best effort to describe the threat actor’s activity without valuable information provided by the Command and Control servers.

Technical analysis

SHA256: 9719D7CDD78794920A795C6BF8BC3797EC9DAABD6619899D0726E90E8DD2B42D

One of the first steps the malware is performing is to allocate 3 new memory areas using the VirtualAlloc API:

Figure 1

The file implements a custom decryption algorithm that will also be used to encrypt information, as we’ll see during the entire analysis. This is used to decrypt the following encrypted content:

Figure 2

The algorithm consists of shr (shift right), shl (shift left), XOR, and, add (addition), sub (subtract) operations and the representation in assembly language is shown in figure 3. Let’s call this algorithm Algorithm1.

Figure 3

After the decryption operation is complete, the information reveals DLL names, function names, registry keys, HTTP methods, and so on:

Figure 4

The process changes the current directory to the location of the initial executable file (in our case, the Desktop folder), as shown in the next figure:

Figure 5

More relevant strings are decrypted by the same decryption algorithm as above (Algorithm1):

Figure 6

The sample creates a handle to a CSP (cryptographic service provider) hash object that will be used to hash streams of data. The important parameter here is 0x8003 (MD5 hashing algorithm):

Figure 7

The MD5 algorithm is used to hash the “Netqps” string by calling the CryptHashData function:

Figure 8

The hash value is extracted via a CryptGetHashParam function call:

Figure 9

The following bytes represent the hash value extracted above:

Figure 10

Using the hash value, the process constructs a registry key name by calling the wsprintfA function:

Figure 11

The SetSecurityDescriptorDacl function is utilized to set information in a DACL (discretionary access control list):

Figure 12

The malicious sample creates 3 named event objects by using the CreateEventA API. If any of these already exists, the process exits (only one instance of malware is running):

Figure 13

The OpenSCManagerA function is used to establish a connection to the service control manager on the local machine (the handle will be used to create a new service, as we’ll see later on):

Figure 14

The file tries to open a service called “Netqps”, which doesn’t exist at this time:

Figure 15

The sample is made persistent by creating a new service called “Netqps”, with the “Network QoS Provisioning” as the display name, as shown below:

Figure 16

The description of the service is changed by calling the ChangeServiceConfig2A API:

Figure 17

The following configuration information (0x2 SERVICE_CONFIG_FAILURE_ACTIONS) is modified using the same function:

Figure 18

The malware performs function calls to OpenSCManagerA and OpenServiceA in order to open the “Netqps” service, which exists now. The service is started by calling the StartServiceA function:

Figure 19

If the user has successfully created the service, the process exits. However, if the process hasn’t enough rights to create the service, there will be a new entry under the “Run” registry key created for persistence purposes:

Figure 20

The new entry is called “Netqps”, and the malware is supposed to run with the “-run” parameter, as shown in the next figure:

Figure 21

The proof that the value was successfully added to the “Run” registry key:

Figure 22

The process creates a new desktop using the hardcoded string “__efb37f_21_76“, associates it with the current window station and assigns it to the calling thread. This is done via a CreateDesktopA function call:

Figure 23

The CreateEventW function is used to create 2 unnamed event objects. One of the function calls is displayed in figure 24:

Figure 24

A new thread is created by calling the CreateThread API:

Figure 25

One of the unnamed events created earlier is set to the signaled state by calling the SetEvent function:

Figure 26

“-run” Parameter

It’s important to mention that the execution flow of the malware is passed to the newly created thread, which coincides with the execution flow of the sample run with the “-run” parameter. The desktop created earlier is assigned to the new thread using the SetThreadDesktop API:

Figure 27

The file attempts to open a registry key called “Software\Classes\CLSID\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}”, which doesn’t exist on the local machine:

Figure 28

Because the registry key doesn’t exist, it is created by calling the RegCreateKeyExA function:

Figure 29

Moreover, the malware creates a subkey called “ShellFolder” under the freshly created registry key, as follows:

Figure 30

One of the C2 servers (https[:]//srv18.banner-add[.]com) is encrypted with Algorithm1:

Figure 31

After the encryption is complete, the data looks like in the following buffer:

Figure 32

The encrypted C2 server is copied into a value called “ProgID” under the “Software\Classes\CLSID\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}\ShellFolder” registry key:

Figure 33

The confirmation that the value was successfully created by the sample:

Figure 34

The process repeats the encryption operation, the only difference being that it’s encrypting two C2 servers (one domain and one IP address):

Figure 35

The “ProgID” value is modified to the new encrypted content, as shown in figure 36:

Figure 36

The current system date and time in UTC are retrieved using the GetSystemTime function, and then they’re converted to file time format by calling the SystemTimeToFileTime API:

Figure 37

The file generates 10 random bytes by calling the CryptGenRandom function:

Figure 38

There is a buffer that is encrypted using Algorithm1. We have the confirmation that we’re dealing with Green Lambert (APE ESCAPE – version 3.0.2):

Figure 39

After the encryption is complete, the buffer is transforming to the following:

Figure 40

The encrypted buffer is saved at {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\ShellFolder\(Default) via a call to the RegSetValueExA function:

Figure 41

The confirmation that the value has been successfully added:

Figure 42

The User-Agent that is currently being used is extracted using the ObtainUserAgentString API, as shown in the next figure:

Figure 43

The malware opens the access token associated with the current process (the relevant parameter here is 0x20 TokenAdjustPrivileges):

Figure 44

The malicious process verifies if it has “SeDebugPrivilege” privilege (usually, it can be used to inject code into other processes owned by other accounts):

Figure 45

For example, if the process determines that it doesn’t have the above privilege, it’s trying to enable it by calling the AdjustTokenPrivileges function:

Figure 46

As described before, the process calls the CryptAcquireContextW and then CryptCreateHash in order to initialize a hash object that will be used to hash streams of data (0x8003 – MD5 algorithm). This is used to hash the “MRSNetqps” string, as shown in figure 47:

Figure 47

The hash value is retrieved via a CryptGetHashParam function call, and it’s highlighted in the following picture:

Figure 48

The malware opens the kernel32.dll file located in the System32 directory:

Figure 49

The created, last accessed, and last modified times of kernel32.dll are retrieved using the GetFileTime API:

Figure 50

The process performs a technique called “timestomping”, which means that the created, last accessed, and last modified times of the malicious binary are changed to the ones extracted above, as follows:

Figure 51

The technique worked, and now we can observe that the timestamps have been changed:

Figure 52

The system time converted to file time is compared to another file time (the function returns 1 because the system time is later than the other file time obtained by subtracting around 34 minutes from the current system time). This method is probably used to avoid some sandboxes:

Figure 53

There is a 2nd comparison operation performed between the current system time and another time obtained by adding around 26 minutes to the system time. The result of the function is -1:

Figure 54

The binary initializes the use of the WinINet functions by calling the InternetOpenW API with the User-Agent retrieved earlier:

Figure 55

The internet connection receive and send timeouts are set to 60 seconds using the InternetSetOptionW function:

Figure 56

The binary opens an HTTP session for www.microsoft.com by calling the InternetConnectW API with the corresponding parameter:

Figure 57

It performs a GET request using the HttpOpenRequestW API, as shown in figure 58:

Figure 58

The HttpSendRequestW function is used to send the request to the HTTPS server, as shown below:

Figure 59

The malware is extracting the HTTP status code, and it’s expecting to be 200, otherwise, the process finishes its execution:

Figure 60

As before, the process generates 32 random bytes by calling the CryptGenRandom function:

Figure 61
Figure 62

There is a call to the InternetOpenW function and then 2 calls to the InternetSetOptionW API. The process initiates an HTTPS connection (port 443) to the first C2 server srv18.banner-add[.]com:

Figure 63

A GET request to /login.php?d=<C2 server>&session=<32 random chars generated earlier> is performed:

Figure 64

There is a call to the InternetQueryOptionW function for determining the security flags for the handle (0x1f = INTERNET_OPTION_SECURITY_FLAGS):

Figure 65

The security flags are modified using the InternetSetOptionA API (0x3180 = 0x2000|0x1000|0x100|0x80 = SECURITY_FLAG_IGNORE_CERT_DATE_INVALID|SECURITY_FLAG_IGNORE_CERT_CN_INVALID|SECURITY_FLAG_IGNORE_UNKNOWN_CA|SECURITY_FLAG_IGNORE_REVOCATION):

Figure 66

The malware sends the request by calling the HttpSendRequestW function and then it extracts the status code using the HttpQueryInfoW function. It reads data received from the server by calling the InternetReadFile API (number of bytes = 0x10 = 16):

Figure 67

More data is read using the same function, however it’s expecting a large file/configuration because the number of bytes is 0xc800 (51200):

Figure 68

The server response is supposed to be encrypted, and the process is using a custom algorithm to decrypt it. Let’s call this algorithm Algorithm2:

Figure 69

If the server response has a length of 0 bytes, the malware will perform a different request, as we’ll see moving forward. There is a call to CryptAcquireContextW and then to CryptCreateHash with the 0x8003 parameter (MD5 algorithm). This hash object is used to hash a buffer of 16 NULL bytes, as shown in the next figure:

Figure 70

The hash value is extracted by calling the CryptGetHashParam API:

Figure 71
Figure 72

It’s interesting to see that the malware is repeatedly constructing a buffer that reveals the malware family (Green Lambert – APE ESCAPE – Version 3.0.2) and includes the 10-bytes value generated earlier (will be defined as implaintid):

Figure 73

16 random bytes are generated using the CryptGenRandom API:

Figure 74
Figure 75

The buffer from above, which contains the information regarding the malware family, is encrypted using Algorithm2:

Figure 76

The encrypted information from figure 76 is exfiltrated to the C2 server via a GET request. The getconf.php script suggests that the file is trying to download a configuration from the server (session=<16 random chars generated earlier>+<encrypted buffer>):

Figure 77

As before, the malware is calling the following functions: InternetQueryOptionW, InternetSetOptionW and HttpSendRequestW. The HTTP status code is extracted using the HttpQueryInfoW API and then the server response is parsed using 2 InternetReadFile function calls. The response is decrypted using Algorithm2. If the data received by the malware has 0 bytes then the following buffer is encrypted using Algorithm1 (note the system date and time):

Figure 78

The encrypted content is saved at {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\InProcServer32\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}, as shown in the next figure:

Figure 79

The binary tries to open a file named <binary name>.dat. Unfortunately, we were not able to determine its exact purpose due to the lack of evidence provided by the real C2 servers:

Figure 80

Now, if the server’s response wasn’t empty, the next buffer is encrypted using Algorithm1 (observe the system date and time):

Figure 81

As in the first case, the encrypted buffer is saved at {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\InProcServer32\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}:

Figure 82

The process performs a call to the CryptAcquireContextW API and then CryptCreateHash API (with the 0x8003 parameter – MD5 algorithm). This is used to hash the buffer from figure 72, as shown below:

Figure 83

The CryptGetHashParam function is used to retrieve the hash value: 39 8D 01 FD F7 93 4D 12 92 C2 63 D3 74 77 8E 1A. 16 random bytes are generated using the CryptGenRandom API:

Figure 84
Figure 85

The following information is encrypted using Algorithm2:

Figure 86

The malicious binary constructs the following URL that will be used to exfiltrate information, as we’ll see in the following paragraphs:

Figure 87

Other 16 bytes are generated using the CryptGenRandom API:

Figure 88
Figure 89

Algorithm2 is utilized to encrypt the buffer presented in figure 90:

Figure 90

The CryptGenRandom function is used to generate 6 random bytes that will be utilized to construct some HTTP header fields:

Figure 91
Figure 92

The process creates a new HTTPS request handle:

Figure 93

The HttpAddRequestHeadersA API is utilized to add a request header to the handle (note the randomly-generated bytes):

Figure 94

The request is sent to the C2 server by calling the HttpSendRequestExW function:

Figure 95

The application sends more data to the C2 server by calling the InternetWriteFile API:

Figure 96

The buffer that was encrypted earlier (figure 86) is exfiltrated to the C2 server:

Figure 97

The exfiltration is completed by sending the following string:

Figure 98

The process ends the HTTPS request by calling the HttpEndRequestW function, and also it ensures that the status code is 200 by calling the HttpQueryInfoW API. The CryptHashData function is used to hash one of the hashes obtained above using the MD5 algorithm:

Figure 99

The CryptGetHashParam function is utilized to get the hash value computed in figure 99: 79 95 88 60 59 DF 6D D8 A1 CE F4 33 8A A4 11 8E. The same steps presented starting with figure 84 and ending with figure 98 are repeated one more time, so we will not describe them again. It’s important to mention that the next steps are performed only if the above request fails for some reason. The CreateToolhelp32Snapshot API is used to take a snapshot of the current processes (0x2 TH32CS_SNAPPROCESS):

Figure 100

All processes that are running on the host are enumerated by calling the Process32FirstW and Process32NextW functions. The binary is interested in finding the explorer.exe process:

Figure 101

The file retrieves a handle to the explorer.exe process by calling the OpenProcess API:

Figure 102

The access token associated with explorer.exe is retrieved by calling the OpenProcessToken function (0xb TOKEN_WRITE):

Figure 103

The sample uses the ImpersonateLoggedOnUser API to impersonate the security context  of the logged-on user, as shown in figure 104:

Figure 104

If the malware determines that the OS version is a legacy version, such as Windows XP, it attempts to load the pstorec.dll library and access the legacy Windows data store called Pstore. There is a call to the PStoreCreateInstance function in order to retrieve a pointer to protected storage that will allow the malware to enumerate and retrieve stored credentials, as shown below:

Figure 105

The CredEnumerateW function is used to enumerate the credentials from the user’s credential set:

Figure 106

There is a call to the RevertToSelf API in order to terminate the impersonation. The process tries to open the following registry key “SOFTWARE\Clients\StartMenuInternet\firefox.exe\shell\open\command” (this contains a path to the Mozilla Firefox browser):

Figure 107

If the registry key from above exists, the binary tries to access the following files: C:\Users\All Users\Application Data\Mozilla\Firefox\profiles.ini, C:\Users\Default\Application Data\Mozilla\Firefox\profiles.ini, C:\Users\Default User\Application Data\Mozilla\Firefox\profiles.ini and C:\Users\Public\Application Data\Mozilla\Firefox\profiles.ini. If any of these is found, the file content will be exfiltrated to the C2 server using the same steps that were already described (POST request to show.php). It looks like the process parses the response from the C2 server and compares some values with a hardcoded string, as shown below:

Figure 108

Unfortunately, since we were not able to obtain a proper C2 response, we have a limited overview of what these commands are supposed to do. We have observed the following strings used in the comparison: delmrs, 0oR, listmrs, loadmrc, P6S, unloadmrc, listmrc, exit, ps, kill, reboot, execAttached, exec, flushq, clearq, uploadmax, get, upload, put, download, delete, dir, suspend, lpabout, about. The binary is looking for a file called “herror.log”, as shown below:

Figure 109

If the file exists, the malware reads it using the ReadFile function. The file content is encrypted using Algorithm2:

Figure 110

The encrypted content of the file is exfiltrated to the C2 server using the same logic as before. The file is deleted after the exfiltration finishes:

Figure 111

The “{731823ef-11b3-6c7f-dd21-d5763d265f9e3}” value is deleted from “\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}\InProcServer32” (this value was created in figure 79 or figure 82):

Figure 112

The same activity described so far regarding the C2 server is also applied for the second one: 119.235.255.197.

Now we will describe the execution flow of the function found at 0x4057D8. The file compares a local variable with one of the following hardcoded values: NOWAIT, WAIT and WAIT_FOREVER. There is a call to GetTickCount in order to retrieve the number of milliseconds that have elapsed since the system was started and then to GetCurrentProcessId, which is used to get the process id. These 2 values are used to create a named pipe:

Figure 113

The binary uses the CreateFileA API in order to ensure that the pipe can be used for simultaneous read and write operations (0x40000000 FILE_FLAG_OVERLAPPED):

Figure 114

The ConnectNamedPipe API is utilized to enable the current process in order to wait for a client process to connect to the named pipe:

Figure 115

The malicious file has the capability of creating new processes based on the C2 server’s instructions:

Figure 116

The data from the named pipe (the output of the created process) is copied into a buffer via a PeekNamedPipe function call. Based on our static analysis, the output retrieved earlier will be exfiltrated to the C2 server. In the following few paragraphs we’ll describe the functionality of the function found at 0x404CD5. The binary is using the RegOpenKeyExA API in order to open the “Software\Classes\CLSID\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}” registry key. It tries to see if any of the following values exist: {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\ShellFolder\Attributes, {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\ShellFolder\ProgID, {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\ShellFolder\QueryForOverlay, {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\ShellFolder\CallForAttributes (if any of these exists, it will be decrypted using Algorithm2). An example of such a query is displayed below:

Figure 117

The following functions are called: GetSystemTime, GetCurrentThreadId and GetThreadDesktop. The desktop name (“__efb37f_21_76“) is retrieved by calling the GetUserObjectInformationA function:

Figure 118

The computer name is extracted by calling the GetComputerNameA API, and then the process retrieves adapter information for the local machine by calling the GetAdaptersInfo function. The following information has been collected and can be exfiltrated to the C2 server: Windows version, system time and date, binary name, process ID, computer name, desktop name, service name installed in case 1, family name (APE ESCAPE), implant_id, C2 servers, modules (we didn’t see these because the C2 servers are down), proxy configuration, local IP address. A detailed view is shown in figure 119:

Figure 119

The process tries to open a value called “{30869038194918520211989843}” from {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\InProcServer32:

Figure 120

The buffer with the information collected earlier is encrypted using Algorithm1:

Figure 121

The encrypted content is saved at {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\InProcServer32\{30869038194918520211989843}:

Figure 122

“-remove” Parameter

The binary opens the “Netqps” service created earlier by calling the OpenServiceA API:

Figure 123

The SHDeleteKeyA function is used to delete the key created in the first case, as shown in the next figure:

Figure 124

The 2nd persistence mechanism is also eliminated using the RegDeleteValueA API (the entry added under the “Run” registry key):

Figure 125

It’s interesting to see that the malware also decommits the memory allocated with VirtualAlloc in the first case, using the VirtualFree API (no traces left).

“-f” Parameter

The “Global\P731823ef11b36c7fdd21d5763d265f9e” event is set to the signaled state using the SetEvent API:

Figure 126

References

Symantec report: https://www.symantec.com/connect/blogs/longhorn-tools-used-cyberespionage-group-linked-vault-7

Kaspersky report: https://securelist.com/unraveling-the-lamberts-toolkit/77990/

MSDN: https://docs.microsoft.com/en-us/windows/win32/api/

Fakenet: https://github.com/fireeye/flare-fakenet-ng

Any.run: https://any.run/report/9719d7cdd78794920a795c6bf8bc3797ec9daabd6619899d0726e90e8dd2b42d/b4b9b504-88c9-48a2-81c4-12c2ecc34e24

VirusTotal: https://www.virustotal.com/gui/file/9719d7cdd78794920a795c6bf8bc3797ec9daabd6619899d0726e90e8dd2b42d/detection

INDICATORS OF COMPROMISE

C2 domains: https[:]//srv18.banner-add[.]com, https[:]//119.235.255.197

SHA256: 9719D7CDD78794920A795C6BF8BC3797EC9DAABD6619899D0726E90E8DD2B42D

URLs: https[:]//srv18.banner-add.com/login.php?d=https[:]//srv18.banner-add.com&session=<32 random bytes>

https[:]//119.235.255.197/login.php?d=https[:]//119.235.255.197&session=<32 random bytes>

https[:]//srv18.banner-add.com/getconf.php?session=<encrypted bytes>

https[:]//119.235.255.197/getconf.php?session=<encrypted bytes>

https[:]//srv18.banner-add.com/show.php?session=<encrypted bytes>

https[:]//119.235.255.197/show.php?session=<encrypted bytes>

https[:]//srv18.banner-add.com/upload2.php?session=<encrypted byes>

https[:]//119.235.255.197/upload2.php?session=<encrypted byes>

HTTP header fields: Content-Type: multipart/form-data; boundary=—————————<6 random bytes>

Content-Disposition: form-data; name=”file0″; filename=”herror.log.0.done”

Content-Disposition: form-data; name=”file0″; filename=”herror.log.0.000000″

Registry key: “Software\Classes\CLSID\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}”

Registry value called “Netqps” under “Software\Microsoft\Windows\CurrentVersion\Run”

Windows Service: “Netqps”

Events: Global\R731823ef11b36c7fdd21d5763d265f9e, Global\C731823ef11b36c7fdd21d5763d265f9e, Global\P731823ef11b36c7fdd21d5763d265f9e

File created: herror.log

5 4 votes
Article Rating
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Ioan Uța

Best article yet! Really like that you guys made it an interesting read although it had 120 screenshots 😊.