
In October 2025, the ESET Research Team published an excellent article about the identification of a new instance of the Operation DreamJob cyberespionage campaign, conducted by the Lazarus APT Group, aligned with the North Korean government. This instance was identified by ESET as Gotta Fly, as it was determined that Lazarus was directing cyberattacks with an espionage focus to steal know-how related to the production of Unmanned Aerial Vehicles from companies that are providing such technology to Ukraine. In the same article, the ESET Research Team provided information on the identification of two kill chains, both of which implement ScoringMathTea. Below, you can see an image taken from the ESET post, showing the identified execution chains.

ScoringMathTea is a RAT (Remote Access Trojan) in C++, developed and operated by Lazarus, which provides operators with all the necessary capabilities that a good RAT can offer, including remote command execution, loading and execution of plugins in memory, among other capabilities. This is the object of analysis in my research.
Below is a general overview of the main capabilities implemented in ScoringMathTea.

ScoringMathTea’s Reverse Engineering
The sample we are going to analyze is ScoringMathTea in DLL format. When we open it in IDA Pro, the first function that appears is DllMain, which simply creates a thread through CreateThread WinAPI, setting the lpStartAddress as a wrapper function for the main function of ScoringMathTea.

In the image below, we can observe the flow identified by the Proximity Browser, which leads from DllMain to the Main function of ScoringMathTea.

Upon reaching this function, we can already observe the creation of a pseudo-random seed using GetTickCount64 followed by srand, followed by a function that initializes some fields of the ScoringMathTea configuration struct, and finally arriving at the identification of the API resolution technique via API Hashing.

Initializing Specific Fields of the Configuration Struct
In the smt_init_config function, several fields of the struct that stores certain configuration aspects of ScoringMathTea are initialized. Below, we can see the storage via Stack Strings of the C&C URL of this sample. The use of Stack String is strategic so that the URL address is not easily identified by any string extraction tool.

In addition to the URL address, it is also possible to observe the creation of slots for more C&C addresses, which were declared as null, but which I believe could be added during the operation.

Other interesting fields that were observed being initialized include possible random seed that can be used later, as well as an ID, which could be from the campaign or the build of this ScoringMathTea sample.

Analyzing the API Hashing Routine
After initializing certain fields of the ScoringMathTea configuration struct, the main function loads and executes the dynamic API loading routine via API Hashing. Upon analyzing the function responsible for this process, we encounter an initial string deobfuscation routine, using the smt_string_decryption function, which only receives the strings as arguments.

Next, we will analyze in deep the custom algorithm used by ScoringMathTea to deobfuscate these strings.
Analyzing the Custom String Deobfuscation Algorithm
This if the main function used by the ScoringMathTea to deobfuscate static strings at runtime, implementing something like a polyalphabetic substitution cipher with chaining (propagating cipher).
The decoding mechanism operates as follows:
- Initialization: The function receives an obfuscated string (
enc_str) and an output pointer (dec_str). - Substitution Alphabet: The algorithm relies on a global lookup table (pointed in
alphabet_decryption = "pB1Q5ZyneCb6sR03u2OxfK8vVMkEaow_ciSDYIUmlF4hq9XLPJNzTHGgr.WtdA7"). This table functions as a 64-character “alphabet” (as indicated by the0x40and0x3Fdelimiters). - Key State: An initial key, or “state“, is hardcoded with the value
11. This key is dynamic and changes with each iteration. - Decoding Process (Loop): The function iterates through each character of the copied obfuscated string:
- First, it locates the obfuscated character within the
alphabet_decryptiontable to find its index (alphabet_idx). - Then, it calculates the index of the actual (decoded) character by subtracting the current
key_statefrom thealphabet_idx. A bitwise AND operation with0x3Fis used to ensure the result is a valid index (essentially a modulo 64 operation that also handles “underflow” of negative indices). - The newly calculated index is used to retrieve the actual decoded character from the same
alphabet_decryptiontable. - This decoded character replaces the obfuscated character in the buffer.
- First, it locates the obfuscated character within the
- Chaining Mechanism: This is the polyalphabetic aspect of the cipher. After decoding a character, the
key_stateis updated for the next iteration. The new state is calculated by adding the value of the newly decoded character to the previouskey_state, again applying a modulo 64 (& 0x3F).
This chaining mechanism makes the key dependent on the decoded output itself, meaning that each character is decoded with a different key, derived from the previous character.
Finally, the algorithm stores the pointer to the decoded string (in char* format) in the first argument and then converts and copies the deobfuscated string to an adjacent wide string buffer (wchar_t*). Below we can see the pseudocode of this algorithm:

Having understood the algorithm, I developed a string deobfuscation Python script for IDA Pro, which is available in my GitHub repository.
The script is quite straightforward, following these steps:
- Collects each XRef’s offset of the smt_string_decryption function;
- Identifies and collects the arguments of each XRef;
- Submits the obfuscated strings (which are passed as arguments to the smt_string_decryption function) to the string deobfuscation algorithm implementation;
- The result is written as a comment at each offset identified in the XRef identification process in the Disassembler.
This allows the string decryption process to be automated within IDA itself, making the analysis more fluid with the comments placed in the Disassembler. Below is the output of the script in the IDA Pro Output window.

And below, we can partially observe the result of the Python script execution.

Thus, it can be observed that the deobfuscated strings in the API resolution function (smt_api_resolution) are DLLs that will be used throughout. Let’s continue with the analysis of the API loading routine via API Hashing.
Continuation of the Analysis of the API Loading Routine via API Hashing
After deobfuscating the strings, the smt_api_resolution function will perform PE parsing of the desired module (DLL), with the aim of collecting the names of the APIs exported by that DLL, using AddressOfNames in the IMAGE_EXPORT_DIRECTORY.

In each iteration of AddressOfNames, the collected result will be submitted to the ScoringMathTea hashing algorithm, as can be seen in the pseudocode below.

The while loop will check if the resulting hash from each API in each iteration matches the hash received as an argument to this function. The hashing process for each API name begins with a fixed seed value (api_hashed = 0x2DBB955), with its main logic described as:api_hashed ^= (char)*v18++ + (api_hashed >> 2) + 32 * api_hashed.
The custom hashing algorithm used by ScoringMathTea follows the following steps:
api_hashed ^= ...: The result of this sum is then combined with the current hash value using an XOR operation. The result becomes the newapi_hashedvalue for the next iteration.32 * api_hashed: The current hash value is multiplied by32. In terms of bits, this is a left shift of 5 bits (api_hashed << 5).(api_hashed >> 2): The current hash value is right shifted by 2 bits (equivalent to an integer division by 4).(char)*v18++: The ASCII value of the current character in the string.- Sum: The algorithm sums these three components:
(character) + (hash / 4) + (hash * 32).
As we can see in the image below, the hashing resolution function is called approximately 273 times throughout the ScoringMathTea code.

And in the same way that it was done for the string decryption function, it is possible to collect each hash that will be resolved to an API at runtime. Of course, it’s possible to observe several repeated APIs, which refer to APIs that are used multiple times, such as LocalAlloc, LocalFree, GetLastError, among others.

Since the hashing algorithm is relatively simple, it’s possible to create a Python script to automate the API hashing process. I implemented this and submitted a Pull Request to the OALabs open-source project, HashDB, with the goal of automating the de-hashing and facilitating static analysis. With the Pull Request accepted, it is now possible to look up the hashes of the current ScoringMathTea samples.


This makes it possible to have a clearer view of what ScoringMathTea is loading and executing throughout its operation.

Analysis of Communication Capability with C&C and Plugin Execution
The logic of the main function of ScoringMathTea consists of an infinite loop, where the main functionalities of the agent (ScoringMathTea deployed on the infected system) and its communication with the C&C server are implemented, awaiting commands received from the operators to finally execute them, maintaining a 60-second beacon heartbeat to try to reduce network noise, but in reality it seems to be a standard agent configuration.

This main function appears to have 3 execution phases. The first one has already been identified and analyzed previously: the initialization of the configuration struct and the initialization of Winsock to enable network communication.
The second phase consists of the main communication loop with the C&C server, where the agent enters an infinite loop to try to connect with one of its C&C servers. This is not the case with this sample, but as previously seen, ScoringMathTea has the capability to have multiple C&C addresses; therefore, the main action of the ScoringMathTea’s main function is to randomly select a C&C address. However, as we already know, this sample only has one C&C address.

After selecting the command and control (C&C) address, the agent will execute the function to configure the connection to the C&C server, making an initial connection and storing the connection handle for future use.
This function performs this configuration using common WinAPIs, but all of them are resolved dynamically through the API resolution function, analyzed previously. The function defines the following settings:
- Spoofing the User-Agent for further communications: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.42`

- Initial connection to the C&C server via the WinHttpConnect API, returning a connection handle;

- Creation of an
HTTP POSTrequest via the WinHttpOpenRequest API, using the connection handle created previously. A beacon is likely sent to the C&C server here;

- Definition of connection timeouts of
10 seconds, via the WinHttpSetTimeouts;

After this communication is initiated, the agent uses the Handle of the connection created and configured with the C&C to send beacons and receive commands from the operators. In other words, from the previously configured communication, the agent’s communication with the C&C server operates over HTTP/HTTPS using a two-way communication channel that is possibly Base64 encoded, encrypted using the TEA/XTEA algorithm in CBC mode, and optionally compressed.
Firstly, the function that receives the identifier of the previously configured connection seems to construct a pseudo-random request payload (using rand()) from some constants, making it difficult to detect the beacon sent to the C&C using static signatures. An interesting feature of ScoringMathTea’s communication is that, when connecting to the command and control (C&C) URL, if the HTML document header is identified, the function removes this “header” and adjusts the pointers so that the rest of the code can process the actual payload that comes next.
Below, we can see the execution of the function that sends the beacon and receives the response from the C&C (smt_c2_http_send_receive), followed by the function that filters the contents of the buffer received by the C&C.

Below you can see the content of the function that removes HTML header (smt_c2_filter_html_garbage), if identified.

When testing a POST request, it’s possible to identify the header that is filtered, as we will observe later.

And when we simply send a GET request to the root domain, we get a fatal PHP error, indicating that the website is indeed fake, not even containing a page at the root of the domain.

After stripping the header, the agent will receive an encrypted payload, extract a key and an Initialization Vector (IV) from it, and use this key to generate a decryption key schedule through two preparation functions. Then, it uses this key schedule and the IV to decrypt the payload in-place using a variant TEA algorithm in CBC mode. Finally, the resulting plaintext payload is checked; if a flag in the header indicates that the data is compressed, it decompresses it before moving the final command to an execution buffer. Below, we can observe the flow of this execution.

This communication mechanism with C&C is the core of ScoringMathTea. This entire flow is executed to receive and execute commands.
Analysis of the Modular Capacity for Injecting Memory-Reflected Plugins
Another extremely interesting feature is the ability to load and inject reflected plugins into memory. Let’s see.

The plugins that are loaded into memory in ScoringMathTea are loaded using the Reflective DLL Injection technique, receiving the plugin through the communication flow with the C&C analyzed previously, and implemented in three main layers:
- The Main Task Manager of Plugin Load: This is the handler function for orchestrating the download and loading of the plugin.
- Plugin Loading Orchestrator: This function prepares the environment, loading the necessary DLLs and APIs using the PEB Walking technique. In addition, this function feeds a structure I called
PE_CONTEXT, which will contain information about the plugin that will be loaded into memory. This same function is responsible for cleaning up memory using APIs (loaded by the same API Hashing method analyzed previously).
Below you can see the orchestrator’s body, which calls upon the two main functions that will do the heavy lifting.

When we enter the smt_load_apis_dlls function, we can observe the implementation of the PEB Walking technique, with the goal of dynamically loading kernel32.dll, and a series of 7 APIs, which will be used later.


The API offsets are stored in a structure, which is later called to use the APIs, as shown below.

From this point on, the orchestrator function follows the flow for the Reflected loading of the Plugin.
- The Reflective Plugin Loader: This is the function that manually implements the Windows Loader, where it takes the plugin (a PE artifact), submits it to a custom in-line CRC32 algorithm to perform a checksum of the mapped plugin, maps it in memory, and finally executes it.
Right at the beginning of the function, it’s possible to see that the plugin must be a PE artifact, since the function checks both DOS and PE headers.

The function then manually maps the PE plugin in memory, allocating memory regions for each section. Basically, this function implements the Windows Loader manually and in a customized way, to avoid detection by cybersecurity products.

This function also implements a custom in-line CRC32 algorithm, which creates the CRC32 table on the stack and performs a checksum of the mapped plugin, in order to guarantee the integrity of the plugin and that everything has gone as expected. This is also an efficient method for detecting software breakpoints in debuggers. After generating the checksum, the function calls a routine to modify the protections of the previously allocated memory regions, in a manner appropriate for each section.

Finally, this function manually implements the Windows Loader, ensuring that the plugin received by C&C was properly loaded manually, thus avoiding detection by certain cybersecurity products.
When loading the plugin into memory, the main function implements a loop to manually identify a function exported by the loaded plugin, identified as exportfun. Upon identifying this function exported by the plugin, it will be executed.

Conclusion
Therefore, based on all this analysis, the research concludes that ScoringMathTea is a modular Remote Access Trojan (RAT), designed for evasion, with a sophisticated architecture to avoid detection both on the network and at some endpoint aspects. Its C&C communication, operating over HTTP/S with SSL bypass, is a multi-layered channel that protects its command payloads through Base64 encoding, encryption (likely TEA/XTEA in CBC mode), and optional compression. The malware’s core capability it’s a reflective plugin loader capbility, that load a plugin in DLL format, allowing the operator to download and execute plugins entirely in memory.
The most critical potential evasion mechanism is the manual mapping of the IAT (Import Address Table). The malware implements the PEB Walking technique (via smt_load_apis_dlls) to locate kernel32.dll and manually obtain a “clean” pointer to GetProcAddress. With this, it builds its own API table (smt_api_table) at runtime, containing non-hooked pointers to VirtualAlloc, VirtualProtect, LoadLibraryA, etc. This secure table is then used by its loader (smt_reflective_plugin_loader) to perform manual plugin mapping, resolving imports, applying relocations, verifying module integrity with a dynamically generated CRC32, and finally applying the correct memory permissions (smt_set_section_protections) before invoking the plugin’s standard exported function (“exportfun“) to execute the malicious code related to the installed Plugin.
With this, I conclude this research. I hope that all of you who have read this far have learned something or found something interesting. Until next time!
MITRE ATT&CK Mapping
With the goal of mapping the implementations identified during the analysis, the MITRE ATT&CK mapping from ScoringMathTea is shown below.
| Tactic (ID) | Technique (ID) | Comment (Context from our Analysis) |
| Defense Evasion (TA0005) | Reflective Code Loading (T1620) | The ScoringMathTea‘s core capability. It loads plugins (DLLs) directly from memory without writing them to disk. |
| Execution (TA0002) | Native API (T1106) | Implements PEB Walking to locate kernel32.dll and its own version of GetProcAddress to trying to bypass API hooking. |
| Defense Evasion (TA0005) | Obfuscated Files or Information: Dynamic API Resolution (T1027.007) | ScoringMathTea’s manually implemented a Dynamic API Resolution, through API Hashing. |
| Defense Evasion (TA0005) | Masquerading: Browser Fingerprint (T1036.012) | Spoofs a legitimate Microsoft Edge User-Agent string to make its C&C traffic blend in with normal network activity. |
| Defense Evasion (TA0005) | Virtualization/Sandbox Evasion (T1497) | Actively filters HTML tags (like <!DOCTYPE html>) from C&C responses, maybe can be used to defeat captive portals and automated sandbox analysis. |
| Defense Evasion (TA0005) | Debug Evasion (T1622) | Calculates a CRC32 checksum of its own in-memory module after loading to detect if an analyst has applied patches or tampered with it. |
| Defense Evasion (TA0005) | Hide Artifacts (T1564.004) | Suppresses all system error dialogs to ensure the malware fails silently without alerting the user. |
| Command and Control (TA0011) | Application Layer Protocol: Web Protocols (T1071.001) | Uses standard HTTP/S POST requests for all C&C communications. It also bypasses SSL/TLS certificate validation, allowing it to use self-signed or invalid certs. |
| Command and Control (TA0011) | Encrypted Channel: Symmetric Cryptography (T1573.001) | C&C payloads are protected in multiple layers: compressed, then encrypted with a symmetric algorithm (identified as TEA/XTEA), and finally Base64 encoded. |
References & Links
- Gotta Fly: Lazarus targets the UAV sector.
- ScoringMathTea Python String Decryption.
- ScoringMathTea Python API Hash Algorithm.
- ScoringMathTea Yara Rule.