# Automatic ReZer0 payload and configuration extraction
**[maxkersten.nl/binary-analysis-course/analysis-scripts/automatic-rezer0-payload-and-configuration-extraction/](https://maxkersten.nl/binary-analysis-course/analysis-scripts/automatic-rezer0-payload-and-configuration-extraction/)**
#### This article was published on the 17th of September 2020. This article was updated on the 8th of December 2021.
Understanding how a loader works shortens the time an analyst needs when it is encountered again. It also allows an analyst to create detection rules. This is, however, still manual work. Automating the extraction of the payload and possible configuration of a loader is the ideal scenario. This article covers the automatic extraction of both, based on the ReZer0 loader analysis which was analysed earlier on in this course.
As a follow-up article, I dug deep into this loader’s details, as well as historical versions, can be found on McAfee’s Advanced Research Team’s blog.
## Table of contents
The approach
#### As is described in the analysis, the loader’s payload is stored within a private static byte array, whereas the configuration is stored in a private static string array. The required fields are populated based on the string array, which the loader then uses to determine which functions need to be executed.
Within the Dot Net framework, the Assembly class can be used to interact with classes, functions, and fields within a Dot Net binary.
Note that both required fields are static, meaning they are assigned their value when the file is loaded. As such, one can iterate over the private static fields within all classes, as the variable and class names are randomised per sample. Once a byte array or string array is found, additional checks can be performed. If these checks are passed, one can store the payload on the disk and print the loader’s configuration.
## Writing the extractor
#### Based on prior research, the payload is an executable, meaning one can assume it starts with the MZ header. The size of the string array is 37, meaning all other string arrays can be ignored. These checks, especially the second one, may change in the future. If this is the case, or to avoid missing future samples, one could dump all occurrences that are encountered. The code is given in pieces, where each part is explained in the usual step-by- step manner.
-----
### Configuring the project
#### The extractor will be written in C# using Visual Studio 2019. Other versions should work as well, but might require different or additional steps. After making a console application, one has to edit the App.config file to allow the project to load code from remote sources. The code snippet to add to the configuration file is given below.
```
The reason as to why this is required, is the code that is executed within the ReZer0 loader when it is loaded. This is also the exact functionality on which the extractor is based.
### The main function
#### To easily extract payloads from loaders in bulk, this extractor will go over each file in a given directory. As such, the program requires a single command-line argument: the path to a folder that contains the loaders that are to be analysed. Tests of this program as a whole were done based upon on the 215 samples that were found during prior research. The main function is given below.
static void Main(string[] args)
{
//Get all files in the given folder
string[] files = Directory.GetFiles(args[0]);
//Loop through all files in the given folder
foreach (string file in files)
{
//Handle each file
HandleFile(file);
}
//Keep the window open once all files have been iterated through
Console.ReadKey();
}
### Extracting the payload
#### To extract the payload, one must load the loader sample as an assembly in C#. This does not execute the entry point of the binary, which is essential in this case. Do note that technically, all static code is executed by loading the binary. Once the loader sample is loaded, one needs to go over all classes, as the class name that contains the payload is unknown. For each class, one wants to obtain all private static fields, since the payload is defined as such. The type of the field is then matched for each encountered field. If the payload contains MZ as the first two bytes, it is safe to assume that the payload has been found. Writing it to the disk then preserves the decrypted payload. The payload is already
```
-----
#### decrypted, as it is a static field in the loader, meaning the value is given to the field once the executable is loaded, and prior to the execution of the main function. The code to do so is given below.
```
//Load the ReZer0 loader file
Assembly assembly = Assembly.LoadFile(file);
//Loop over all classes
foreach (Type type in assembly.GetTypes())
{
//Get all nonpublic static fields from each class
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic |
BindingFlags.Static);
//Loop over all fields
foreach (FieldInfo fieldInfo in fields)
{
//Get the value of each field, as the name is randomised for the
loader
object value = fieldInfo.GetValue(null);
//If the type of the current field's value is a byte array, its the
payload, as there is only 1 embedded in the class
if (value is Byte[])
{
//Create a local variable to more easily handle it
byte[] payload = (byte[])value;
//Verify that the byte array is a PE file
if (payload[0] == 0x4d && payload[1] == 0x5a)
{
//Write the payload to the disk
File.WriteAllBytes(file + "_extracted", payload);
Console.WriteLine("Wrote payload to disk as " + file
+ "_extracted");
Console.WriteLine("Payload size: " + payload.Length +
" bytes");
}
}
### Extracting the configuration
#### To extract the configuration array, one can use the same loop to iterate through all fields. In this case, the requested field type is a string array. If the type matches, and the length is equal to 37, which is the length that is used for the configuration array in the loader, one can assume the configuration array has been found. All that rests then, is to print the configuration value with their corresponding settings. The knowledge based on this is based on the previous research into this loader. The code for the configuration extraction is given below.
```
-----
```
//If the value type is a string array, it means that the raw configuration values of
the loader
if (value is String[])
{
//Create a local variable based on the value, casting the type safely due to
the previous if-statement
String[] settings = (String[])value;
//If the length of the string array is equal to 37, it is safe to assume that
the configuration array has been found
//The length is based on prior research
if (settings.Length == 37)
{
//Create and instantiate the output string
String output = "Payload launch method: ";
//Get the launch enum value
int launchEnum = Conversions.ToInteger(settings[0]);
//If the launch enum value is equal to 4, the payload is launched
directly
if (launchEnum == 4)
{
output += "launch from loader's memory";
}
//In other cases, it executes the payload via a hollowed process
else
{
output += "process hollowing into ";
//Depending on the value, the loader uses a specific process
if (launchEnum == 0)
{
output += "the loader's process";
}
else if (launchEnum == 1)
{
output += "MSBuild.exe";
}
else if (launchEnum == 2)
{
output += "vbc.exe";
}
else if (launchEnum == 3)
{
output += "RegSvcs.exe";
}
}
//Add a newline for readability
output += "\n";
//Get the value of the scheduled task setting
int shouldSetScheduledTask = Conversions.ToInteger(settings[1]);
//Print the scheduled task value
output += "Sets a scheduled task: " + shouldSetScheduledTask + "\n";
//Get the remote payload execution setting's value
int shouldExecuteRemotePayload = Conversions.ToInteger(settings[4]);
```
-----
```
//Display the value
output += "Executes remote payload: " + shouldExecuteRemotePayload +
"\n";
//if the setting is enabled, the specific settings are read and
printed
if (shouldExecuteRemotePayload == 1)
{
string url = settings[5];
string downloadedFileName = settings[6];
output += "URL: " + url + "\n";
output += "File name on victim's machine: " +
downloadedFileName + "\n";
}
//Gets the anti-virtualisation detection setting
int shouldDetectVirtualEnvironments =
Conversions.ToInteger(settings[7]);
//Prints the setting
output += "Exits when in a virtual environment: " +
shouldDetectVirtualEnvironments + "\n";
//Gets the anti-sandbox setting
int shouldDetectSandboxes = Conversions.ToInteger(settings[8]);
//Prints the setting
output += "Exits when in a sandboxes: " + shouldDetectSandboxes +
"\n";
//Gets the messabox display setting
int shouldDisplayMessageBox = Conversions.ToInteger(settings[29]);
//Prints the setting's value
output += "Displays messagebox: " + shouldDisplayMessageBox + "\n";
//If the setting is enabled, all details are printed
if (shouldDisplayMessageBox == 1)
{
string messageBoxTitle = settings[30];
string messageBoxText = settings[31];
int messageBoxButtonsStyle =
Conversions.ToInteger(settings[32]);
int messageBoxIconStyle =
Conversions.ToInteger(settings[33]);
output += "\tTitle: " + messageBoxTitle + "\n";
output += "\tText: " + messageBoxText + "\n";
output += "\tButtons style: " + messageBoxButtonsStyle +
"\n";
output += "\tIcon style: " + messageBoxIconStyle + "\n";
}
//Get the sleep setting
int sleepTime = Conversions.ToInteger(settings[34]);
//Print the setting
output += "Uses sleep to evade detection: " + sleepTime + "\n";
//If the setting is enabled, the sleep duration is also printed
if (sleepTime == 1)
{
int sleepDuration = Conversions.ToInteger(settings[35]);
```
-----
```
output + Sleeps for + sleepDuration + seconds\n ;
}
//Add a newline for readability
output += "\n";
Console.WriteLine(output);
}
}
### Running the extractor
#### Upon putting all the pieces together, one can run the program to iterate all files within a folder. In this test, the files were located at C:\. The output of the extractor for several files is given below.
Parsing C:\01a083f468e17d5da38d15907e26a71ce4ec6ee575aa5069e090e3d325f855ce
Wrote payload to disk as
C:\01a083f468e17d5da38d15907e26a71ce4ec6ee575aa5069e090e3d325f855ce_extracted
Payload size: 150016 bytes
Payload launch method: process hollowing into MSBuild.exe
Sets a scheduled task: 0
Executes remote payload: 0
Exits when in a virtual environment: 0
Exits when in a sandboxes: 0
Displays messagebox: 0
Uses sleep to evade detection: 0
Parsing C:\03c27b0f45e222123d76ed5a54538bb27ae2739644567985c8f52216b783116d
Wrote payload to disk as
C:\03c27b0f45e222123d76ed5a54538bb27ae2739644567985c8f52216b783116d_extracted
Payload size: 774144 bytes
Payload launch method: process hollowing into the loader's process
Sets a scheduled task: 1
Executes remote payload: 0
Exits when in a virtual environment: 1
Exits when in a sandboxes: 1
Displays messagebox: 0
Uses sleep to evade detection: 0
Parsing C:\03cb9a030d70871b55b2b60be423496a52d536dbd07a94b5597d7395cd0ec130
Wrote payload to disk as
C:\03cb9a030d70871b55b2b60be423496a52d536dbd07a94b5597d7395cd0ec130_extracted
Payload size: 126976 bytes
Payload launch method: process hollowing into vbc.exe
Sets a scheduled task: 1
Executes remote payload: 0
Exits when in a virtual environment: 0
Exits when in a sandboxes: 0
Displays messagebox: 0
Uses sleep to evade detection: 1
Sleeps for 10 seconds
```
-----
## Conclusion
#### Based on the analysis and the usage of reflection within the Dot Net framework, it is possible to extract the required data from a given ReZer0 loader without executing it. Changing the encryption (and therefore the decryption) routine does not make a difference in this scenario, as the fields are already assigned a value once the file is loaded, effectively bypassing the complete encryption module of the loader.
## Extractor code
#### The complete code for the extractor is given below. Keep the aforementioned App.config changes in mind, and import the Visual Basic reference correctly in order to make the code work.
-----
```
using System;
using System.IO;
using System.Reflection;
using Microsoft.VisualBasic.CompilerServices;
namespace ReZer0_extractor
{
///
/// ReZer0 payload and settings extractor by Max 'Libra' Kersten (@Libranalysis
on Twitter, https://maxkersten.nl)
/// Licensed under GPLv3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
///
class Program
{
///
/// Extracts the payload from the loader to a file with the same name with
"_extracted" appended to it.
/// The config of the loader is printed to the console.
///
///
static void HandleFile(string file)
{
//Print the file path to indicate which file is being processed
Console.WriteLine(@"Parsing " + file);
//Load the ReZer0 loader file
Assembly assembly = Assembly.LoadFile(file);
//Loop over all classes
foreach (Type type in assembly.GetTypes())
{
//Get all nonpublic static fields from each class
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic |
BindingFlags.Static);
//Loop over all fields
foreach (FieldInfo fieldInfo in fields)
{
//Get the value of each field, as the name is randomised for the
loader
object value = fieldInfo.GetValue(null);
//If the type of the current field's value is a byte array, its
the payload, as there is only 1 embedded in the class
if (value is Byte[])
{
//Create a local variable to more easily handle it
byte[] payload = (byte[])value;
//Verify that the byte array is a PE file
if (payload[0] == 0x4d && payload[1] == 0x5a)
{
//Write the payload to the disk
File.WriteAllBytes(file + "_extracted", payload);
Console.WriteLine("Wrote payload to disk as " + file +
"_extracted");
Console.WriteLine("Payload size: " + payload.Length + "
bytes");
}
```
-----
```
}
//If the value type is a string array, it means that the raw
configuration values of the loader
if (value is String[])
{
//Create a local variable based on the value, casting the
type safely due to the previous if-statement
String[] settings = (String[])value;
//If the length of the string array is equal to 37, it is
safe to assume that the configuration array has been found
//The length is based on prior research
if (settings.Length == 37)
{
//Create and instantiate the output string
String output = "Payload launch method: ";
//Get the launch enum value
int launchEnum = Conversions.ToInteger(settings[0]);
//If the launch enum value is equal to 4, the payload is
launched directly
if (launchEnum == 4)
{
output += "launch from loader's memory";
}
//In other cases, it executes the payload via a hollowed
process
else
{
output += "process hollowing into ";
//Depending on the value, the loader uses a specific
process
if (launchEnum == 0)
{
output += "the loader's process";
}
else if (launchEnum == 1)
{
output += "MSBuild.exe";
}
else if (launchEnum == 2)
{
output += "vbc.exe";
}
else if (launchEnum == 3)
{
output += "RegSvcs.exe";
}
}
//Add a newline for readability
output += "\n";
//Get the value of the scheduled task setting
int shouldSetScheduledTask =
Conversions.ToInteger(settings[1]);
//Print the scheduled task value
```
-----
```
output + Sets a scheduled task: +
shouldSetScheduledTask + "\n";
//Get the remote payload execution setting's value
int shouldExecuteRemotePayload =
Conversions.ToInteger(settings[4]);
//Display the value
output += "Executes remote payload: " +
shouldExecuteRemotePayload + "\n";
//if the setting is enabled, the specific settings are
read and printed
if (shouldExecuteRemotePayload == 1)
{
string url = settings[5];
string downloadedFileName = settings[6];
output += "URL: " + url + "\n";
output += "File name on victim's machine: " +
downloadedFileName + "\n";
}
//Gets the anti-virtualisation detection setting
int shouldDetectVirtualEnvironments =
Conversions.ToInteger(settings[7]);
//Prints the setting
output += "Exits when in a virtual environment: " +
shouldDetectVirtualEnvironments + "\n";
//Gets the anti-sandbox setting
int shouldDetectSandboxes =
Conversions.ToInteger(settings[8]);
//Prints the setting
output += "Exits when in a sandboxes: " +
shouldDetectSandboxes + "\n";
//Gets the messabox display setting
int shouldDisplayMessageBox =
Conversions.ToInteger(settings[29]);
//Prints the setting's value
output += "Displays messagebox: " +
shouldDisplayMessageBox + "\n";
//If the setting is enabled, all details are printed
if (shouldDisplayMessageBox == 1)
{
string messageBoxTitle = settings[30];
string messageBoxText = settings[31];
int messageBoxButtonsStyle =
Conversions.ToInteger(settings[32]);
int messageBoxIconStyle =
Conversions.ToInteger(settings[33]);
output += "\tTitle: " + messageBoxTitle + "\n";
output += "\tText: " + messageBoxText + "\n";
output += "\tButtons style: " +
messageBoxButtonsStyle + "\n";
output += "\tIcon style: " + messageBoxIconStyle +
"\n";
```
-----
```
}
//Get the sleep setting
int sleepTime = Conversions.ToInteger(settings[34]);
//Print the setting
output += "Uses sleep to evade detection: " + sleepTime +
"\n";
//If the setting is enabled, the sleep duration is also
printed
if (sleepTime == 1)
{
int sleepDuration =
Conversions.ToInteger(settings[35]);
output += "Sleeps for " + sleepDuration + "
seconds\n";
}
//Add a newline for readability
output += "\n";
Console.WriteLine(output);
}
}
}
}
}
///
/// ReZer0 payload and settings extractor by Max 'Libra' Kersten
(@Libranalysis on Twitter, https://maxkersten.nl)
/// Licensed under GPLv3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
///
static void Main(string[] args)
{
//Get all files in the given folder
string[] files = Directory.GetFiles(args[0]);
//Loop through all files in the given folder
foreach (string file in files)
{
//Handle each file
HandleFile(file);
}
//Keep the window open once all files have been iterated through
Console.ReadKey();
}
}
}
```
-----