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:
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:
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.
After the decryption operation is complete, the information reveals DLL names, function names, registry keys, HTTP methods, and so on:
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:
More relevant strings are decrypted by the same decryption algorithm as above (Algorithm1):
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):
The MD5 algorithm is used to hash the “Netqps” string by calling the CryptHashData function:
The hash value is extracted via a CryptGetHashParam function call:
The following bytes represent the hash value extracted above:
Using the hash value, the process constructs a registry key name by calling the wsprintfA function:
The SetSecurityDescriptorDacl function is utilized to set information in a DACL (discretionary access control list):
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):
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):
The file tries to open a service called “Netqps”, which doesn’t exist at this time:
The sample is made persistent by creating a new service called “Netqps”, with the “Network QoS Provisioning” as the display name, as shown below:
The description of the service is changed by calling the ChangeServiceConfig2A API:
The following configuration information (0x2 – SERVICE_CONFIG_FAILURE_ACTIONS) is modified using the same function:
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:
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:
The new entry is called “Netqps”, and the malware is supposed to run with the “-run” parameter, as shown in the next figure:
The proof that the value was successfully added to the “Run” registry key:
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:
The CreateEventW function is used to create 2 unnamed event objects. One of the function calls is displayed in figure 24:
A new thread is created by calling the CreateThread API:
One of the unnamed events created earlier is set to the signaled state by calling the SetEvent function:
“-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:
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:
Because the registry key doesn’t exist, it is created by calling the RegCreateKeyExA function:
Moreover, the malware creates a subkey called “ShellFolder” under the freshly created registry key, as follows:
One of the C2 servers (https[:]//srv18.banner-add[.]com) is encrypted with Algorithm1:
After the encryption is complete, the data looks like in the following buffer:
The encrypted C2 server is copied into a value called “ProgID” under the “Software\Classes\CLSID\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}\ShellFolder” registry key:
The confirmation that the value was successfully created by the sample:
The process repeats the encryption operation, the only difference being that it’s encrypting two C2 servers (one domain and one IP address):
The “ProgID” value is modified to the new encrypted content, as shown in 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:
The file generates 10 random bytes by calling the CryptGenRandom function:
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):
After the encryption is complete, the buffer is transforming to the following:
The encrypted buffer is saved at {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\ShellFolder\(Default) via a call to the RegSetValueExA function:
The confirmation that the value has been successfully added:
The User-Agent that is currently being used is extracted using the ObtainUserAgentString API, as shown in the next figure:
The malware opens the access token associated with the current process (the relevant parameter here is 0x20 – TokenAdjustPrivileges):
The malicious process verifies if it has “SeDebugPrivilege” privilege (usually, it can be used to inject code into other processes owned by other accounts):
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:
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:
The hash value is retrieved via a CryptGetHashParam function call, and it’s highlighted in the following picture:
The malware opens the kernel32.dll file located in the System32 directory:
The created, last accessed, and last modified times of kernel32.dll are retrieved using the GetFileTime API:
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:
The technique worked, and now we can observe that the timestamps have been changed:
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:
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:
The binary initializes the use of the WinINet functions by calling the InternetOpenW API with the User-Agent retrieved earlier:
The internet connection receive and send timeouts are set to 60 seconds using the InternetSetOptionW function:
The binary opens an HTTP session for www.microsoft.com by calling the InternetConnectW API with the corresponding parameter:
It performs a GET request using the HttpOpenRequestW API, as shown in figure 58:
The HttpSendRequestW function is used to send the request to the HTTPS server, as shown below:
The malware is extracting the HTTP status code, and it’s expecting to be 200, otherwise, the process finishes its execution:
As before, the process generates 32 random bytes by calling the CryptGenRandom function:
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:
A GET request to /login.php?d=<C2 server>&session=<32 random chars generated earlier> is performed:
There is a call to the InternetQueryOptionW function for determining the security flags for the handle (0x1f = INTERNET_OPTION_SECURITY_FLAGS):
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):
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):
More data is read using the same function, however it’s expecting a large file/configuration because the number of bytes is 0xc800 (51200):
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:
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:
The hash value is extracted by calling the CryptGetHashParam API:
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):
16 random bytes are generated using the CryptGenRandom API:
The buffer from above, which contains the information regarding the malware family, is encrypted using Algorithm2:
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>):
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):
The encrypted content is saved at {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\InProcServer32\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}, as shown in the next figure:
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:
Now, if the server’s response wasn’t empty, the next buffer is encrypted using Algorithm1 (observe the system date and time):
As in the first case, the encrypted buffer is saved at {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\InProcServer32\{731823ef-11b3-6c7f-dd21-d5763d265f9e3}:
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:
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:
The following information is encrypted using Algorithm2:
The malicious binary constructs the following URL that will be used to exfiltrate information, as we’ll see in the following paragraphs:
Other 16 bytes are generated using the CryptGenRandom API:
Algorithm2 is utilized to encrypt the buffer presented in figure 90:
The CryptGenRandom function is used to generate 6 random bytes that will be utilized to construct some HTTP header fields:
The process creates a new HTTPS request handle:
The HttpAddRequestHeadersA API is utilized to add a request header to the handle (note the randomly-generated bytes):
The request is sent to the C2 server by calling the HttpSendRequestExW function:
The application sends more data to the C2 server by calling the InternetWriteFile API:
The buffer that was encrypted earlier (figure 86) is exfiltrated to the C2 server:
The exfiltration is completed by sending the following string:
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:
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):
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:
The file retrieves a handle to the explorer.exe process by calling the OpenProcess API:
The access token associated with explorer.exe is retrieved by calling the OpenProcessToken function (0xb – TOKEN_WRITE):
The sample uses the ImpersonateLoggedOnUser API to impersonate the security context of the logged-on user, as shown in 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:
The CredEnumerateW function is used to enumerate the credentials from the user’s credential set:
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):
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:
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:
If the file exists, the malware reads it using the ReadFile function. The file content is encrypted using Algorithm2:
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:
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):
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:
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):
The ConnectNamedPipe API is utilized to enable the current process in order to wait for a client process to connect to the named pipe:
The malicious file has the capability of creating new processes based on the C2 server’s instructions:
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:
The following functions are called: GetSystemTime, GetCurrentThreadId and GetThreadDesktop. The desktop name (“__efb37f_21_76“) is retrieved by calling the GetUserObjectInformationA function:
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:
The process tries to open a value called “{30869038194918520211989843}” from {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\InProcServer32:
The buffer with the information collected earlier is encrypted using Algorithm1:
The encrypted content is saved at {731823ef-11b3-6c7f-dd21-d5763d265f9e3}\InProcServer32\{30869038194918520211989843}:
“-remove” Parameter
The binary opens the “Netqps” service created earlier by calling the OpenServiceA API:
The SHDeleteKeyA function is used to delete the key created in the first case, as shown in the next figure:
The 2nd persistence mechanism is also eliminated using the RegDeleteValueA API (the entry added under the “Run” registry key):
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:
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
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
Best article yet! Really like that you guys made it an interesting read although it had 120 screenshots 😊.