The Brothers Grim The reversing tale of GrimAgent malware used by Ryuk July 2, 2021 · min to read · Malware Analysis GrimAgent Malware Ransomware Threat Intelligence Executive summary ← Blog balance of shadow universe https://www.group-ib.com/blog/grimagent/ Page 1 of 32 Ransomware activity increased drastically over the past couple of years and became the face of cybercrime by 2021. According to the “Ransomware Uncovered 2020-2021” report, the number of ransomware attacks increased by more than 150% in 2020. The attacks grew in not only number but also scale and sophistication — the average ransom demand increased by more than twofold and amounted to $170,000 in 2020. The norm is shifting toward the millions: the Colonial Pipeline allegedly paid USD 5 million to get its business back. The case propelled the question of ransomware to the top of the political agenda. In the meantime, 2021 continues to prove that no company is immune to the ransomware plague. Ransomware operators are not concerned about the industry so long as the victim can pay the ransom. The prospect of quick profits motivates new players to join big game hunting. Ransomware operations show no signs of slowing down. The gangs evolve. They change their tactics, defense evasion techniques, and procedures to ensure that their illicit business thrives. Given that ransomware attacks are conducted by humans, understanding the modus operandi and toolset used by attackers is essential for companies that want to avoid costly downtimes. Ultimately, knowing how ransomware gangs operate and being able to thwart their attacks is more cost-effective than paying ransoms. One of the underlying trends of 2021 to keep in mind is the use of commodity malware. The infamous ransomware gang Ryuk, which is responsible for many high-profile cyber heists (including the attack on the Baltimore County Public Schools system) followed suit. The most recent addition to their arsenal, which is yet to be explored, is the malware called GrimAgent. Our team did the first comprehensive analysis of the GrimAgent backdoor. It is intended mainly for reverse engineers, researchers and blue teams so that they can create and implement rules that help monitor this cyber threat closely. Group-IB’s Threat Intelligence team has created Yara and Suricata rules as well as mapped GrimAgent’s TTPs according to the MITRE ATT&CK® matrix. Introduction GrimAgent is a malware classified as a backdoor and that has been used as a prior stage to Ryuk ransomware. The ransomware family appeared in 2018 and was mistakenly linked to North Korea. Later on, it was attributed to two threat actors, FIN6 and Wizard Spider. https://www.group-ib.com/blog/grimagent/ Page 2 of 32 Given the limited knowledge about the links between Ryuk and GrimAgent, we decided to research GrimAgent samples discovered in the wild and show how GrimAgent is connected to Ryuk. The article analyzes the execution chain, TTPs, and the malware’s relevant characteristics. The first known GrimAgent sample (SHA-256: 03ec9000b493c716e5aea4876a2fc5360c695b25d6f4cd24f60288c6a1c65ce5) was uploaded to https://www.group-ib.com/blog/grimagent/ Page 3 of 32 VirusTotal on August 9, 2020 at 19:20:54. It is noteworthy that an embedded binary into the initial malware was employed and had a timestamp of 2020-07-26, the timestamp could have been altered but the dates coincide with our hypothesis about the new malware. From a functionality point of view the malware is a backdoor, but it behaves like a bot. We analyzed a completely different custom network protocol where the infected computer would register on the server side and provide a reconnaissance string of the client, after which it would constantly make requests to the C&C server asking which are the next commands to be executed. During our research we performed several tests with the aim to get the next stage payload. We infected several testing devices with different settings, but did not manage to obtain any payloads. Based on our findings, it is likely that the actor implemented different defense and delivery mechanisms to protect the integrity of its systems and ensure that the operations are flawless — which is not uncommon and we have witnessed this in the past. This means that GrimAgent developers potentially implemented threat detection systems capable of detecting sandboxes or bot requests in order to protect themselves from things such as analysis, added filters based on geolocation and blacklists/whitelists. The extreme meticulousness shown by the actors behind the malware and their attention to detail when carrying out attacks is both relevant and remarkable. According to Group-IB’s Threat Intelligence & Attribution system, Ryuk operators used different commodity malware over time (including Emotet, TrickBot, Bazar, Buer, and SilentNight) to deploy ransomware. However, the big blows suffered by Trickbot and Emotet could have prompted Ryuk operators to partner with GrimAgent. A detailed analysis of the latest TTPs used by various ransomware strains is available in the Ransomware Uncovered 2020/2021 report. Welcome to the world of Threat Intelligence Defeat threats efficiently and identify attackers proactively with a revolutionary cyber threat intelligence platform by Group-IB Learn more https://www.group-ib.com/blog/grimagent/ Page 4 of 32 Connections to Ryuk Analyzing the GrimAgent Command and Control domain revealed an interesting URL. When making a request on the domain, the Command and Control server returns a content designed for victims of the Ryuk ransomware, in addition to revealing its location on the TOR network. Command and control landing page: Fig. 1: C2 landing page Command and control landing page source code: < html > < body > < style > p:hover { background: black; color:white } < /style> < p onclick = 'info()' style = 'font-weight:bold;font-size:127%; top:0;left:0;border: 1px solid black;padding: 7px 29px;width:85px;' > contact < /p> < div style = 'font-size: 551%;font-weight:bold;width:51%;height:51%;overflo y u k < /div> < /body> https://www.group-ib.com/blog/grimagent/ Page 5 of 32 Fig. 2: Ryuk Cpanel (TOR) These findings show that the GrimAgent backdoor is being used as a part of Ryuk’s operations. We have not observed any selling or advertising related to GrimAgent on underground forums, nor any use of the malware in the infection chain of any other malware besides Ryuk. Based on the above facts we believe that this malware is used by the same TA that uses Ruyk ransomware and does not use MaaS. GrimAgent in a nutshell File Information MD5 015E5A1FA9D8151EE51D6E53E187FBB2 SHA1 163917CED2D1B33023FA47ECA8BEEC3EA871D517 SHA256 63BD614434A70599FBAADD0EF749A9BB68F712ACA0F92C4B8E30A3C4B4DB5CAF CPU 32-bit https://www.group-ib.com/blog/grimagent/ Page 6 of 32 Size 270,945 (bytes) Compiler stamp Oct 31 22:57:25 2020 Debugger stamp Oct 31 22:57:25 2020 Resource language Russian Functionality Retrieve information about the victim: IP and country code Domain Vendor Build version OS Arch Username Privileges user_id (computed to identify infected clients) AES key obtained from C2 to encrypt the communications Execute Execute shellcode (MZ launcher) Download and execute Update Execute DLL (MZ launcher trampoline) https://www.group-ib.com/blog/grimagent/ Page 7 of 32 Encryption/decryption scheme Two decryption keys used to decrypt the strings (decrypted by the same algorithm). Four keys are used: 1. Hardcoded server public RSA key: Used to encrypt the first request to the Command and Control server and register the infected client on the server side. 2. Generated client RSA public and private keys: Both stored at the config file. a. Public RSA key i. Used to compute the user_id in order to track and identify infected users. ii. Sent to C2 on the first request along with client reconnaissance; used to encrypt the AES key sent by C2. 3. Used to compute the user_id in order to track and identify infected users. b. Private RSA key: Used to decrypt the AES key received. 4. AES key: Used on command and control further communications (symmetric encryption). https://www.group-ib.com/blog/grimagent/ Page 8 of 32 Fig. 3: Execution flow diagram *Command 6 has been added on new versions of the malware, check “Dealing with newer GrimAgent versions” section. In-depth analysis https://www.group-ib.com/blog/grimagent/ Page 9 of 32 This section details the malware’s behavior. It covers the following points: Bypassing anti-analysis mechanisms Overlapping instruction obfuscation Our investigation involved dealing with anti-analysis techniques, which complicated our task of analyzing the malware in addition to breaking the IDAPro Hex-Rays display view. Basically, the execution flow has been altered with the entire code region between the pusha and popa instructions, as well as the stack usage. This code region does not perform any relevant actions while executing the binary, so we can NOP all these instructions. Fig. 4: Anti-analysis technique at WinMain Bypassing anti-analysis mechanisms Malware execution Network protocol https://www.group-ib.com/blog/grimagent/ Page 10 of 32 Once done, we can set WinMain as a function and try to visualize the pseudocode as well as the IDA graph mode. String encryption When executing the WinMain routine, the first step is to decrypt the key that will later be used to decrypt the sensitive strings needed to continue its execution. In order to decrypt the key, the malware uses a function which may vary depending on the sample. Then, decrypt some basic strings to be able to execute their most basic actions and decrypt the second key that will be used on the decryption of all the necessary strings for their complete and correct execution. IDA Python script Since, in most cases, it is better to automate than perform a task manually, we use IDA Python to ensure it is done quickly. IDAPro script: https://github.com/apriegob/GrimAgent/blob/main/IDAPro/IDAPro_string_decryptor.py The “decrypt_strings_func” and “decrypt_strings2_func” values should be changed to the memory location where they are placed. The value of key1 and key2 will be the buffers that contain the keys used by the malware (which are easily obtained through the debugger). Malware execution The execution of GrimAgent from can be divided into the following subsections. Decrypting strings and calculating indirect calls path_mutex_buffer Mutex Configuration file First launch of the malware Handling commands https://www.group-ib.com/blog/grimagent/ Page 11 of 32 Decrypting strings and calculating indirect calls This is the first step the malware must take because it needs to decrypt the sensitive and relevant strings as well as calculate the indirect calls in order to continue with its normal execution flow. path_mutex_buffer (deprecated) One of the most relevant features of GrimAgent is that it uses the last 64 bytes of the binary for two purposes: This means that after decrypting strings and calculating indirect calls, GrimAgent will read itself, and more specifically its last 64 bytes. Henceforth we will call this set of bytes “path_mutex_buffer“. Dealing with newer GrimAgent versions 1. Compute mutex name 2. Check path for store malware configuration https://www.group-ib.com/blog/grimagent/ Page 12 of 32 Fig. 5: GrimAgent sections Mutex The function that creates the mutex will iterate over the 64 bytes, checking if the value is alphanumeric and, if it is not, the function will try to transform it into an alphanumeric value. If the name of the mutex created is not valid, it will create a mutex with the hardcoded name “mymutex”. In case of an error, the execution will terminate. https://www.group-ib.com/blog/grimagent/ Page 13 of 32 Fig. 6: Computing Mutex Configuration file The malware will check if it has a specific path hardcoded. The path corresponds to the buffer that it read in its last 64 bytes (“path_mutex_buffer“). Custom path syntax (deprecated): To do so, the malware will make recursive calls to check if the SHA256 of the different writable paths and filenames matches with the buffer retrieved. In terms of writing its config, the malware has three possibilities: SHA256 [unicode(FilePath)] + SHA256 [unicode(Filename)] → length of SHA256 x 2 = 64 (0x40) bytes https://www.group-ib.com/blog/grimagent/ Page 14 of 32 With the config path defined, the malware checks if there is a valid config file: A valid configuration file will contain three different objects: First launch of the malware When the malware is executed, the first step is to decrypt the strings and create a mutex based on the last 64 bytes (path_mutex_buffer), but there are cases where GrimAgent binaries have been compiled without these last bytes. Unable to read its last 64 bytes This execution branch can only occur on malware first execution. If we enter this execution branch, the malware will perform the following actions: Custom Path → SHA256[unicode(Path)] + SHA256[unicode(FileName)]: The malware will start from “C:\” path, and it will call a function recursively (find_custompath) in which it will compute the SHA256 of the different directories to see if it matches with the first 32 bytes of “path_mutex_buffer” and, if It does, it will try to find the filename by performing the SHA256 of every filename in the matched directory by checking the next 32 bytes. 1. 2. Hardcoded path 1 → C:\Users\Public\config; used if unable to get the custom path. 3. Hardcoded path 2 → C:\Users\Public\reserve.sys a. This path will be used when a custom path is matched with the character ‘ * ‘, for example: “C:\Users\*” b. There is a string “reserve.exe” that will replace the last 3 characters from “exe” to “sys” during execution time. If the config file is less than 0x32 bytes in size, it has no valid config. Otherwise, the config will be considered as valid and continue the execution to an infinite loop that will perform C2 requests asking for new commands to execute. Generated client public RSA key: to compute user_id and encrypt the AES key that the C2 will send. 1. 2. Generated client private RSA key: to decrypt the AES key sent by C2. 3. AES encrypted key received from C2: to encrypt/decrypt C2 communications. Retrieve the writable path Compute the new path_mutex_buffer based writable path and filename Copy itself into the writable path https://www.group-ib.com/blog/grimagent/ Page 15 of 32 Reconnaissance At this point, the malware will have been able to get its path to store its configuration file where it will store the keys necessary for its execution and communication with C2 as well as for creating a mutex to avoid conflicts if it is executed several times. GrimAgent will then perform a system reconnaissance and collect device information. It will collect the following fields: To retrieve fields such as the internet IP address and country code, which will append into the reconnaissance string, the malware will use public resources. In our case, this means performing a GET request to api.myip. RSA key generation and user_id Append the new path_mutex_buffer at the end of the new copied binary Set persistence of the copied binary Task scheduler Registry key Execute copied malware Delete old sample Exits Country code (api.myip.com) IP (api.myip.com) Vendor Domain Build Version OS Architecture Username Privileges (A/U) A = Admin U = User https://www.group-ib.com/blog/grimagent/ Page 16 of 32 GrimAgent calls a function in order to compute a user_identifer (used to identify the infected user) and generates the client RSA public and private keys: The next screenshot shows the finished string obtained by the malware with the value of user_id added at the end. Fig. 7 – Complete victim reconnaissance. NB: Country code and IP address values are invalid because we executed the malware in a controlled environment without internet access. Example of a reconnaissance string:
vendor=1. Generates the client RSA public and private keys by using CryptGenKey. Retrieves the new generated client RSA public key in PEM format (with the headers ‘—– BEGIN PUBLIC KEY—–‘ and ‘—–END PUBLIC KEY—–‘). 2. Computes the hash for that key, which generates a random id to be included in the reconnaissance string (used as a user identifier): This user_id would be different for every execution but, to be able to identify the infected user and not generate a random key each time, the new key will be stored and used to compute the user_id when needed. 3. 4. Retrieves the generated client RSA private key. 5. Stores both public and private keys in the config file. https://www.group-ib.com/blog/grimagent/ Page 17 of 32 Next, the malware sends this information along with the generated client public RSA key to the Command and Control server and waits until the reply contains ‘fr’ or ‘ar’ as the first characters in the reply. Only if the first characters received are ‘fr’ will the malware store the reply as hex values. The reply from C2 will be a symmetric encryption key (AES) that the malware will use from then on in order to interact with the Command and Control server. The malware will JMP to the ‘exist_conf‘ location and proceed with the execution. Handling commands We land in this section once the malware obtains a valid configuration and is about to enter an infinite loop where it will make requests for new commands to the Command and Control server. For GrimAgent, the configuration file will be valid if it is ≥ 32 bytes. Reading config: the AES Key In this case, the malware will decrypt and import the AES key received from the C2 which will be in the config file. Then, the key will be used in order to encrypt and decrypt subsequent communications between the infected client and the C2. Commands GrimAgent will start an infinite loop (while 1) in order to request the next commands to execute to the Command and Control server. The loop is made up of three points: Command table: Command id Functionality 1 Execute 2 Execute Shellcode (MZ Launcher) 3 Download and Execute (Schtask) 1. C2 request and decryption 2. Execute command 3. Sleep [180-190] seconds and start again https://www.group-ib.com/blog/grimagent/ Page 18 of 32 Command id Functionality 4 Update 5 Execute DLL (MZ trampoline) 6 Download and Execute (ShellExecuteW) The commands and other malware features have been updated in newer versions of the malware, explained at Dealing with newer GrimAgent versions section. On receiving the C2 reply, the malware parses the command and executes it. Command 1: Execute At this point, the malware uses the same logic for execution that we have already seen. It creates a task with a random name and length 8. The task will be executed through the task scheduler with maximum privileges. Afterwards, the malware removes the task to avoid being detected. Command 2: Execute shellcode (MZ launcher) When command ‘2’ is received, the malware parses the shellcode received as an argument. The malware checks for ‘\\’ and ‘x’ bytes, which are common delimiters used in binary data. After parsing the shellcode, the malware calls the ‘drop_and_execute_shellcode’ function, which drops an executable MZ (embedded into initial binary) with the appended shellcode into a writable directory that will act as a launcher of the received binary data. The embedded MZ contains the compiler stamp from July 2020. This point in time could be when the first version of GrimAgent appeared. Moreover, it contained another embedded MZ with the same behavior, but designed for 64b architecture systems. The 64b launcher has nearly the same time stamp. https://www.group-ib.com/blog/grimagent/ Page 19 of 32 Fig. 8: MZ Shellcode/DLL launcher properties The most interesting part of the new executable is how it executes the shellcode using sentinel bytes, with two possibilities: In both cases, the malware parses the sentinel bytes and executes the shellcode/DLL. The sentinel bytes are used to calculate where the shellcode is located and be able to execute the malicious code. Once dropped, this MZ launcher will be executed through task scheduler with a random name of 8 alphanumeric characters. By executing this command, the malware will also try to privesc by executing the payload as the highest privileges possible. It will then sleep for 195-205 seconds, after which the malware will delete the newly created task. Command 3: Download and Execute (Schtask) This is the basic functionality found in many malware families. It is the common download and execute command functionality. 1. @@@@@@@ + shellcode 2. @@@@@@d + DLL path + ‘:’ + arguments (used at command 5) https://www.group-ib.com/blog/grimagent/ Page 20 of 32 In order to receive the payload after the request of a new command, GrimAgent performs a request to obtain the payload to the specified URI received as an argument. The payload will be dropped into a writable folder and executed through the task scheduler, as in command 1. Command 4: Update When this command is received, the malware updates itself by dropping the new updated GrimAgent file into its current directory and appending padding bytes (this action will change the file hash, used as a defensive mechanism) as well as the same path_mutex_buff at the end of the new binary. By appending these bytes, the malware creates the same mutex and checks for the same custom path for the config file. When finished, it runs the new updated binary (ShellExecuteW) and exits the process. Fig. 9: Updated GrimAgent Command 5: Execute DLL (MZ launcher trampoline) This command is very similar to command 2. The function is called with three parameters: 1. Payload size to be received by C2 on the next request 2. DLL arguments 3. Bool if the DLL is 32b or 64b https://www.group-ib.com/blog/grimagent/ Page 21 of 32 As in previous commands, the malware will receive the URI to download the payload and, once executed, it will obtain a writable directory in order to be able to drop payloads. It will then perform a GET request to the URI in order to download the DLL to be executed. Next, the malware will drop the DLL launcher (MZ) with the sentinel bytes “@@@@@@d” and append (at the next bytes) the path of the DLL to be executed + “:” + arguments. The file extension of the DLL launcher will be “.exe”. On the other hand, it will also drop the DLL to be executed (with the file extension .dll). Sentinel bytes syntax: @@@@@@d DLL_path : arguments Once everything has been prepared, the malware will execute the launcher through the task scheduler and this will act as a stepping stone for executing the DLL, sending the previously obtained arguments. Fig. 10: DLL execution Command 6: Download and Execute (ShellExecuteW) https://www.group-ib.com/blog/grimagent/ Page 22 of 32 This command obtains the payload by making a request to the URI (received as the command argument), drops it into the current directory and executes through a ShellExecuteW call. This command was found in the newer versions of the malware. Dealing with newer versions of GrimAgent While working on this article, we found some new GrimAgent samples in the wild with variations from the version explained above. As a sample of the different changes detailed below, we have the hash SHA256 – D6EE553F52F20127301F737237B174EF6241EC9049AB22155DCE73144EF2354D, which presents variations such as: Hardcoded mutex name “T10”: This malware version does not generate a mutex name; instead, it creates one based on a hardcoded value. Hardcoded config path “\Users\Public\microsoft.cfg”: As with the mutex, a path is no longer defined for the configuration; instead, it is hardcoded in the sample. Copies itself into a hardcoded path and filename (“\Users\Public\svchost.exe”), and then sets persistence through the registry Run key. The execution is done directly through the call ShellExecuteW&country= &ip= &domain= &build_version= &systemOS= ®istry=<32/64>&username= &privileges= &id=