The expert investigators at GoSecure Titan Labs have found, analyzed and created signatures to detect a new malware that they call Gameloader – since it and its variants contain numerous strings that attempt to disguise themselves as video games. The file Titan Labs used for their research was a Rich Text Format (RTF) file entitled New Purchase Order from Alibaba.doc provided by the GoSecure Titan Inbox Detection and Response (IDR) team. The RTF file downloads a 32-bit .NET loader, which loads FormBook Stealer. The following is an in-depth analysis of the Gameloader.
Analysis
Infection Chain
The initial infection vector is via malspam containing links to cdn.discord.com. Using Discord’s content delivery network (CDN) as a malware distribution system continues to grow in popularity among threat actors. The email (51875bd4157c2755a6af3ce92218ea03), shown in Figure 1, purports to be from Alibaba, stating that the user’s purchasing order has been received and that they can download it by clicking the link labelled DOWNLOAD PURCHASE ORDER, which downloads a malicious RTF file (dd59a0508d8c8327a0a326a8e50bc508) from hxxps://cdn[.]discordapp[.]com/attachments/ 882541551555846144/882541673186484224/New_Purchase_Order.doc.
Figure 1: Malspam
Figure 2 compares a standard RTF file, shown on the left, to New_Purchase_Order.doc, shown on the right. A standard RTF file consists of RTF control words whereas New_Purchase_Order.doc clearly does not, indicating that it is heavily obfuscated.
Figure 2: Standard RTF vs. Obfuscated RTF
As depicted in Figure 3, the tool rtfdump.py displays the streams contained in New_Purchase_Order.doc, their nesting level, and also the amount of hexadecimal characters in each one. We can see that streams 1, 2, and 3 have a high number of hexadecimal characters, which warrants a closer examination.
Figure 3: rtfdump Output
As displayed in Figure 4, by using the s flag to investigate specific streams and the H flag to decode the hexadecimal characters, we find that stream 3 contains the string equATION.3, which invokes Equation Editor. By exploiting CVE-2017-11882, a buffer overflow vulnerability in Microsoft Equation Editor, the RTF file downloads GameLoader (e3488000bfab3d82a4fd31206ba01954) from hxxp://lg-tv[.]tk/bankzx.exe. Note that we also used rtfdump.py’s S flag to shift the hexadecimal bytes by one nibble. Due to the implementation of the RTF parser, it is possible to make the parser ignore a nibble of a byte, which has the effect of shifting the starting point of hex-encoded data by one nibble, which is exactly what the threat actor did in this case to further obfuscate the file.
Figure 4: Stream 3, hex-decoded
GameLoader
Figure 5 depicts the functionality of GameLoader’s first stage. Line 105 begins with a call to function fDangNhap.X0203, which decrypts the second-stage loader. fDangNhap.X0203 receives two parameters, Desc.String1, which is a string in GameLoader’s resource section that contains the encrypted second-stage loader, and the string Z7FE68C5, which is the decryption key. We can see that function fDangNhap.X0203’s implementation, beginning at line 136, iterates through both the encrypted string and the decryption key, subtracting the integer value of the current character in the key from the integer value of the current character in the encrypted string. It then converts the resulting value back to a character and appends it to a string. The resulting string is a base64-encoded, 32-bit .NET DLL, which gets passed to Convert.FromBase64String, back on line 105. The decoded DLL (36fa916ea33da29b017dc9b363834024), GameLoader’s second stage loader, is then passed to the function this.X0204, the implementation of which begins at line 129. It uses Assembly.Load to reflectively load the DLL into the application domain of GameLoader. It then gets the Type object Meshomatic.Ms3dLoader from the newly loaded assembly and stores it in this.Linear. The function this.X0202 is then called on line 106. As shown in its implementation, on line 152, this.X0202 calls the Activator.CreateInstance method to create an instance of Meshomatic.Ms3dLoader, essentially executing the second-stage loader. Activator.CreateInstance accepts two parameters: the object type to instantiate and an array of arguments to be passed to the instantiated object. In the bottom of Figure 5, where local variables are shown, we can see that the array contains 3 strings: 4950726F6475636572436F6E73756D6572436F6C6C65637469, 6A7067, and Tetris.
Figure 5: GameLoader’s First Stage Loader
Following the execution of the instantiated object, we can step inside the second-stage DLL, internally named ColladaLoader, and see that the first method executed in Meshomatic.Ms3dLoader is Ms3dLoader.SelectorX, which is passed the 3 aforementioned arguments. Figure 6 illustrates that besides sleeping for a random amount of time, Ms3dLoader.SelectorX calls Ms3dLoader.XeH on line 48. Ms3dLoader.XeH simply converts the hexadecimal string stored in ugz1 to the ascii string IProducerConsumerCollecti. This string, along with the variable projname, which stores the string Tetris, is then passed to Ms3dLoader.xyz.
Figure 6: ColladaLoader’s Injection Function
As shown in Figure 7, Ms3dLoader.xyz retrieves the bitmap image IProducerConsumerCollecti from Tetris.Properties.Resources.
Figure 7: Ms3dLoader.xyz
The bitmap image, depicted in Figure 8, does not display an image of any kind, only seemingly randomized pixels.
Figure 8: Bitmap Image Containing An Encrypted DLL
Back on line 48, in Figure 6, the bitmap image gets stored in a variable that is passed to Ms3dLoader.cba on line 49. Ms3dLoader.cba iterates through the bitmap image, converting the Argb value of each pixel into bytes and storing them in an array. It then creates a second array, using the first 4 bytes of the first array as the size of the second. Starting from the fifth byte, it copies the bytes in the first array to the second array until the second array is fully populated. This byte array is then passed to Ms3dLoader.fgh, which is responsible for decrypting the array. The return value from Ms3dLoader.XeH, which is the string jpg, is also passed to Ms3dLoader.fgh. Once inside Ms3dLoader.fgh, shown in Figure 9, the string jpg is converted to a byte array, stored in the variable bytes, and used as the decryption key. The array is decrypted by XORing each of its bytes with a key byte, and also with the value stored in the variable num, which is 0x9d. This value was created by XORing the length of the encrypted array minus one with 112. The decrypted array is GameLoader’s third-stage loader, a 32-bit .NET assembly internally named CF_Secretaria (d8e57e7bf8dfe611427511dcc5ae2ec8). Once again, Assembly.Load is used to reflectively load the DLL. Next, ColladaLoader calls Assembly.GetTypes, which returns an array of all the types defined in the loaded assembly, and stores the twenty-first type in a variable. It then uses Type.GetMethods to return an array of the all methods defined for the specified type, and stores the sixth method in a variable, which it then executes with a call to MethodInfo.Invoke.
Figure 9: ColladaLoader’s Decryption Function
Again, we follow the execution to step inside the invoked method. However, CF_Secretaria is protected with .NET Reactor, a code obfuscation tool designed to protect intellectual property, which greatly hinders analysis. To overcome this, we save CF_Secretaria to disk, then use the .NET deobfuscator de4dot to improve the readability of the code. The executed method belongs to a class named FrmIntegrante. Figure 10 displays the method responsible for initializing the class’s fields. The method Class6.smethod_0 retrieves the byte array zdljr from CF_Secretaria’s resource section. The byte array is passed, along with the string YgSqwuwwJHlcE, to Class6.smethod_5, which is a decryption function that is exactly the same as ColladaLoader’s decryption function, shown above in Figure 9. Of course, the string YgSqwuwwJHlcE is the decryption key. The decrypted array is then passed to Class6.smethod_4, which removes the first 16 bytes, before being stored in FrmIntegrante.byte_0. The resulting array is a 32-bit executable, FormBook Stealer (4c1f6f8f4bf9678c49d6ea74baca3576).
Figure 10: FrmIntegrante’s Initialization
Also depicted in Figure 10 are a set of Windows API functions and their corresponding libraries that are commonly used for process hollowing, a technique where a payload is injected and executed in the context of a selected process. Each API function is passed to FrmIntegrante.smethod_7, which is stored in FrmIntegrante.delegate{number}_0. From Figure 11, which displays FrmIntegrante.smethod_7’s implementation, we see that the first parameter, a library name, is passed to FrmIntegrante.LoadLibraryA, a pointer to the imported function kernel32.LoadLibraryA, which returns a handle to the specified library. The handle is passed, along with a function name stored in FrmIntegrante.smethod_7’s second parameter, to FrmIntegrante.GetProcAddress, a pointer to kernel32.GetProcAddress, which returns the address of the specified function. This is then converted to a delegate and returned. Therefore, FrmIntegrante.delegate{number}_0 is a reference to a specified Windows API function.
Figure 11: API Delegate-Building Function
CF_Secretaria implements a switch statement, displayed in Figure 12, to determine into which process to inject its payload, which is FormBook in this instance. Case zero returns string_10, which is the path of the currently executing assembly, GameLoader, while cases 1, 2, and 3 return the paths of MSBuild.exe, vbc.exe, and RegSvcs.exe, respectively. In this instance, case 0 is selected and GameLoader proceeds to inject the payload into its own process. The case that is selected is determined by the parameter int_12, which is set by the threat actor in CF_Secretaria’s configuration.
Figure 12: Process Injection Options
Figures 13 and 14 display the function responsible for the process injection. For readability, we have added the API call that each delegate refers to. The function begins by calling kernel32.CreateProcessA. The first parameter, which specifies the process to create, is string_10, the result from the aforementioned switch statement. The sixth parameter, containing 134217732U, specifies flags used to set the properties of the created process. 134217732U (0x08000004) sets the flags CREATE_SUSPENDED and CREATE_NO_WINDOW to true. Thus, the process will be created in suspended mode and will be executed without a console window. Next, either kernel32.GetThreadContext or kernel32.Wow64GetThreadContext will be called, depending on whether the process is 32-bit or 64-bit, and a CONTEXT structure of the process’s main thread will be retrieved. The base address of the process is parsed out of the CONTEXT structure and passed to kernel32.ReadProcessMemory, which is used to retrieve the base virtual address of the process’s view. The base virtual address is then passed to ntdll.ZwUnmapViewOfSection, which unmaps, or hollows, the entire view from the process’s virtual address space. The virtual address space is no longer reserved and is now available to map other views. Next, it allocates space within the process’s virtual address space by calling kernel32.VirtualAllocEx, then calls kernel32.WriteProcessMemory to inject the payload into the newly allocated memory. Either kernel32.SetThreadContext or kernel32.Wow64SetThreadContext is called to change the process’s thread context so that it will now point to the injected payload. Finally, kernel32.ResumeThread is called to resume the thread and thus, execute the injected payload.
Figure 13: Process Injection Function
Figure 14: Process Injection Function Continued
Conclusion
GameLoader is a multi-stage, steganographic loader that has been observed loading various types of commodity malware, including AgentTesla, LokiBot, and Snake Keylogger. At the time of writing, over 1000 GameLoader samples have been uploaded to VirusTotal.
The signatures to detect the emerging threats discussed in this report were developed through the close monitoring, analysis and reverse engineering conducted by GoSecure Titan Labs, as part of the GoSecure Titan Managed Detection and Response (MDR) solution, which includes GoSecure Titan Inbox Detection and Response (IDR) tools for users to share suspicious emails in real-time with our professional threat hunting team.
Malware Analyst: Sean Mahoney
Indicators of Compromise
+------+---------------------------------------------------------------------------------------------------------+---------------------------+ | Type | Indicator | Description | +------+---------------------------------------------------------------------------------------------------------+---------------------------+ | md5 | 51875bd4157c2755a6af3ce92218ea03 | Malspam Email | | md5 | dd59a0508d8c8327a0a326a8e50bc508 | RTF File | | md5 | e3488000bfab3d82a4fd31206ba01954 | GameLoader | | md5 | 36fa916ea33da29b017dc9b363834024 | GameLoader's Second Stage | | md5 | d8e57e7bf8dfe611427511dcc5ae2ec8 | GameLoader's Third Stage | | url | hxxps://cdn[.]discordapp[.]com/attachments/882541551555846144/882541673186484224/New_Purchase_Order.doc | GameLoader Download URL | | url | hxxp://lg-tv[.]tk/bankzx.exe | RTF File Download URL | +------+---------------------------------------------------------------------------------------------------------+---------------------------+
Detection
rule other_rtf_obfuscated_0 { meta: author = "Titan Labs" company = "GoSecure" description = "Obfuscated RTF File" created = "2021-09-28" hash = "dd59a0508d8c8327a0a326a8e50bc508" os = "windows" type = "other" tlp = "white" id = 1 strings: $magic = "{\\rtf" $ansi = "ansi" $deflang = "deflang" $windows = "windows" $deff = "deff" condition: $magic at 0 and filesize < 1MB and not $ansi in (5..20) and not $deflang in (5..20) and not $windows in (5..20) and not $deff in (5..20) } rule malware_game_loader_0 { meta: author = "Titan Labs" company = "GoSecure" description = "GameLoader" hash = "e3488000bfab3d82a4fd31206ba01954" created = "2021-09-28" os = "windows" type = "malware.loader" tlp = "white" rev = 1 strings: $decryptyion_routine = { 00 02 07 28 ?? ?? ?? ?? 28 ?? ?? ?? ?? 03 07 03 6F ?? ?? ?? ?? 5D 17 58 28 ?? ?? ?? ?? 28 ?? ?? ?? ?? 59 0C 06 08 28 ?? ?? ?? ?? 0D 12 ?? 28 ?? ?? ?? ?? 28 ?? ?? ?? ?? 0A 00 07 17 58 0B 07 02 6F ?? ?? ?? ?? FE 02 16 FE 01 13 ?? 11 ?? 2D ?? } condition: uint16(0) == 0x5a4d and uint32(uint32(0x3c)) == 0x00004550 and $decryptyion_routine }