11/19/2024 | News release | Distributed by Public on 11/19/2024 11:14
In the following sections, we present the features and functionalities of Raspberry Robin, including its obfuscation and anti-analysis methods.
Due to the amount of different components, the analysis is divided into four major sections.
Execution layers
The core functionality of Raspberry Robin is unwrapped after executing a series of different layers. Each layer serves a different purpose with three of them (highlighted in bold) being the most important. In the table below, we briefly describe the functionality of each layer.
Execution Layer Name/Index |
Description |
---|---|
First Layer |
Uses the segments registers GS/CS for code emulation detection and (XOR) decrypts the next layer. |
Second Layer |
Decompresses (using a modified aPLib algorithm) and executes the next layer. |
Third Layer |
Measures the performance of the CPU to verify if Raspberry Robin is executed in an analysis/sandbox environment and decrypts the next layer using the RC4 algorithm. |
Fourth Layer |
Decrypts (via XOR) and executes the next layer. |
Fifth Layer |
Decompresses (with a modified aPLib algorithm) and executes the next layer. |
Sixth Layer |
Runs a series of anti-analysis techniques and executes a decoy payload if an analysis environment is detected. Otherwise, it decrypts the next layer using the Rabbit stream cipher. |
Seventh Layer |
Decrypts (using XOR) and executes the next layer. |
Eighth Layer |
Decompresses (using a modified aPLib algorithm) and executes the core layer. |
Table 1: Raspberry Robin execution layers.
ANALYST NOTE: Even though this is not mentioned in the table above, recent samples of Raspberry Robin do a filename check in the initial executable file. Specifically, the binary compares its filename with the string . We assess that the purpose of this check is to detect commercial sandboxes. Moreover, there are code checks to detect inline hooking of Windows APIs.
The relationship among the different layers is shown in the figure below:
Figure 1: High-level diagram of the multi-layered architecture of Raspberry Robin.
First execution layer
The first layer performs the following anti-analysis actions to detect code emulators:
ANALYST NOTE: Emulation detection with the segment register GS has been previously documented . If the last anti-analysis technique does not pass successfully, the code enters an infinite loop state since it passes an incorrect key value for the array table of the flattened control flow (which will be discussed later).
Third execution layer
The third layer uses an interesting trick, which is quite effective in detecting an analysis environment, even when set up on a physical machine. Specifically, Raspberry Robin (ab)uses a write-combining technique. The use cases for this method are limited and usually applied only in critical-performance operations (e.g. in graphics programming). Consequently, this technique affects the system's cache model in different ways:
Combining the information above with the fact that analysis environments (virtual or physical) have a limited amount of resources, Raspberry Robin uses this limitation to detect analysis environments, which either do not provide hardware support for this operation or their write/read operations are slow. The anti-analysis method can be reproduced with the following steps and the C++ code here .
ANALYST NOTE: The expected minimum return value for the method above might vary from sample to sample. For example, recent samples have increased this variable.
If the anti-analysis method does not detect an unusual behavior (as described above), then the fourth layer is decrypted using the RC4 algorithm.
Sixth execution layer
The sixth execution layer includes the majority of the anti-analysis techniques. Moreover, if an anti-analysis method is not passed in this layer successfully, then Raspberry Robin deploys a decoy payload.
Interestingly, this layer does not execute its functionality (the anti-analysis methods) immediately. Instead, it replaces any stack addresses, that point to any address of the code segment of the initial executable file, with the address of the malicious function. This results in the execution of this layer when a return ( ret ) instruction executes any of the modified pointers.
To accomplish this, this layer follows the procedure described below.
Figure 2: Raspberry Robin return address patching.
Having completed the steps above, Raspberry Robin returns the execution to the initial executed file, which in return executes the malicious patched address.
Upon execution, the sixth layer starts running a series of anti-analysis techniques. A notable observation is the marking of any detected anti-analysis method using the reserved field of the Process Environment Block (PEB) at offset . Specifically, this field is set to zero by default and Raspberry Robin updates it with a constant value, which indicates the anti-analysis method that was triggered (as shown in the table below). For example, detecting an unwanted process sets the field to the value plus the index of the detected process name in the list.
Base Value |
Anti-Analysis Method |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Table 2: Raspberry Robin anti-analysis techniques.
ANALYST NOTE: In addition to the methods above, Raspberry Robin uses other minor evasion techniques such as using random offsets in a memory-allocated area. Furthermore, certain anti-analysis methods, which were described by other researchers in the past, were not present in the recent samples we analyzed. For example, detection by using the Windows API or mapping a large virtual memory blob with junk data were removed.
The same PEB field is updated before executing the next stage with the following operations.
Based on our analysis, the final value serves an important role in both the decoy payload and the final stage in the execution chain.
Lastly, if all anti-analysis checks have passed successfully, the current layer decrypts the seventh layer using the Rabbit stream cipher and executes it. Otherwise, the layer executes a decoy payload.
Decoy payload
The decoy payload is executed using a reflective loader and does not have any obfuscation methods applied (unlike all other layers). Despite this, all of the following conditions must be met in order to proceed:
After passing the checks above, the decoy payload collects system information and sends the following data to a hardcoded domain.
Once the information above has been collected, the decoy payload prepends the CRC32 value of the username, the value from , and the system's timestamp.
If the length of this data is greater than 105 bytes, the code encrypts the data after this offset using the RC4 encryption algorithm (the encryption key is the first 12 bytes of the packet, which consists of the CRC32 of the username, value, and system's timestamp). The RC4 encrypted data is not used by Raspberry Robin, thus it may be an artifact from older code or an oversight by the malware author. The first 105 bytes of the data (including the CRC32 of the username, value, and system's timestamp) are encrypted using the RSA algorithm (with a hard-coded key) and encoded with Base64.
As a last step, the decoy payload replaces the characters , , with , , respectively and uses the output as a URI. Lastly, the expected response is a Windows executable file, which is saved to a filename that is the hex-encoded CPU name of the compromised host.
ANALYST NOTE: Considering the amount of information collected from the system along with the rest of the operational checks, we assess with medium confidence that this payload might (also) be used as a method for the threat actors to track unexpected compromised hosts (for example, sandbox environments).
Obfuscation methods
Raspberry Robin extensively uses a variety of different obfuscation methods. These are:
The first five methods are applied during compilation at the intermediate representation (IR) level.
Obfuscated function key
Each obfuscated function includes an encrypted array table (unique per function). This array table is used during the entire execution of the function for any of the following reasons:
An example is shown in the figure below.
Figure 3: Raspberry Robin encrypted strings and control-flow flattening example.
However, the major issue is that in order to decrypt the array table, an integer key needs to be used. This key is passed into the function in one of the following ways:
The first two cases are related to each other because each key passed to a function has been derived either from the global variable of the current layer or the variable passed from the previous layer. For example, in the figure below, the global variable has its value calculated based on a value passed from the previous execution layer.
Figure 4: Raspberry Robin layer decryption using a global variable.
As a result, retrieving the initial/global variable of a layer is important since without it, it is not possible to conduct any analysis (with a few exceptions).
Even though it was rarely required, our approach to solve this problem was the following:
Control flow flattening
The implemented control flow flattening method, which Raspberry Robin uses, is encountered in all layers (including the final stage) and makes the analysis more tedious and time-consuming.
Each conditional statement is calculated by using a substitution, addition, or bitwise XOR operation on the state variable with an integer from the decrypted array table and then comparing the output with another value from the same array table.
Raspberry Robin takes care of boolean conditional cases by using a similar approach. In general, boolean comparisons are treated with one of the following methods:
As expected, the formulas above might vary from function to function (for example, the order might be different), but the core concept remains the same in all of them.
Lastly, considering all of the information above, encountering the obfuscated flow was a priority during our analysis and as a result we ended up creating our own IDA (decompiler) plugin.
Obfuscation algorithms
In addition to the previous obfuscation methods, both the execution layers and the core layer of Raspberry Robin contain different algorithms to either obfuscate or decrypt parts of the code. These are:
ANALYST NOTE: Constant numbers in the algorithms above are different per sample. For example, the constant numbers 0xe9 and 0x66 (in the modified aPLib algorithm) are expected to be different.
Core layer
In this section, we describe the functionality of the core features for the final and main layer of Raspberry Robin.
Synchronization and code-execution behavior
The dynamic behavior of Raspberry Robin mostly depends on three factors:
Evasion and anti-analysis
Despite the amount of anti-analysis techniques used in previous layers, the final layer includes its own set of anti-analysis methods.
The implemented anti-analysis and evasion methods in the core layer are mentioned below.
Obscure registry modification
In addition to the techniques above, Raspberry Robin uses an interesting approach to avoid detection while adding registry data.
Rather than modifying the Windows registry directly using common Windows API functions (e.g. , ), Raspberry Robin first renames the target registry key to a random one, writes the registry data into the renamed key, and renames it back to its original name.
However, if administrator privileges are available, Raspberry Robin uses a different approach. At first, it renames the registry key, creates an offline registry hive in the Windows temporary directory with a random filename. Then, it writes the registry data in the offline registry hive and loads the offline hive to the global registry tree using .
Persistence
Raspberry Robin uses the Windows registry keys for adding persistence on the compromised host.
Upon execution, it adds the directory in the Windows Defender's exclusion list and generates a random lowercase-alphabetic string ranging in size from 3 to 9 characters. The file's extension is randomly chosen from the list below.
Then, the Raspberry Robin file is moved to a new directory using the previously generated string as a filename. Similarly, the directory created uses the same attributes of the directory and has a randomly generated lowercase-alphabetic name ranging between 4 and 7 characters in length. Furthermore, the file moved has its alternate stream name stripped and the timestamp changed to the current system time.
Raspberry Robin uses the registry key for adding persistence on the compromised host. The created key and value names are random lowercase-alphabetic strings ranging in size from 3 to 7 characters
It is crucial to highlight that Raspberry Robin checks if any of the following conditions are met:
If any of the above is true, Raspberry Robin changes the following aspects of its behavior.
Lastly, depending on certain conditions, Raspberry Robin uses a different command-line (shown in the table below) for the persistence registry key.
Requirements |
Command-Line |
---|---|
|
|
|
|
|
|
|
|
|
Table 3: Raspberry Robin persistence command-line.
Another interesting feature is the use of the Windows Encrypted File System (EFS) in case of having administrator privileges, but failing to add persistence on the compromised system. In this case, Raspberry Robin sets the value for the registry key to 3 and encrypts the Raspberry Robin binary file. Since this operation invokes the process, Raspberry Robin attempts (10 times in total) to terminate it.
Network propagation
One of the features of Raspberry Robin is to propagate itself and compromise Windows devices on the same network.
RDP propagation
At first, Raspberry Robin uses the Windows API with parameters and in order to determine if the compromised host is remotely controlled with an Remote Desktop Control (RDP) session. In the event of an unsuccessful return, Raspberry Robin retrieves the local session ID by reading the value of the registry key and compares it with the session ID of the current process. If these two values are equal then the propagation process stops.
Next, Raspberry Robin constructs a file path using the prefix along with the current file path of Raspberry Robin and searches for target directories in the directory. A target directory must meet certain criteria such as:
For each directory that is discovered, the Raspberry Robin file is copied into the target directory with a randomly generated filename with the extension under the default Windows path of the selected user.
If the operation above fails, Raspberry Robin verifies that the current user has administrator privileges and starts to iterate all network drives from up to . If any of them exist, it creates a symbolic path to and copies the file of Raspberry Robin using the same method as described earlier.
SMB propagation
Raspberry Robin uses two legitimate and publicly available tools for replicating itself, PsExec and PAExec, along with the Windows built-in utility IExpress.
Notably, PsExec and PAExec are downloaded from their official websites and are not embedded in Raspberry Robin. Before proceeding with their download and usage, Raspberry Robin ensures that the current user is part of the domain's administrator group. Otherwise, the execution does not proceed.
Furthermore, Raspberry Robin stores PsExec (or PAExec in case of a download failure) in the Windows temporary folder and adds the path to the Windows Defender exclusion list. Additionally, Raspberry Robin creates a Self Extraction Directive (SED) file (shown below) and saves it as a text file, with a randomly generated filename, in the Windows temporary directory. The placeholders of the SED file specify the following properties.
Raspberry Robin executes the formatted SED file with the IExpress command to generate the payload. Lastly, it creates a list of internal IPs and enumerates all Windows devices in the domain. The information collected is then written in a text file and passed to PsExec/PAExec when executing them.
In general, Raspberry Robin uses the following commands to execute the generated payload on other Windows devices connected to the network:
Depending on the selected tool (PsExec or PAExec), the commands might differ in their parameters. For example, to avoid notifying the user, the parameter is used for PAExec while, in the case of PsExec, Raspberry Robin modifies the registry key to .
It is noteworthy to mention that, in case administrator privileges are available, Raspberry Robin adds a list of additional exclusions and rules, as shown in the table below.
Exclusion Description |
Exclusion Rule |
Windows Defender Process exclusion |
IExpress file path ( ). |
Windows Defender Process exclusion |
Filepath of the downloaded PsExec/PAExec. |
Windows firewall rule to enable ICMP and to ensure that PsExec/PExec will work properly. |
|
Windows firewall rule to allow discovery and sharing between network hosts. |
Table 4: Raspberry Robin exclusion rules list.
All files that were created in the Windows temporary directory during the execution of the operations above, are removed.
Local privilege escalation
One of the interesting features of Raspberry Robin is that it goes to a great extent to elevate its local privileges by using UAC-bypass methods and local privilege escalation exploits.
UAC bypass methods
Raspberry Robin includes the following UAC-bypass methods, which are copied from public sources:
The UAC-bypass targeted registry classes are shown in the table below:
Registry Key Class Name |
Executable Name |
---|---|
Table 5: Raspberry Robin UAC-bypass targeted registry classes.
As in other cases, Raspberry Robin performs a series of verifications and checks that affect the elevation method selection process:
The malicious file is executed using the legitimate Windows application or (depending on the Windows version) along with the file path of Raspberry Robin with junk data appended to it. For example:
ANALYST NOTE: There are indications in the binary's code that instead of using , Raspberry Robin might use along with the parameter followed by the target file path. The part of this code is currently unreachable based on our analysis.
Exploits
Historically, Raspberry Robin has used a different set of exploits. At the time of publishing this blog, these are CVE-2024-26229 and CVE-2021-31969. Their payloads are decrypted at runtime using RC4 and the choice for which exploit to use depends on the Windows version of the compromised host.
Raspberry Robin uses the Donut loader to load and execute its exploits. Unlike other components, neither the Donut loader nor the exploits have any obfuscation applied to them, most likely due to the fact that the binary's target architecture is x64. The code of the exploits is injected into the target process with the parameter by using the KernelCallbackTable injection method . Because the injected payload is x64, Raspberry Robin uses the Windows API functions , , and .
Network communication
The main objective of Raspberry Robin is to download and execute a payload on the compromised host. However, to reach this stage, there are several tasks that need to be completed first before requesting the payload.
The first step is to verify that the host can connect to the TOR network. This is achieved by decrypting a list of legitimate onion domains (version 3) and attempting to connect to one of them (randomly chosen). It is important to note that since the network communication is over the TOR network, Raspberry Robin has its own TOR client embedded in its code.
Next, Raspberry Robin creates three file mapping objects, encrypts the onion URL using RC4, and copies into one of them the following structure.
The member has an important role in the behavior of Raspberry Robin. This is because the TOR module does not proceed if the external TOR IP address belongs to any of the following subnets.
Moreover, the external IP address is RC4 encrypted and written in one of the remaining two memory mappings, which are not used at any other point during the execution. The RC4 key is 4 bytes long and is derived from the value of .
The calculated elapsed timestamp is added to the local timestamp and the resulting output is compared with the hardcoded termination timestamp described earlier.
The RC4 key for the encryption of the packet data is derived from the mulberry PRNG algorithm with the value of structure as a seed. Similarly, the names of the mapping objects are generated in the same way as the RC4 key and therefore the injected TOR module can access this information independently.
Next, Raspberry Robin proceeds with the code injection in one of the processes from the list below.
The process created has its parent process identifier (PPID) replaced with the PID of and its command-line generated based on the name of the selected process. For example, the format of the command-line for the target process is .
The code injection method that Raspberry Robin chooses depends on the presence of the module (component of AVG Antivirus). If this module is present, then Raspberry Robin patches the entry point of the target process using a combination of Windows API functions , , and . Otherwise, it uses the APC code injection technique with the Windows API function (instead of ) if the Windows build version is higher than 19603. As a last step, a number of random bytes are added at the start of the mapped memory area of the TOR module loader.
ANALYST NOTE: It is common practice for Raspberry Robin to check for the build number of Windows to verify the support of new low-level APIs to avoid any detection alerts. For example, to use instead of .
Another important observation is the use of Return-oriented Program (ROP) during the code injection process. Raspberry Robin collects the number of available address-candidates in the module, chooses one at random, and uses it. An address is selected only if the following conditions are met.
In case of failure to find a candidate address, the entry point of the injected TOR module is used instead.
The injected module loader is similar to the previous execution layers. Therefore, the loader has the same decompression (modified aPLib) and decryption (RC4 and bitwise XOR-operations) algorithms, as well as the same anti-analysis methods of the core layer.
Following a successful TOR connectivity check, Raspberry Robin constructs a packet that includes the following information from the compromised host:
To avoid issues with a large data size, Raspberry Robin sets a maximum length of 131,072 bytes. This limit has a critical role when a desktop screenshot is taken. In case the screenshot's data size makes the total network packet size exceed the maximum limit, the screenshot is not included in the packet.
The 2-byte calculated value is of a particular interest. This value is calculated by reading the PE headers of the initial executable file (in the event that a filepath parameter is not included, this value is set to 5) and setting specific headers to zero (the size of the headers must not be over 256 bytes and the file must be larger than 65,536 bytes). The names of these PE headers are mentioned below:
Also, depending on the number of PE sections and the raw size/virtual-address of the first section, the header information ( ) of the first section might be set to zero.
Then, Raspberry Robin calculates the CRC-64 checksum of the modified PE headers and bitwise-XORs the output with the lower part of the PE timestamp followed by a hard-coded constant value. If the value of the output is higher than 1,000, then Raspberry Robin changes the value to 4.
ANALYST NOTE: There are certain conditions, which can skip the process above and set a hardcoded value instead. For instance, the value is set to 1 if the session ID of the process is different than zero and the checksum of the value of the environmental variable does not match an (unknown) output.
Having collected all the information above, Raspberry Robin generates 8 random bytes and appends them to a hardcoded 8 byte value that acts as a salt and uses this value as an RC4 key to encrypt the data. The randomly generated part of the key is prepended to the packet after the encryption since the C2 server requires it. Furthermore, it generates random padding that is appended at the end of the RC4 encrypted data (the size of the random data is randomly chosen up to 2,048 bytes).
As the final step in the encryption process, a checksum value of the data is calculated (CRC-64) and appended. Raspberry Robin encrypts the packet produced using AES-CTR with a randomly generated initialization vector (IV) using the mulberry algorithm and encodes the encrypted output using Base64. The layout of the header for the final packet is provided below.
The Base64 output is appended as a URI to an onion domain (version 3) and a request is sent using the TOR module (in the same way as described above).
ANALYST NOTE: It is important to point out that the CNCs of Raspberry Robin are not decrypted all at once. Instead, Raspberry Robin decrypts only a randomly-chosen one. One more interesting observation is the incorrect size of the onion domains, which are short by one character. This is an intentional choice by the developers. The character is added at the end of the onion domain by the TOR module.
After sending the request, the C2 server of Raspberry Robin replies with a payload that is mapped directly into the current process and executed in a new thread.
However, before proceeding with its execution, Raspberry Robin parses the downloaded payload and prepends the following data:
Moreover, Raspberry Robin creates a registry key (host-based name) at , which indicates that the payload has been downloaded.