Deep Dive into UPAS Kit vs. Kronos

By Mark Lechtik

Introduction

In this post we will be analyzing the UPAS Kit and the Kronos banking Trojan, two malwares that have come under the spotlight recently due to the back story behind them.

Background

In May 2017, WannaCry wreaked havoc in over 150 countries and brought down companies across all industries. Luckily, the attack was brought to a halt by the British security researcher, Marcus Hutchins, through the discovery of the first of several kill-switches.

The tide of thanks rapidly turned, however, when Hutchins was then arrested and investigated by the FBI for his alleged role in creating and distributing a previous and unrelated malware, the Kronos banking Trojan. But Hutchins’ woes didn’t end there, though, for he has recently come under renewed investigation for being the supposed author of another malware, UPAS Kit.

Until now, although much analysis has been done on Kronos, there is no online availability for the same on UPAS Kit. So Check Point Research decided to take a closer look.

Disclaimer: Our research does not seek to imply or to make any claims with regards to Marcus Hutchins’ role, or otherwise, to these two malwares, but is rather a mere comparison between them. Neither does our research show any conclusive evidence that point to whether Kronos and UPAS Kit were written by the same person.

 

Initialization Phase

Resolving ntdll.dll functions

UPAS Kit makes usage of multiple low-level ntdll functions and resolves their addresses during run-time. While it may serve as a means to thwart analysis, this is not a very sophisticated trick on its own. The procedure responsible for this loading action iterates over a table of functions containing entries of the following structure:

          typdef struct _ntdll_function_entry{

                         CHAR *function_name;

                        PVOID function_address;

            }ntdll_function_entry;

It simply takes the string field of each entry and resolves the corresponding address using the Win32 API function GetProcAddress. This can be seen in the following figure:

Figure 1: Resolution of ntdll function by UPAS Kit]

In fact, a similar method is used in the Kronos malware to achieve the same goal. However, in this case the function names are not kept in cleartext in the binary, but rather as string hashes. Also, some of the resolved functions serve the purpose of being utilized as syscalls, thus making it a lot harder to detect the malware’s activity, whether it is by sandboxes\emulators or even manually. To do this, Kronos leverages a slightly different function entry struct, as shown below:

         typedef struct _ntdll_function_entry{

                     CHAR *function_name_hash;

                      PVOID function_address;

                       DWORD encoded_syscall_number;

                       DWORD is_used_as_syscall_flag;

 }ntdll_function_entry;

This certainly doesn’t imply that the latter is an extended mechanism of the first method, however if we compare the order of loaded functions (i.e. the order in which the table entries reside within the binary), it can be seen that there is some overlap between the two cases.

Figure 2: Comparison of loaded ntdll functions order.

Anti-VM

In order to avoid execution in analysis environments, the malware employs two techniques. The first one avoids detection by the ThreatExpert sandbox, whereby the system volume serial number is retrieved using the function GetVolumeInformationW, and checked against the value 0xCD1A40 (which corresponds to the aforementioned sandbox). The second technique is fairly well-known, and that is a check of VMWare’s artifact in a response from a virtual I/O port used for communication between the guest and host. It’s noteworthy that once an unwanted environment is detected by the malware, it responds by spawning an error box with the message shown below:

Figure 3: Anti-VM techniques used by UPAS Kit.

Also, it should be noted that the equivalent checks made by Kronos differ quite a lot. These work to seek for the existence of various processes or loaded modules in the malware’s address space that might indicate the nature of the environment in which it’s executed. These types of checks cover more scenarios than the former case, which may imply that the evasion procedures were written by different authors, or the same one taking a different approach to the problem.

Global Mutex

The mutex name generated by the bot is the result of the action – MD5(system_volume_serial || “LPLl3h3lDh1d3djP7P3”). In the event there was an error in the generation of the mutex name, a hardcoded value (“A5DEU79”) will be set to it.

Figure 4: Mutex name generation.

In this case, a similarity can be spotted between Kronos and UPAS Kit in the implementation of the MD5 function, as indicated by Ignacio Sanmillan (@ulexec) from Intezer. But what’s more evident is that it creates a mutex name in a similar manner, by calculating MD5(system_volume_serial), and in case this fails assigns it to MD5(“Kronos”).

 Self-Installation

In order to remain persistent, UPAS Kit conducts several common actions.

First it copies itself into a new directory under %APPDATA%, named ‘Microsoft’ as well as to the %TEMP% directory. The name of the copied file will be the first seven characters of the global mutex name described above for %APPDATA%, and the same for %TEMP% only with “_l.exe” and “_a.exe” appended to it. Then, the current file name will be checked and compared against the newly generated name, such that if the two don’t match then the malware will get executed from the new path in %APPDATA%. If the check succeeds (i.e. at the second time the malware runs from the %APPDATA% path), the current file path will be written to the well-known registry run-keys Software\Microsoft\Windows\CurrentVersion\Run (under both HKLM and HKCU), where the name of the key is identical to the name of the copied file. Finally, the malware will establish the current system architecture using the function IsWow64Process, or GetNativeSystemInfo if the former is not available, and return it to the main function.

Figure 5: Architecture check.

The naming convention used for both the file, registry key and mutex is similar for Kronos, where it uses the first eight characters of MD5(system_volume_serial) for this purpose.

 Process Injection

The injection conducted by the malware depends on the system architecture. In 32 bit systems it would create the ‘explorer.exe’ process and inject its own image into it, whereas in 64 bit systems it would do so for the 32 bit version of ‘iexplore.exe’, which resides at “%ProgramFiles(x86)%\Internet Explorer“. The injection function itself gets a PID and the main thread handle of the target process, as well as an address of the function to execute after the injection takes place.

In order to conduct a successful injection, UPAS Kit uses a simple trick. First it copies its current virtual image to a buffer, after which it attempts to allocate memory with the image’s size at an arbitrary address in the remote process. The retrieved base address of the allocation will be used to relocate the injected image, which resides at the copied buffer, and write it to the target process. Then, it will prepare a hardcoded call stub and overwrite three of its DWORDs so that it will call the function that should be executed upon injection. These DWORDs are outlined in the following figure:

Figure 6: Call stub and replaced bytes.

The struct that is pushed on the stack serves to convey some parameters that should be used later by the hook functions. These parameters include:

         typedef struct _config_struct{

                     DWORD whitelisted_process_pid;

                     CHAR whitelisted_run_key_name[16];

                     wchar_t whitelisted_malware_binary_path[260];

                     wchar_t mutex_name[260];

                     DWORD ntdll_load_status;

                     DWORD some_flag;

         }config_struct;

 

Finally, in order to trigger the execution of the requested function in the remote process, the malware will set the entry point of the remote process by adjusting the value of EAX in the Context struct to that of the call stub function, and then resume execution by calling the NtSetContextThread function. If this fails, it will attempt to spawn the target function (and not the call stub) directly with the CreateRemoteThread function.

Figure 7: Beginning execution in the injected process.

In contrast to the above, Kronos conducts a different type of injection, as described here. Having said that, it is possible to notice a similarity between both injection implementations, in that both present an attempt to elevate the malware’s process token to SeDebugPrivilege, which is not mandatory for the injection to succeed. The call for the token elevation function, as well as the function itself (which is identical in both binaries), are shown below:

Figure 8: Injection comparison between Kronos and UPAS Kit.

Injected Payload

Once again, the payload executed after injection will differ depending on the underlying system architecture. For 32 bit processes the injected payload will carry out the following actions:

  • Assign global variables based on the config_struct passed to it (as described in the previous section)
  • Load dll‘s raw image
  • Create a mutex using the mutex name set by the injecting process
  • Check if an uninstall flag is on, and if not –
    • Creates a thread to inject itself to all other processes, setting the hooking function as the one that should be executed upon injection.
    • Creates a thread which is in charge of spreading the malware through USB media.
    • Enters an infinite loop of communication with the C2 server.

The 64 bit payload is very similar, only it doesn’t check for the uninstall flag (hence can’t conduct an uninstall of the malware if requested), and doesn’t inject itself to all other processes, rendering the rootkit not useful. A comparison of both payloads can be seen in the following figure:

Figure 9: Function invoked after initial injection of UPAS Kit to explorer.exe\iexplore.exe.

We will focus on both hooking mechanism and C2 communication in the subsequent sections, so we’ll address only the lateral movement through USB media here. The way it’s done is by registering a new window class (with the name of the mutex described before) and entering an endless message loop.

Figure 10: Registration of window class for USB spreading thread.

Each intercepted message will be handled by a function that will inspect if it represents the insertion of new media and if so will initiate the spreading action and report on it to the C2 server.

Figure 11: Window class handler for USB spreading thread.

The spreading itself happens by copying the malware file to the USB drive and generating a new autorun.inf file with the string “[autorun]\r\nopen=<malware_filename>_a.exe\r\n”. Then, the spreader will look for any .lnk files and will replace their path with:

/C start \”\” \”<original_filename>\\\” && start \”\” \”<malicious_filename>_l.exe\”‘.

This will cause both the original and malware files to be executed as a result of pressing the corresponding shortcut. The replacement is done using the IShellLinkW COM class, as outlined below:

Figure 12: Replacement of path in .lnk files.

User Land Rootkit Functionality

UPAS Kit uses a pretty straight forward inline hooking mechanism, which works using the following flow:

a). Checks if the target function is already hooked (by comparing it’s first byte to 0xE9, which is the jmp instruction)

b). If it isn’t, it starts disassembling the first bytes of the function, until it processes at least 5 bytes. To do so, it uses a simple disassembly engine which merely counts the number of disassembled bytes per instruction. These bytes are referred to as the stolen bytes.

c). Prepares a buffer with 21 NOP bytes (0x90), and then reads the stolen bytes into it. Also, it modifies the last 5 bytes with a jump to the address that follows the stolen bytes. The buffer’s protection is then set to be the same one used for the original bytes (i.e. should be executable).

d). Sets the stolen bytes part of the original function to 0, and replaces the first 5 bytes with a jump to the hook function.

The following ntdll.dll functions are hooked and are intended to hide the malware’s artifacts, thus making it covert:

  • NtResumeThread: Intercepted to inject the malware binary into newly created processes.
  • NtQuerySystemInformation: Checks if the requested information class is SystemProcessInformation, and if so compares the requested PID to the whitelisted explorer.exe PID. If these match, it will set the following SYSTEM_PROCESS_INFORMATION entry (corresponding to the process that precedes the rogue explorer.exe) to point to the subsequent process to explorer.exe.
  • NtQueryDirectoryFile: Hides the directory in which the malware copy resides
  • NtEnumerateValueKey: Hides the registry run key corresponding to the malware
  • NtDeleteValueKey: Same as above
  • NtSetValueKey: Avoids the action if the requested key is the malware’s run key
  • NtSetInformationFile: Checks if the file information class corresponds to one of the following: FileDispositionInformation, FileRenameInformation, FileEndOfFileInformation or FileAllocationInformation. If so, compares the file name to the whitelisted malware’s copy binary, and if they match avoids the action.
  • NtOpenProcess: Avoids the action if the requested PID is that of the rogue explorer.exe process.
  • NtWriteFile: Avoids the action if the target file is the malware’s binary.

It’s important to note that the hooking method used by Kronos is quite different. Although both conduct inline hooking, Kronos uses a much more stable and safe implementation. Inline hooking introduces a concurrency issue whereby a context switch that occurs before all stolen bytes are overwritten may cause a system crash if the hooked function is called (since it’s code is not in a consistent state). Therefore, the Kronos hooking method uses an atomic write of the prologue bytes using the instruction ‘lock cmpxch8b’. In this sense, the hooking engine of UPAS Kit is a lot simpler, and instead carries out an unsafe write with WriteProcessMemory function.

However, once again some similarity can be spotted, and that is in the hook functions themselves. Eight of the above hooks appear in a similar form within Kronos, and serve the exact same purposes. This suggests that part of the rootkit component in those binaries was possibly reused.

CnC Communication

The interaction with the C&C server is done using the HTTP protocol. Most of the communication is done after the malware executed all other actions (i.e. injection, hooking and USB spreading). In this sequence of communication the malware beacons the server indefinitely and updates it with the following information:

  • System architecture
  • flag that indicates whether the malware copies at the %TEMP% directory still exist
  • OS version
  • Bot version (in this case 1.0.0.0)

The server, in turn, may respond with one of two commands: ‘uninstall’ and ‘download’. The latter can also include one of two subcommands: ‘update’ and ‘execute’, which are self-explanatory. Several commands can be sent in one response, delimited by the “|” character, and the command sequence will begin after the first appearance of a “!” character. The arguments of each command are delimited by a space.

Another possible message sent to the C2 server is an update on an infected USB, which will be sent once the autorun file and malware binary are copied into it.

In essence, this is the central role of UPAS Kit, i.e. serve as a covert and infectious downloader of other modules. Some of the modules for this malware offered for sale back in 2012 can be seen in the following thread from the exploit.in forum:

Although we didn’t investigate the additional modules, it seems from their description that they are similar to ones leveraged by ZeuS and some of its variants.

References

IOCs

  • Analyzed UPAS Kit sample: 1e87d2cbc136d9695b59e67f37035a45a9ad30f5fccc216387a03c0a62afa9d4
  • Analyzed Kronos sample (analyzed in Lexsi’s article): 4181d8a4c2eda01094ca28d333a14b144641a5d529821b0083f61624422b25ed