
Executive Summary This analysis represents the second instalment in a comprehensive examination of the KorPlug malware family. Previous reporting detailed the initial loading vector utilising DLL side-loading techniques against legitimate utilities to achieve code execution. The second-stage payload executes via a designated entry point function. Static analysis of the binary reveals that the Initialise function, invoked by the preceding loader stage, exhibits an anomalous Control Flow Graph (CFG) structure. Reverse-engineering efforts targeting this function present significant analytical challenges due to implemented obfuscation mechanisms. Both static disassembly and dynamic analysis methodologies encounter substantial impediments when attempting to enumerate the function's operational logic. The following technical analysis, with the file-details in Table #1, documents the methodologies employed to circumvent the identified obfuscation techniques and extract actionable intelligence regarding KorPlug's second-stage execution capabilities. Table #1. Details of malicious DLL Technical Breakdown Flashback to Stage-3 Analysis of the terminal phase detailed in Part 1 reveals that despite the decoded payload maintaining standard DLL file structure, execution occurs through non-conventional loading mechanisms. The sample implements shellcode-style execution via the EnumSystemGeoID API function call. The payload's initial byte sequence contains redirect instructions that divert execution flow directly to the Initialize function, circumventing standard Windows DLL loading procedures and associated security mechanisms. Static analysis of the binary within disassembly environments reveals that the Initialize function exhibits a complex and extensive Control Flow Graph (CFG) structure. This report documents the methodologies employed to transform the obfuscated CFG representation into a comprehensible format suitable for reverse engineering analysis, while maintaining the integrity of the original execution logic and operational behaviour. O-LLVM The observed CFG structure and block segmentation patterns are consistent with O-LLVM [1] implementation characteristics. O-LLVM represents a modified iteration of the LLVM compiler infrastructure [2] that incorporates code obfuscation capabilities. This toolset is commonly deployed to impede reverse engineering and static analysis efforts across various threat vectors, including malware campaigns, digital rights management systems, and software protection schemes. O-LLVM implements three primary obfuscation methodologies: Control Flow Flattening: Transforms function control flow structures into flattened switch-based dispatch mechanisms, obscuring conventional conditional logic and iterative constructs. Bogus Control Flow: Introduces spurious conditional branches and unreachable code segments designed to mislead disassembly tools and analytical processes. Instruction Substitution: Replaces straightforward instruction sequences with functionally equivalent but syntactically complex alternatives to defeat signature-based detection mechanisms. O-LLVM operates on LLVM's Intermediate Representation (IR) layer, enabling language-agnostic obfuscation across any codebase compiled through Clang or LLVM-based toolchains. The resulting binary complexity substantially increases reverse-engineering difficulty. Analysis efforts included evaluation of multiple publicly available deobfuscation tools and documented methodologies. Testing revealed that existing solutions did not successfully process the sample without significant modification. In RevEng.AI’s assessment of publicly-available tooling, MODeflattener [3] demonstrated the highest compatibility potential, with associated documentation [4] providing foundational reference material for the techniques detailed in this analysis. Control Flow Graph Component Analysis Effective deobfuscation of the target function requires systematic identification and categorisation of individual components within the Control Flow Graph (CFG). O-LLVM obfuscation implementations assign specific operational roles to distinct basic block types. The following analysis examines the functional classification of these block categories to establish the foundation for subsequent deobfuscation procedures. The pre-dispatcher block represents the most readily identifiable component within the obfuscated CFG structure. This block exhibits a characteristic high predecessor count relative to other basic blocks within the function. Analysis reveals that the pre-dispatcher typically contains minimal operational logic, consisting primarily of an unconditional jump instruction directing execution flow to the initial dispatcher block. The first dispatcher block initiates the computational logic responsible for determining subsequent execution paths within the obfuscated function. This block operates through manipulation of a designated control variable, referenced as the state variable within control flow flattening implementations. Individual basic blocks within the obfuscated structure assign discrete values to this state variable. The dispatcher and backbone components subsequently utilise these assigned values to perform calculated branch operations, determining the target basic block for continued execution flow. The dispatcher block initiates execution immediately following initial state variable assignment. The opening instruction within this block performs additional manipulation of the state variable (observed as [esp+9Ch+var_8C] within the analysed sample) to commence the resolution process for determining the current state value and directing execution to the corresponding target block. Subsequent blocks contain the core operational logic intended for execution by the malware sample. These backbone blocks demonstrate consistent instruction patterns utilising JMP, MOV, SUB, and JZ operations. These instructions function collectively to manipulate and evaluate the state variable contents, facilitating calculated jumps to appropriate successor blocks within the execution sequence. This instruction sequence architecture serves as the foundational mechanism governing execution flow control throughout the obfuscated function. The relevant blocks constitute the critical components containing the sample's core operational functionality. These blocks house the primary behavioural logic and executable actions that fulfil the malware's intended objectives. Irrespective of the specific malicious capabilities implemented by the threat actor, the fundamental operational code responsible for achieving the malware's design goals resides within these relevant block structures. Not all critical blocks within the obfuscated structure maintain the pre-dispatcher as a successor block. The terminal block, which denotes function completion, contains no successor relationships. This architectural variation does not compromise execution integrity, as proper resolution of state variable logic and backbone comparison mechanisms ensures natural progression to the terminal block without disrupting established control flow patterns. Within relevant blocks, state variable manipulation represents the primary mechanism governing execution flow transitions. Analysis of the sample reveals that state variable assignment operations consistently occur at the conclusion of each relevant block. These relevant blocks can be categorised into two distinct operational types based on their state assignment methodology. Simple Assignment Blocks implement direct state variable assignment through hardcoded value placement utilising MOV instructions. Conditional Assignment Blocks incorporate decision logic where subsequent state values depend on evaluated conditions, enabling selection between two possible state values. This conditional pattern typically manifests through the following instruction sequence: MOV eax, value1; MOV ecx, value2; CMOVZ ecx, eax; MOV [state variable], ecx. Through systematic manipulation of state variable values at the conclusion of each relevant block that transitions back to the pre-dispatcher, the sample enforces a predetermined execution sequence and ensures processing of functional components in a specified order. This obfuscation technique, while maintaining logical execution flow integrity, generates a significantly more complex CFG structure compared to conventional compilation output. The resulting architectural complexity substantially impedes manual analysis efforts within standard SRE tooling. Deobfuscation Implementation Methodology With comprehensive understanding of the Control Flow Graph components established, analysts can develop automated deobfuscation tools utilising any programming language or framework capable of CFG analysis and structural pattern recognition. The primary objective involves systematic mapping of all potential state variable values assigned within simple and conditional relevant blocks, followed by determination of actual jump targets processed by backbone block logic. The deobfuscation procedure requires enumeration of state variable assignments across all relevant blocks and correlation of these values with backbone block comparison operations to identify legitimate execution paths. Upon completion of this mapping process, the deobfuscation tool can implement binary patching operations to redirect each relevant block's execution flow directly to its intended successor relevant block, effectively bypassing the obfuscated dispatcher mechanism. This process includes removal of operational code within backbone blocks and the first dispatcher, restoring the function to its original, unobfuscated control flow structure. Following successful mapping of execution paths, the deobfuscation process requires patching each relevant block to implement direct jumps to the subsequent relevant block within the legitimate execution sequence. The following implementation utilises Python in conjunction with the angr binary analysis framework to automate the deobfuscation process. Angr provides comprehensive capabilities for extracting critical components from individual basic blocks, enabling systematic identification and classification of the structural elements detailed in the preceding analysis. The implementation iterates through identified relevant blocks to catalog their operational characteristics, classifying each block as either simple or conditional type based on state variable assignment methodology. The analysis process records the specific values assigned to the state variable at the conclusion of each block's execution sequence. Analysis must also account for a specialised category designated as tail blocks. While tail blocks technically qualify as relevant blocks due to their direct predecessor relationship with the pre-dispatcher, they contain no substantive operational logic. These blocks typically consist of a single JMP instruction directing execution to the pre-dispatcher component. During the binary patching phase–which will be covered in more detail further in this report–tail blocks are classified and processed as backbone components, requiring removal from the execution logic. This treatment is appropriate as tail blocks neither manipulate the state variable nor contribute meaningful functionality to the CFG structure, serving only as transitional elements within the obfuscated architecture. With state variable values enumerated and cataloged for each relevant block, the subsequent analysis phase requires identification of corresponding jump operations within the backbone block structures. With comprehensive state variable to jump target correlation established, the final implementation phase involves systematic patching of the binary to eliminate obfuscated control flow mechanisms. This process replaces existing state variable manipulation operations and jumps to the pre-dispatcher with direct jump instructions targeting the appropriate successor relevant blocks. For conditional execution flows, the patching procedure may require insertion of conditional jump instructions followed by unconditional jump operations, depending on the specific branching logic requirements. Attention must be maintained regarding instruction size variations during the patching process. Original instruction sequences, such as MOV [state variable], value; JMP pre-dispatcher, typically consume 13 bytes of binary space. Direct JMP instruction replacements may require only 5 bytes, creating a size differential that must be addressed to preserve binary integrity. The remaining byte space must be populated with NOP (no operation) instructions to maintain proper binary alignment and prevent execution of unintended instruction sequences that could compromise deobfuscation effectiveness. The concluding phase involves systematic removal of backbone blocks, dispatcher components, and tail blocks from the binary's execution flow. This elimination process produces a simplified binary structure that accurately represents the underlying operational logic of the original, unobfuscated code. Conclusion This methodology demonstrates effective analysis and deobfuscation capabilities against heavily flattened control flow structures generated by O-LLVM obfuscation implementations. Through systematic identification of obfuscation architectural components, classification of relevant block types, and reconstruction of original execution logic via targeted binary patching, analysts can achieve significantly improved visibility into the malware's core operational behaviour. While this implementation was developed for analysis of the specific KorPlug sample, the underlying methodology provides a framework adaptable to similar obfuscation schemes through appropriate modifications. This approach parallels adaptation strategies employed with existing tools such as MODeflattener, enabling broader application across O-LLVM obfuscated threat samples with suitable technical adjustments to accommodate implementation variations. As documented in the initial assessment phase, MODeflattener failed to process the analysed sample without modification. Manual intervention involving direct specification of missing basic block addresses and corresponding state variable transition updates enabled successful tool operation for this particular instance. However, these modifications represent sample-specific adaptations that do not provide generalised compatibility across other O-LLVM obfuscated binaries. The complete Python implementation utilising the angr binary analysis framework[5], including comprehensive reference materials and supporting documentation, is accessible through the resources provided in the footnote citations. IOCs Footnotes [1] The LLVM Compiler Infrastructure - https://llvm.org/ [2] O-LLVM Wiki - https://github.com/obfuscator-llvm/obfuscator/wiki [3] MODeflattener GitHub Repository - https://github.com/mrT4ntr4/MODeflattener [4] mrT4ntr4's MODeflattener Post - https://mrt4ntr4.github.io/MODeflattener/ [5] Github link to the code.

Executive Summary In late May 2025, RevEng.AI identified a new sample of KorPlug (a.k.a Hodur) - a well-known Remote Access Trojan (RAT) frequently leveraged in targeted cyber-espionage campaigns - uploaded to a third-party file-scanning platform. This report is the first in a three-part series detailing a malware campaign involving KorPlug. This is the first of three reports describing a KorPlug campaign, detailing a three-stage malware-execution chain. The observed campaign detailed in this Part 1 of reporting employs a multi-stage execution chain designed to evade detection: Initial Access begins with a seemingly benign Microsoft Installer (MSI) file. Execution involves DLL sideloading alongside deobfuscation of a payload file on disk– by abusing a legitimate, signed Canon Inc. executable. Execution of a malicious payload, culminates in a memory-only deployment of KorPlug, using non-standard Windows callback APIs to execute the final stage RevEng.AI assesses with high confidence that this activity is attributable to an China-nexus threat actor based on: Code Similarity Analysis: Large-scale static analysis performed by RevEngAI’s proprietary engine revealed significant code-level overlaps with previously documented KorPlug variants. Infrastructure and Tooling Overlap: Strong alignment with KorPlug infrastructure and techniques observed in previous China-attributed operations [7]. Use of Trusted Signed Binaries: The abuse of a Canon Inc. signed executable for DLL hijacking mirrors tradecraft seen in earlier campaigns attributed to Chinese threat actors, specifically MUSTANG PANDA (a.k.a. Proofpoint: TA416, PwC: Red Delta, or PAN: PKPLUG). Analysis Highlights Stage 1 - MSI File The initial stage of the execution chain is a Microsoft System Installer (MSI) file with an unspecific name (SHA-256: 2e888ffd9d7ab1a210b4165f4f2aa34b1e42e7c4eed79dd9c9f310659c59f10d). An MSI file is a structured database used by Windows Installer to manage the installation, maintenance and removal of software. This file was first seen on a third-party scanning service on 16 April 2025 [8]. Figure 2: Files contained within the MSI file Analysis of the MSI within the CustomAction and InstallExecuteSequence tables reveals further insight into the initial installation logic of KorPlug. In the sample examined by RevEng.AI, a custom action named FovFancUbFDD is defined that is configured to launch an executable file cnmpaui.exe (SHA-256: 2e888ffd9d7ab1a210b4165f4f2aa34b1e42e7c4eed79dd9c9f310659c59f10d) within %LOCALAPPDATA%. The command-line: %LOCALAPPDATA%\ZDYmiFz\cnmpaui.exe is configured to be used. Stage 2 - Execution of Malicious Hijacked DLL The legitimate, signed executable (cnmpaui.exe) executed under the MSI installer, signed by Canon Inc (serial: 20 A9 47 94 7E 70 33 91 C3 00 8B 62 66 06 FA 8F) is a printer utility used for Canon devices. Although the signature has expired (30/12/2020 - 23:59:59), Windows does not mark the signature as invalid – a design choice by Microsoft. The flawed implementation of blindly loading another application-related DLL, cnmpaui.dll (SHA-256: c1124deb209ed291a98c7185bee26946beec7161d4f22519902f80393ffca660) – which allows for DLL hijacking – is relatively straightforward. Towards the end of the entrypoint within the executable, it exhibits a common pattern involving the dynamic loading of the DLL file, the resolution of exported function MaintenanceAppStart using GetProcAddress, and the subsequent execution of the retrieved function pointer with three parameters. The use of DLL hijacking in this context allows the malicious DLL bundled within the KorPlug execution pipeline to be executed under the context of a signed process. This is a common technique frequently used by threat-actors to abuse legitimate executables as means of evading detection on a host. Stage 3 - KorPlug DLL Loader The malicious hijacked DLL named cnmpaui.dll, SHA-256: c1124deb209ed291a98c7185bee26946beec7161d4f22519902f80393ffca660, is signed with a bogus, invalid signature masquerading as the legitimate executable’s signer–Canon Inc. This can be observed below in Figure 4. The DLL file only contains two exports: DllMain and MaintenanceAppStart. MaintenanceAppStart contains the malicious functionality of the DLL loader in this instance, which – as previously discussed – is executed by the loading executable. The first function called within the MaintenanceAppStart function is located at 0x10001000, referred in the pseudocode here as get_function_pointers [1]. Upon initialisation, the function searches for Windows API functions by precomputed hashes and stores their resolved addresses in the Thread Environment Block (TEB) – using Thread Local Storage (TLS). This is a common evasion tactic designed to avoid direct API references appearing in the Import Address Table (IAT). To aid in this resolution, it calls a helper function located at 0x10001F90, referred within Figure 5 as get_function_by_hash [2]. The functions that are resolved are primarily memory management system-call APIs. Further analysis of the MaintenanceAppStart function reveals a typical setup for Windows event-driven applications, although without a message loop. Examining the function flow, we can identify that the Windows Procedure function. By calling a ShowWindow function in MaintenanceAppStart, a message with the ID 0x18 is sent to the message queue to be interpreted by WinProc. This function will forward all messages that do not have the ID 0x18 to NtdllDefWindowProc_W. However, if the message ID matches the expected one (0x18), it triggers the creation of a new thread via CreateThread [3], passing the function located at 0x10001C60 as the target function: The function first proceeds by calling itself recursively. During this process, it invokes GetModuleFileNameW [4] to retrieve the path of the current executable, extracts the directory, and appends the string _\cmplog.dat, a file also present in the MSI package and which appears to be obfuscated. Next, it checks for the existence of this file using the CreateFileW [5] API. If the file is found successfully, the execution continues by calling the final key function in this sample, located at 0x10001490, renamed to load_and_execute [6]. This function is responsible for allocating memory and reading the contents of the file, as well as allocating additional memory to hold the decrypted version of the next stage in the execution chain. Finally, to load KorPlug, this sub-stage makes use of the EnumSystemGeoID API. At first glance, one might assume that the next stage is a shellcode rather than a PE file, but that assumption would be incorrect. In reality, the first bytes of the PE file are shellcode, effectively invoking the first function of the next stage. An extract of the first 22-bytes in the shellcode used for the execution of the DLL can be observed below, which has been highlighted with a colour-key: This loading phase ultimately leads to the execution of KorPlug. This will be analysed in the next part of this series. Assessment RevEng.AI assesses this activity to a currently unnamed China-nexus threat actor based on comprehensive analysis of observed Tactics, Techniques, and Procedures (TTPs) that demonstrate strong alignment with established patterns documented in open-source intelligence reporting – specifically the code overlap with known KorPlug builds, use of DLL hijacking, and tooling overlap. This attribution assessment reflects behavioural consistencies and modus operandi of China-nexus actors. RevEng.AI does not currently have enough evidence to corroborate these findings to known activity from a named China-nexus actor. Conclusion The first stage of this KorPlug execution chain showcases a structured and multi-layered approach designed to evade detection and ensure successful payload delivery. From the use of a seemingly benign MSI installer to the strategic abuse of DLL side-loading and dynamic API resolution, each step in the chain demonstrates a clear intent to blend into legitimate system activity. The reliance on obfuscated payloads, memory-only execution, and signature misuse further reinforces the sophistication behind the operation. How RevEng.AI Can Help RevEng.AI's advanced analysis capabilities provide unparalleled insights into complex malware. Our ability to deconstruct intricate infection chains, identify hidden tactics, and attribute activity to specific threat actors empowers security teams to: Gain deeper threat intelligence: Understand the "how" and "why" behind sophisticated attacks. Strengthen defensive postures: Proactively adjust security controls based on real-world threat actor methodologies. Improve incident response: Quickly and accurately identify compromised systems and eradicate threats. For more information you can request a Demo of the RevEng.AI platform. MITRE ATT&CK® Mapping Indicators Of Compromise (IOCs) Footnotes [1] korplug_get_function_pointers function in KorPlug [2] korplug_get_function_by_hash function in KorPlug [3] CreateThread function (processthreadsapi.h) - https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread [4] GetModuleFileNameA function (libloaderapi.h) - https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea [5] CreateFileW function (fileapi.h) - https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew [6] korplug_load_and_execute function in KorPlug [7] https://www.welivesecurity.com/2022/03/23/mustang-panda-hodur-old-tricks-new-korplug-variant/ [8] https://www.virustotal.com/gui/file/2e888ffd9d7ab1a210b4165f4f2aa34b1e42e7c4eed79dd9c9f310659c59f10d/details

Reverse engineering malware often feels like solving a puzzle where half the pieces are hidden. Among the most common obstacles analysts face is string obfuscation—a technique where malware authors encrypt or encode strings to evade detection and frustrate analysis. This anti-analysis technique appears in virtually every modern malware family, turning what should be straightforward analysis into hours of tedious manual work. In this post, RevEng.AI will explore two approaches to dealing with this form of obfuscation. Firstly, we will demonstrate how to build an IDAPython script that automatically decodes obfuscated strings and renames associated variables using StealC V1 (the predecessor to StealC V2) as our case study. Second, we will showcase how RevEng.AI can expedite this process to dramatically accelerate reverse engineering workflows, freeing up analysts to focus on understanding the malware's actual behaviour. The sample analysed can be accessed here. The Case for Automation Before diving into the technical details, let's understand why automating string decoding is crucial for efficient malware analysis: Time Efficiency: Each obfuscated string requires multiple manual steps: identifying the decoding logic, extracting arguments, and performing the decoding. When dealing with hundreds of strings, this becomes prohibitively time-consuming. Accuracy: Manual analysis is prone to human error. It's easy to miscalculate offsets, incorrectly extract values, or make transcription mistakes that lead to incorrect conclusions about the malware's functionality. Pattern Recognition: Most malware families use a single decoding function throughout their codebase. Once you've identified the pattern, automation can apply it consistently across the entire binary. Focus on What Matters: By automating routine tasks, analysts can dedicate their cognitive resources to understanding the malware's core functionality, network behavior, and potential impact. Understanding StealC V1's Obfuscation When analysing the sample, the following function came across as being of particular interest. It takes a large number of encoded strings and passes them into the function at 0x45c0. We can see the decompiled output of 0x45c0 using the RevEng.AI AI Decompiler below: From this, we can see that StealC V1 employs a straightforward XOR-based string obfuscation technique. The decoding routine is elegantly simple, accepting three parameters via the stack: Key buffer - The XOR key used for decryption Encrypted buffer - The obfuscated string data Length - The size of the string to decode The C implementation resembles: While the algorithm is basic, manually decoding hundreds of strings using this method would be exhausting. Building the IDAPython Automation Script Let's break down our automation approach into four logical components: 1. Locating All Decoder Calls First, we need to find every location where the decodeString function is called: This function leverages IDA's cross-reference capabilities to identify every call instruction targeting our decoder function, giving us a comprehensive list of decoding sites. 2. Extracting Stack Arguments Before each call to decodeString, the malware pushes three arguments onto the stack. We need to backtrack from the call instruction to collect these values: Additionally, we track where the return value is stored after the call: 3. Implementing the Decoder With the extracted parameters, we can reimplement the XOR decoding logic in Python: To ensure variable names remain valid in IDA, we sanitize the decoded strings using regular expressions, removing any non-alphanumeric characters that could cause issues. 4. Automated Variable Renaming The final step renames variables in the Hex-Rays decompiler view to reflect their decoded values: When name collisions occur (multiple variables decoding to the same string), we append a counter to maintain uniqueness while preserving the meaningful connection to the decoded content. Results and Impact Running this script on a StealC V1 sample transforms the analysis experience: Immediate Context: Variables like var_4C become str_kernel32_dll, instantly revealing their purpose Revealed Infrastructure: Command and control servers, file paths, and registry keys become visible Behavioral Insights: API names and system commands expose the malware's capabilities Time Savings: Hours of manual work compressed into seconds of automated processing RevEng.AI Community RevEng.AI users analysing variants of StealC can now benefit from our internal analysis by matching pre-reverse engineered symbol data between the malware variants. To do so, simply match all functions using a RevEng.AI plugin for your SRE tool and set the filter to limit to the sample above. Alternatively, use our Web UI and then export a PDB or ELF debug file. Doing so will match functions using BinNet AI between the samples and merge any debug information. Conclusion Automating string decoding transforms malware analysis from a tedious manual process into an efficient, scalable workflow. Automation frees us to focus on that narrative rather than getting lost in the mechanics of decoding. By investing time in building automation scripts, we not only accelerate individual analyses but create reusable tools that benefit future investigations. Detection YARA Rule YARAI Alternatively, RevEng.AI users can detect StealC variants based on a BinNet AI summary of the sample. This looks at the intent and behaviour of code contained in the malware and finds similar samples uploaded to the platform. For example, the StealC binary referenced in this blog post is most similar to the following files:

Executive Summary In February 2025, the RevEngAI team observed an ongoing LummaStealer campaign that employed a distinct approach compared to the ClickFix method detailed in the previous instalment of this series. In this report, we take a closer look at this campaign and examine how the RevEng.AI platform successfully identified and facilitated the analysis of the associated samples. As described in our previous analysis, LummaStealer (aka LummaC2 or LummaC2 Stealer) is a malware variant designed to extract sensitive data, such as passwords and cryptocurrency wallets, from compromised systems. First identified in 2022, it is widely believed to be a fork of MarsStealer and is predominantly distributed via phishing campaigns. The observed Tactics, Techniques, and Procedures (TTPs) align with previous research on ClickFix; however, at the time of writing, RevEng.AI cannot confirm whether this campaign is directly linked to ClickFix. Although the stages covered in this report do not explicitly reference social engineering tactics, such methods are commonly used to spread LummaStealer. This post will break down the infection chain step by step, focusing on a more autonomous approach where scripts directly download and execute binaries on victim machines while attempting to mimic and hijack well-known DLLs. Running Among the Scripts The First Script At the start of the infection chain, the use of outdated Visual Basic Script [1] sets the tone. Although modern web browsers no longer support VBScript, its execution is enabled by an earlier step that leverages MSHTA to validate and activate the script. By employing this technique, the attacker aims to bypass security measures designed to block certain types of content. In Figure 1, we have the Visual Basic Script content. The script’s objective is clear: it loads and executes a Microsoft Windows PowerShell [2] command designed to access another endpoint. This endpoint is encoded in base64 within the command, and decoding it reveals an additional path on the same domain. Table 1: PowerShell Command content. The Second Script In the next stage of the chain, the script loaded and executed by the previous step is clearly another PowerShell script (Figure 2). It’s objective is to download content from https[:]//guiacui.com[.]br/wp-content-plugins/goodlayers-core-portfolio/languages/es[.]txt. This downloaded content will later be unzipped, and one of the files will be executed. This ZIP file, with a size of 8378189 bytes (7.99 MB) and bearing the SHA-256 hash 9077ef03723211f1f5cb3f26bc292f162d5de9900778042db47d74e437216142, is downloaded with a random name to the system's temporary files directory (%TEMP%) and then unzipped into a folder named LocalApplicationDevelopment, also within the temp directory - rendering a path of %TEMP%\LocalApplicationDevelopment. Two details are noteworthy: the presence of a single executable file, Update.exe, and the inclusion of DLLs with suggestive names: VBoxRT.dll and VBoxVMM.dll. Table 2 provides a list of files associated with the infection chain. In addition to these files, the following DLLs were also present: libcrypto-1_1-x64.dll, msvcr100.dll, msvcr120.dll, libcurl.dll, msvcp120.dll, msvcp100.dll, and libssl-1_1-x64.dll. These files were signed by the same company as Update.exe (Shanghai Chang Zhi Network Technology Co., Ltd.) using certificates issued by DigiCert Trusted G4 Code Signing RSA4096 SHA384 2021 CA1 or Symantec Class 3 Extended Validation Code Signing CA - G2. Additionally, the package included legitimate Microsoft-signed files, such as: msvcp140.dll, concrt140.dll, api-ms-win-crt-conio-l1-1-0.dll, api-ms-win-crt-process-l1-1-0.dll, api-ms-win-crt-math-l1-1-0.dll, and ucrtbase.dll. These are likely included to avoid dependency issues for the Update.exe executable, in Table 2. Table 2: List of files inside the downloaded ZIP file. As indicated in the final line of the script, the executable file is subsequently run. Notably, these DLL names are familiar to VirtualBox users; they are responsible for Real-Time execution and Virtual Machine Monitoring. The First Binary - Update.exe The Update.exe file [3] is 32584 bytes (31.82 KB) in size and has a SHA-256 hash of acfb96912aa38a28faa4c5acbcc976fb3233510126aa40080251db8a8eebafb4. Although it is a signed binary—which generally helps to draw less attention and is more likely to evade security solutions—the signature is attributed to Shanghai Chang Zhi Network Technology Co,. Ltd., raising concerns that it might be a likely stolen signature and compromising its authenticity. Additionally, the main entry point, identified as TrustedMain (at 0x140001480), begins by calling exported functions from DLLs included in the file, specifically VBoxRT.dll and VBoxVMM.dll, which are present in the Import Address Table (IAT). This fixed reference to the DLLs makes the binary likely vulnerable to DLL hijacking, where malicious actors could potentially replace these DLLs to manipulate the program's behaviour and compromise system security. As illustrated in Figure 4, the main function begins by calling the exported functions from VBoxRT.dll, namely, RTR3InitExe, RTPrintf, and RTFlush. When debugging Update.exe, these functions appear to execute normally, and the DLL seems to handle the calls correctly. However, the behaviour changes when it comes to VBoxVMM.dll's VMR3Create function; attempting to step over this call results in an abrupt end of the process. A Deeper look in VBoxVMM.dll VMR3Create VBoxVMM.dll is a 5500928 bytes (5.25 MB) file with a SHA-256 hash of 2eac54ed7103a71a0912d625eef1735b9e1c73ee801175618db72a5544c10beb. Examining the VMR3Create function reveals three primary calls. A closer look shows that the first function, located at 0x18026e360, performs a critical operation by modifying the content of another function later invoked at 0x180165c60 (see Figures 6 and 7). For the rest of this post 0x180165c60 will be referenced as Updated Function and 0x18026e360 as Updater Function. This dynamic alteration of function code during execution hinders disassembly tools and analysts from accurately capturing the true behaviour of the code. Moving to the next instruction at 0x180154ce0, it can be observed that a substantial block of code that also utilises the Updated Function. This step is critical, as it processes and prepares the next stage of the chain. Figure 8 illustrates the sequence of operations performed by the binary - from the initial call in the Updated Function all the way to the routines that successfully load the next stage. In particular, the function at 0x18014e2c0 allocates a large block of memory in 0x1835a8158 (as shown in Figures 9 and 10), loads a file into that space (see Figure 11), and subsequently copies this content for later use within the remainder of the VMR3Create function. The file loaded into memory is a sample of LummaStealer that will be examined in detail shortly, and it can be dumped directly from the process memory. It is important to note that, although the file is loaded into memory, its actual size does not correspond to the value specified in the SizeOfImage field, which is set to 0x5F000 bytes. When comparing the available bytes between the memory regions where the file begins (from 0x1835A8158 to 0x183554119), we actually observe fewer bytes than the reported SizeOfImage. This discrepancy suggests that SizeOfImage may not accurately reflect the contiguous memory allocation for the file in this instance. However, by summing the SizeOfHeaders and SizeOfRawData, we obtain a total of 0x54000 bytes, which accurately represents the file's content, that’s a common behaviour and can be solved with that approach. After determining this size, the full content was successfully dumped from memory. LummaStealer Binary Upon execution of the LummaStealer binary, a message box is displayed asking the user whether they want to run the malware. This prompt appears to act as a safeguard, ensuring that affiliates of LummaStealer do not inadvertently distribute builds that have not been properly encrypted. Unencrypted builds could lead to increased detections by antivirus vendors, as the code would be more vulnerable to analysis and signature-based detection (Figure 12). Anti-Analysis Techniques Examining the dumped binary reveals a LummaStealer sample [5] with a SHA-256 hash of 366bd0e69838b03d40352962e46e8b2eedf5f467d01a0a9f073056dc51a5b3e0. The primary challenge for analysts working with this binary is the obfuscated control flow implemented during its compilation process. When you load the binary into a disassembler or debugger, you'll notice that the function endings do not align with the expected addresses indicated by the disassembly. This behaviour disrupts IDA and other disassemblers because they cannot accurately determine the next execution flow. Instead, the actual control flow is resolved dynamically at runtime, requiring execution to retrieve the correct instruction paths. This discrepancy is due to the obfuscated control flow implemented in LummaStealer samples, which employs a unique technique that greatly complicates analysis. By utilising dispatcher blocks (Figure 14 shows a simple example of one of the patterns), the true control flow is often concealed behind jumps that static analysis alone cannot reliably resolve. Figure 15 provides a YARA rule designed to detect LummaStealer’s control-flow obfuscation dispatchers, which employ some distinct patterns. Figure 15 provides a YARA rule designed to detect LummaStealer’s control-flow obfuscation dispatchers, which employ some distinct patterns - this can also be found in the YARA Rules section. To facilitate the reverse engineering process, symbolic backward slicing [6] can be applied to identify the operations critical to these jumps, enabling analysts to patch them and better visualise the application's workflow. This stage also employs anti-symbolic execution techniques, incorporating loops that can become infinite without proper handling. We mitigated this technique by ensuring that operations capable of causing an infinite loop were executed only once. Another important technique used by this sample is Heaven's Gate, which allows a 32-bit process to execute 64-bit code on 64-bit Windows systems by switching modes. This mode transition bypasses WOW64 limitations, granting the malware access to 64-bit APIs while further obfuscating its control flow and complicating analysis. Network Traffic This sample also exhibits the expected behaviour of LummaStealer by attempting to connect to well-known Command and Control servers (Figure 17). It will use these servers to maintain communication, execute further stages of the attack, and exfiltrate sensitive data from infected victims. Find and Track The RevEng.AI platform simplifies tracking and identifying similar samples by allowing users to compare functions within a given sample. By analysing both strong and weak matches, researchers can identify new variations or directly compare different samples, aiding in malware tracking and research. Examining the functions in the analysed sample (Figure 16), we find that the function responsible for retrieving module pointers (0x447570) closely matches a previously identified LummaStealer sample [7] (SHA-256: 1cf75f97e3bb843054d7a338289fcd892ca800cf2781999501491b33c25681d9). The RevEng.AI platform detects a function at 0x46560 in this older sample with a 90.42% confidence match for the given function. A closer inspection of this function reveals a call to 0x46520, which, as shown in Figure 19, serves the same purpose as the previously identified “get PEB” function (0x447520 in Figure 16). Malware samples likely employ different approaches to achieve the same goal, and the platform can help you keep track of even larger changes. Returning to the module pointer retrieval function (0x447570), the RevEng.AI platform also highlights additional dispatches within the matched function (Figure 19). This could likely indicate additional features embedded within the same code block, warranting further investigation. Conclusion In summary, this analysis breaks down each stage of the infection chain, from the initial script execution to the final payload deployment. While some stages employ obfuscation techniques, such as dispatcher blocks, anti-symbolic execution, and Heaven’s Gate, others are more direct, allowing for step-by-step deconstruction. By systematically analysing function modifications, memory allocations, and command execution flows, we can reconstruct the malware’s behaviour, identify its C2 communication patterns, and understand its data exfiltration mechanisms. This structured approach is crucial for uncovering LummaStealer’s tactics, techniques, and procedures and ensuring a deeper understanding of its behaviour. Host IOCs Table 2: Host Indicators of Compromise. Network IOCs Table 3: Network IOCs Table. YARA Rules Footnotes [1] MITRE, Command and Scripting Interpreter: Visual Basic - https://attack.mitre.org/techniques/T1059/005/ [2] MITRE, Command and Scripting Interpreter: PowerShell - https://attack.mitre.org/techniques/T1059/001/ [3] RevEng.AI Platform, Update.exe File - https://portal.reveng.ai/analyses/162379 [4] RevEng.AI Platform, VBoxVMM.dll File - https://portal.reveng.ai/analyses/162832 [5] RevEng.AI Platform, LummaStealer File - https://portal.reveng.ai/analyses/162994 [6] Mandiant, LummaC2: Obfuscation Through Indirect Control Flow - https://cloud.google.com/blog/topics/threat-intelligence/lummac2-obfuscation-through-indirect-control-flow [7] RevEng.AI Platform, Similar LummaStealer File https://portal.reveng.ai/analyses/174808

Executive Summary Throughout 2024, RevEng.AI has been actively monitoring LummaStealer as part of its mission to uncover and analyse emerging threats across the commodity malware landscape. In mid January 2025, we observed a LummaStealer campaign being distributed via ClickFix - in the form of fake reCAPTCHA pages. RevEng.AI has further examined and documented the delivery chain of LummaStealer in an effort to uncover whether the final payloads have also been subject to alterations in an effort by actors to aid the compromise of victim devices. LummaStealer (a.k.a. Lumma, LummaC2 Stealer) is malware that focuses on extracting sensitive data like passwords and cryptocurrency wallets from infected systems, often delivered through phishing campaigns - first observed in 2022 and thought to likely be a fork of MarsStealer. Throughout 2024, RevEng.AI monitored the ClickFix delivery mechanism used to distribute LummaStealer, first identified by ProofPoint in May 2024 [1]. ClickFix uses deceptive tactics, including phishing and fake reCAPTCHA pages from an open-source repository [2], to trick users into running commands. This report will detail the initial stages of a ClickFix delivery chain: ClickFix pages masquerading as Google reCAPTCHA; the MSHTA execution; several PowerShell stagers and in-turn a PE in the form of a .NET loader. It Started with a Hash During 2024 and into 2025, RevEng.AI acquired and identified a number of LummaStealer samples in an effort to continue its mission to support the reverse-engineering and malware analysis community. In the latest sample (https://portal.reveng.ai/analyses/158599-8?analysis-id=146089), we have observed LummaStealer continue to alter its code base while maintaining its core malicious capabilities. While these changes may impact static rule-based approaches to identifying these malicious payloads such as YARA, the RevEng.AI Binary Analysis platform automatically matched functions from variants of this malware based on our AI models' semantic understanding of the underlying machine code. This approach, in turn, means that constant human maintenance of a YARA rule is not required and we can build AI rules for detecting malware families and their variants. As such, observation of an alternate delivery mechanism prompted further investigation and analysts were able to quickly identify differences between previous samples using the function diff view. In the remainder of this post, we detail the stages needed unpack and examine this latest threat. Stage 1 - ClickFix Delivery Page Masquerading as Google reCAPTCHA Using a well-known captcha service likely leads the user to perceive the interaction as legitimate, building trust and reducing skepticism. By relying on a widely recognized service, attackers can exploit the user’s familiarity with the system, making them more likely to engage with the malicious site. The site then attempts to convince the victim to click a 'verify' or an ‘I’m not a robot’ button and also indicates that they need to manually paste the loaded payload into a run dialog box. In most cases targeting Microsoft Windows observed by RevEng.AI, ClickFix attempts to lure unsuspecting victims into copying malicious commands to their clipboard and executing them via PowerShell or MSHTA, making it a simple yet highly effective way to propagate malware. Upon initial analysis, RevEng.AI identified numerous parallel campaigns being conducted by an unknown threat actor that was consistent with the delivery chain detailed in this report. The base of the analysis for this ClickFix delivery-chain will be: https[:]//googlsearchings[.]online/you-have-to-pass-this-step-2[.]html. The fake reCAPTCHA page mimics real behavior and uses JavaScript to load MSHTA (Figure 5) [3], copying a command to the victim's clipboard to download and execute a malicious payload via a Windows LOTL executable, bypassing security measures and increasing delivery success. Figure 5 contains the malicious JavaScript content that was available on January 13, 2025, accessible via the URL https[:]//sharethewebs[.]click/riii2-b.accdb, which is hosted by Cloudflare (AS13335). Although not the primary focus of this report, it is worth mentioning that some delivery chains were observed using Windows PowerShell scripts (Figure 6) [4] instead of the focus of this analysis, MSHTA. The command is encoded within the JavaScript in an attempt to evade detection, concealing the true intention of downloading and executing the next stage of the attack chain: https[:]//amazon-ny-gifts[.]com/shellsajshdasd/ftpaksjdkasdjkxnckzxn/ywOVkkem[.]txt. Figure 6 contains the malicious JavaScript content that was available on January 21, 2025, accessible via the URLs https[:]//www[.]sis.houseforma[.]com[.]br and https[:]//horno-rafelet[.]es. This resulted in the loading of the PowerShell command shown in Table 1 to the victim's clipboard. Table 1. ClickFix MSHTA and PowerShell Execution Examples. Stage 2 - ACCDB Content Executed By MSHTA Following the URL retrieved from the MSTHA argument in the previous stage, you will encounter a file with a size of 954,627 bytes (932.25 KB) and a SHA-256 hash of 179e242265226557187b41ff81b7d4eebbe0d5fe5ff4d6a9cfffe32c83934a46. The initial bytes correspond to an obfuscated payload, followed by some junk bytes that represent an ISO file, likely designed to mislead anti-virus scanning solutions. To effectively proceed and comprehend the next stage, the initial large string must be deobfuscated by extracting every 2nd character and skipping the next, continuing this pattern until the end of the ASCII string. This transformation can be accomplished using a regular expression combined with common data manipulation tools or a Python script, as demonstrated in the example provided in Figure 8. Stage 3 - Resulting JavaScript Content Stage 3 contains JavaScript, with the approach for deobfuscation similar to the previous stage (Stage 2). The content present in unCR requires to be isolated (variable names and size may vary in other campaigns, (SHA-256: f8cfc73614c279e143b97a0073048925ce8b224ee7ecc03e396d015151147693). Deobfuscation of this script results in the obfuscated JavaScript code in Figure 9. Figure 10 presents a Python script that reimplements the deobfuscation routine used by the JavaScript code. In this routine, a variable holds the encoded data to be processed. A for loop iterates through this data, subtracting a specified number from each integer value, converting the resulting value into its corresponding character, and appending it to a final variable. This final variable ultimately holds the plaintext value required for the next step. Stage 4 - Base64-encoded PowerShell Content The -Enc parameter in the Windows PowerShell command (SHA-256: bea8b8deafad49b4760f6caa17aa8a9bd05786a57a9b6758c7c5d4342df3ebbc) clearly indicates the usage of base64. After the base64-decoding is complete, it results in a PowerShell script with the SHA-256 hash of 61a2424a8442751d9b9da3ff11cb82c5d2ba07a93ee66379db02d4a5cb24a67e. The obfuscated PowerShell script results in further obfuscated PowerShell, containing variables with very long names - a further barrier employed by the threat actor to increase the difficulty of analysing the malicious code. Further deobfuscation through variable renaming, and basic formatting, reveals the true intent of the code in Figure 13. Taking a closer look, unlike the previous stage, there is also a decompress using LZ77 on top of base64-encoded content. You can write your script to do that or use a data manipulation suite such as CyberChef. As shown in Figure 14 (SHA-256: 3739d6cc6eb06121e504eadffecf71568ddcedb98ee6bbbb75bd4b0244b4aec8), after decoding the payload, further obfuscated PowerShell is revealed. Stage 5 - Base64-Decoded, Decompressed PowerShell Content Stage 5 focuses on downloading and executing the next stage of the delivery chain, allowing us to proceed further by reaching another payload at https[:]//h3.errantrefrainundocked[.]shop/riii2[.]aspx. Even though the URL points to what appears to be an aspx file with the size of 9636902 bytes (9.19MB) (SHA-256: 6291ca6b9cf44bb7da8a2740cdf95aacb6eb1b2de32eece3073619a223970d 5e), the reality is that this file is actually a Windows PowerShell script. By doing so, the malware employs a technique aimed at bypassing solutions that are intended to block and filter the download of files with the correct PowerShell extension. However, to complicate the reverse engineering process, evade signatures and hinder detection by security tools, this script is significantly larger than the one from the previous stage, utilizing obfuscation techniques to increase stealth and delay analysis. To achieve a better understanding of the obfuscated content, the same approach used in Stage 4 can be used here, in : simply renaming the variables. After further analysis, it is observed that even post-renaming, it appears the code does not achieve anything noteworthy. However, upon closer inspection, some key findings detailed below were observed by RevEng.AI. A large variable containing the encoded content that will lead to the next step in the chain (Figure 16). The function responsible for decode the variable containing the next stage. The seemingly useless code is not so useless after all; some of it consists of mathematical operations that will ultimately form characters for a script to be used later in the code. Table 2: Variables and their real values after processing. Analyzing $tkMcVgT, the variable shown in Figure 18, all the variables inside it will be 0, except for $rPLyMMsrI, which will be 955. By adding these values to the equation in $tkMcVgT, you will obtain 82. This value will then be used to derive the corresponding ASCII character, which will be the character ‘R’. This approach to building strings enables effective obfuscation of core elements and large code sections. This can be particularly useful for stealthy lines, like the one in Figure 19, which targets the disabling of PowerShell's Antimalware Scan Interface (AMSI) protection. Since the main goal is to reach the next step, which can be achieved in several ways: extracting the XOR key and creating a script to handle it, or even modifying the script to print the value returned by that function. Delete everything after the function definition, then add $data = fdsjnh; Write-Output $data; which will do exactly that, print the decoded content as needed. Stage 6 - Deobfuscated Powershell The next stage consists primarily of additional PowerShell script (SHA-256: 58b27398e324149925adfbab4daae1156e02fd3d8be8fb019bcdfa16881a76fe). However, it is not obfuscated and is much more straightforward. The goal is to take the variable $a, decode it from Base64 (SHA 256: 3d3e71be5f32b00c207e872443d5cdf19d3889f206b7d760e97f5adb42af96fb), and load it as an .NET assembly using Invoke. Stage 7 - Obfuscated .NET Stager Upon analyzing the first Portable Executable file (SHA-256: 3d3e71be5f32b00c207e872443d5cdf19d3889f206b7d760e97f5adb42af96fb) with a size of 1,337,856 bytes (1.28MB), you’ll come across an obfuscated .NET file. Despite the obfuscation, a closer look at the end of the main function reveals the primary objective: loading a DLL. Stage 8 - Reactor Obfuscated .NET DLL This DLL (SHA-256: f279ecf1bc5c1fae32b847589fe3ae721016bde10f87a38a45052defcf2a1c74) has a file size of 1,185,280 bytes (1.13MB) and is also obfuscated, this time using .NET Reactor, which adds an additional layer of complexity, but can also be supported by several tools that properly handle Reactor’s approach. It includes several anti-analysis mechanisms, such as checks for debuggers, common sandbox DLLs, and environment variables, designed to prevent detection in controlled environments. Furthermore, it establishes a connection to the command-and-control server and ensures the loading of LummaStealer. Conclusion In summary, the process involves analyzing each stage of the chain, from decoding Base64-encoded payloads to handling PowerShell scripts. While some stages are obfuscated, others are more straightforward, allowing us to directly manipulate variables for further decoding. By following this methodical approach, you are able to decode the content, load it as assembly, and progressively advance through the stages. This systematic breakdown is essential for understanding the underlying mechanics of the chain and ultimately reaching the final objective. In the next part of this series, we will explore how the Lumma malware continues to be loaded within the chain, as well as how RevEng.AI can assist in both the analysis and identification of the given samples. Host IOCs Table 3: Host IOCs. Network IOCs Table 4: Network IOCs. Footnotes [1] As detailed in industry reporting, ClickFix has been used to deliver Latrodecus, NetSupportRAT, XWorm & BruteRatel C4 since at least March - https://www.proofpoint.com/uk/blog/threat-insight/security-brief-clickfix-social-engineering-technique-floods-threat-landscape [2] John Hammond, recaptcha-phish - https://github.com/JohnHammond/recaptcha-phish [3] MITRE, System Binary Proxy Execution: Mshta - https://attack.mitre.org/techniques/T1218/005/ [4] MITRE, Command and Scripting Interpreter: PowerShell - https://attack.mitre.org/techniques/T1059/001/ [5] MITRE, Command and Scripting Interpreter: JavaScript - https://attack.mitre.org/techniques/T1059/007/

Introduction The challenge of converting low-level assembly code back into human-readable source code is a cornerstone problem in reverse engineering. In this post, we summarise recent work done at RevEng.AI that addresses this challenge through the development of foundational AI models designed for decompilation. As we shall see, this approach is able to produce surprisingly accurate code that more closely resembles human-written source-code than existing rules-based decompilers. Whilst these models currently still have limitations (which we discuss), they offer an exciting new approach that has the potential to greatly aid reverse engineers and security analysts. The Importance of Decompiling Assembly Code Source code written by humans is typically far more comprehensible than the raw assembly code found in binaries. The process of decompilation - translating this low-level assembly code back into human-readable source code - can therefore play a vital role in reverse engineering and software security analysis. In particular, it plays a crucial role in: Malware Analysis - Enabling security researchers to understand and counter malicious software. Legacy Code Maintenance - Recovering source code from old, compiled software. Vulnerability Research - Identifying security flaws in closed-source applications. Intellectual Property Protection - Verifying unauthorised use of proprietary code. However, decompilation faces inherent challenges. The compilation process strips away valuable information such as variable names and transforms higher-level code structures like loops and conditionals in a fundamentally irreversible way, making perfect reconstruction impossible. While established tools like Ghidra and IDA Pro can reconstruct a pseudo code equivalent of the disassembly, it can often be difficult-to-understand or bears little resemblance to human-written source code. Traditional vs. AI-Powered Decompilation Traditional decompilers rely on predefined rules and heuristics, using techniques like control flow analysis and type inference to reconstruct higher-level code. Whilst sophisticated, these systems often struggle with complex code patterns and produce literal, convoluted translations. AI-powered decompilation offers a fundamentally different approach. Instead of following rigid rules, modern AI models learn patterns directly from large datasets. This has two key advantages: The output will naturally resemble human-written code, including natural programming constructs and idioms that make better use of the surrounding context, since the model is trained to mimic real source code examples. Not limited by handcrafted rules - everything required for good decompilation can be learned automatically, including things that are difficult to capture in a set of rigidly defined rules. This has the potential to improve the semantic accuracy of the decompiled code. An Illustrative Example Let's start with an example taken from the HumanEval benchmark, which is a collection of code solutions to programming problems and corresponding tests that need to pass. We have the original source code for a function that takes two lists of integers, along with the sizes of those lists, and then counts the number of even numbers in both lists combined. If that number is more than or equal to the size of the first list, it returns "YES", otherwise it returns "NO". The model takes the function's assembly code (extracted and preprocessed with our internal toolkit) and feeds it into the AI model, which then produces its best guess at replicating the original source code. Original Source Code Ghidra Decompilation AI Decompiler Output Whilst the variable names and formatting are slightly different, we can see that the AI decompiler is able to perfectly replicate the logic of this function in a way that it is relatively simple for an experienced human programmer to understand. On the other hand, the Ghidra decompilation to pseudocode is much more difficult to interpret. Firstly, it does not fully infer the correct types in the function signature, and then the series of operations it performs is much harder to follow, including a number of do-while loops and a goto statement, as well as multiple nested type castings and bit-wise operations. The variable names it returns are also very generic and not at all informative, whilst the variable names the AI decompiler uses are much more natural. AI Decompilation - Problem Setup, Dataset & Training At a fundamental level the problem of decompilation can be posed as a "sequence-to-sequence" translation problem. We have an input (the extracted assembly code for a given function in a binary) and we have a desired output (the original source code), and so we want to train a generative model that can take an input and generate a plausible corresponding output. To do this, we leverage tools/model architectures that have found great success in the field of natural language processing (NLP) for translation tasks - specifically encoder-decoder transformer models. We take inspiration from a number of existing projects [1] [2], although our data collection, preprocessing, tokenisation and model architecture differs significantly from these works. To train a model of this kind, we need to generate a dataset of pairs of assembly inputs and corresponding source code targets. We then tokenize these datasets, converting each input into a sequence of tokens (integers), which can be fed into the transformer architecture. The transformer is trained end-to-end to optimise a loss function that encourages the sequence of output tokens it produces to be as close to the target output (for a given input) as possible. Whilst this is a simple, almost "brute-force" approach to solving the problem, experience has shown that when run at scale with large models and large datasets the performance can be remarkably good. When training a large-scale generative AI model, the most important component is the dataset. The size, diversity and quality of the data used for training is absolutely critical for maximising final performance. We also need to take great care in the way that we represent both the assembly code and the target source code, standardising the formatting and inserting special tokens in place of function calls/custom types to help alleviate issues related to hallucination. We experimented with a variety of model architectures and preprocessing/tokenisation strategies. Our best performing model currently is an encoder-decoder transformer with 1.1 billion parameters, trained on our dataset of ~33 million unique extracted assembly/C source code function pairs. These consist of ~12 million unique C functions compiled using different optimisation settings and different compilers (GCC and Clang). Rather than fine-tuning an existing model, we perform our own pre-training, since we felt that the difference between assembly and natural language/other coding languages is quite significant. Whilst it is likely that larger models would improve performance even further, we wanted to constrain the size of the model somewhat to ensure it can be served efficiently on the RevEng.AI Binary Analysis platform. Although work done on the LLM4Decompile project has suggested that performance can be improved (at least on a particular benchmark) by using the Ghidra decompilation as input to the model rather than the raw assembly code, we chose not to do this. The reason is that although it is likely in many cases that converting decompiled C into "standard" source code is easier than doing so directly from the binary, it is also the case that Ghidra quite often makes irreversible mistakes. In such cases, this fundamentally limits how good the decompilation can be. We also find that by carefully processing and curating the datasets and optimising the tokenisation strategy we can get performance that exceeds the performance of the comparable LLM4Decompile models (see HumanEval benchmark results below). However, it is an interesting question whether using another form of intermediate representation as input (with less information loss) could improve the performance of the model further, and is something we are currently exploring. Annotated Examples In this section we show and annotate a selection of examples. Note that we have omitted the processed input, and just show the original source code alongside what the AI decompiler produces (although it is important to emphasise that the model never uses any of this original source code as part of its input). Each example has some annotations discussing what the function does and noting anything of interest in the AI decompiler's attempt to reconstruct the function. Annotation HumanEval Benchmark Performance A standard benchmark for code generation tasks is the HumanEval benchmark, which is a set of programming challenges and tests that a solution to each challenge must pass. Following [1] , we use a version of HumanEval where the solutions are converted to C. Importantly, all of these tasks only use standard library functions and are therefore relatively easy to compile with GCC or Clang. The evaluation works by compiling the original solutions into binaries (with multiple optimisation levels), extracting the assembly code for these functions, feeding this into the AI decompiler and then substituting the decompiler output in place of the original function implementation to see if the tests still pass. Note that this is quite a challenging and unforgiving benchmark, in the sense that a single mistake will be punished just as much as code that is completely wrong. Establishing evaluation protocols with a stronger feedback signal (i.e. not just a binary success or failure) is something we are actively thinking about here at RevEng.ai, but is left for future work. The table below shows the results on this benchmark. We include a breakdown of the fraction of generated code that successfully passes the original tests in total and for each of the compiler optimisation levels considered. We compare our results against Ghidra and against a variety of models released by the LLM4Decompile project. Some of these take in assembly code as input, whilst some take in Ghidra's attempted decompilation instead (this is noted below each model). We can see that the RevEng AI Decompiler significantly outperforms both Ghidra[3] and the most comparable LLM4Decompile model (1.3b params with assembly input), even outperforming the 6.7b parameter assembly input model. It also slightly outperforms the model of comparable size that takes as input the Ghidra decompiled C, and is only beaten here by a model that is substantially bigger. Conclusions Our work on AI-powered decompilers has demonstrated remarkable promise, and we're actively integrating this technology into our platform for users to experiment with. Through careful dataset curation and innovative training approaches, we've shown that a relatively compact model can produce high-quality decompilations that rival or exceed traditional approaches. Whilst the model is by no means perfect and still makes mistakes, we believe this approach has the potential to lead to significantly more reliable and user-friendly decompilers in the future. Looking ahead, we're pursuing several exciting avenues for improvement: Expanding our dataset and processing pipeline to support C++ and other languages, while incorporating compilers from additional platforms such as Windows. Exploring alternative intermediate representations as model inputs, potentially replacing raw assembly code with more structured formats. Developing more sophisticated evaluation metrics that go beyond simple pass/fail testing. Integrating semantic correctness feedback directly into the training process to enhance output quality. Our AI Decompiler is available for existing RevEng.AI users immediately. For new users wanting to gain access, sign-up and join the waitlist here. Footnotes/References LLM4Decompile Project, Tan et. al (Github, Paper) SLaDe: A Portable Small Language Model Decompiler for Optimized Assembly, Armengol-Estapé et. al (Paper) Note: the comparison with Ghidra is not entirely fair, since Ghidra produces a kind of pseudo-C code rather than standard C and may include e.g. references to addresses of strings and doubles which will not easily recompile/re-execute even if semantically the code is essentially correct. Although equally, this can be seen as a limitation of Ghidra. Evaluating Large Language Models Trained on Code (Paper)

In early September 2024, RevEng.AI conducted a brief analysis of the evasion techniques leveraged by modern malware and command-and-control (C2) frameworks. This analysis underscores the methods employed by adversaries to bypass traditional detection mechanisms and security solutions, enabling their malicious activities to remain concealed. The techniques described reflect the increasing tendency of malware and C2 developers to leverage low-level system features and obscure functionality within Windows operating systems, which are less understood or monitored by conventional security tools. Some of the techniques discussed in this report are far from being "novel", a term frequently used by certain vendors to describe them. In reality, many of these methods have been in use for years, particularly within popular English-speaking game cheat development communities. The adoption of these tactics by malware authors often follows a delayed timeline, with techniques pioneered by cheat developers eventually being integrated into broader malicious frameworks. The research focuses on advanced evasion strategies that include direct driver access, manipulation of DLL loading notifications, abuse of NTFS streams for locked file deletion, and Vectored Exception Handler (VEH) abuse, among others. These tactics demonstrate an adversarial shift toward circumventing high-level constructs in favor of deeper interaction with the Windows kernel, enabling stealthy operations that are harder to detect, prevent, or analyse. This short report delves into these Tactics, Techniques & Procedures (TTPs), providing baseline implementations in some cases. Techniques Direct Driver Access In a typical Windows environment, applications interact with the operating system through a series of high-level APIs. These APIs abstract the complexity of interaction, allowing developers to write code without needing to understand the intricacies of further low-level constructs. However, for certain advanced operations, especially evasion of security mechanisms, it may be beneficial—or even necessary—to bypass these APIs and interact directly with the system's drivers. Drivers in Windows operate at a lower level within the system architecture, are generally managed by the Windows kernel and are accessible via IOCTL (I/O control) commands (subject to implementation), which provide a method for user-mode applications to send instructions directly to a driver. Direct driver access, therefore, refers to the practice of bypassing the typical Windows API layer and interacting directly with system drivers via these IOCTLs. This approach can provide significant advantages, such as greater control and the ability to perform operations that might be restricted or monitored if done through higher-level APIs. Examples of Direct Driver Access The tcpip.sys driver is responsible for handling low-level network communications in Windows. By directly interacting with this driver, a process can perform network operations such as sending and receiving packets without going through the standard Windows Sockets API (Winsock). This can be used to create stealthy communication channels that are harder for network monitoring tools to detect, as the process bypasses common networking APIs that are typically logged or inspected. The ntfs.sys driver manages the NTFS file system, handling all file and directory operations. By directly accessing this driver, an application can manipulate files and directories at a lower level, potentially bypassing security mechanisms like file system filters used by security solutions (depending on specific implementations). For example, malware could use direct driver access to hide files, create stealthy file streams, or delete locked files without triggering standard file monitoring systems. The http.sys driver is used by the Windows HTTP Server API to handle HTTP requests at the kernel level. By directly interacting with http.sys, an application can create an HTTP server that operates entirely in kernel mode, bypassing user-mode components like IIS (Internet Information Services). This technique can be used to establish a covert C2 server that is less visible to traditional monitoring tools, as the HTTP traffic is handled directly by the kernel driver. The portcls.sys driver manages audio operations in Windows. Direct access to this driver allows an application to manipulate audio streams at a lower level, potentially bypassing Windows-wide audio effects or filters. For example, malware could use this capability to intercept or inject audio data, enabling stealthy eavesdropping or data exfiltration via audio channels. Direct driver access eliminates the overhead associated with higher-level API calls, which can be particularly beneficial in evading detection. By interacting directly with drivers, applications can bypass security mechanisms or API restrictions that might otherwise prevent certain operations. It also provides greater control over hardware resources, allowing applications to perform operations that are not exposed through standard APIs. Thread Pool Abuse (Tp*) Using the example of NtWriteVirtualMemory, a pointer to a structure containing the necessary arguments is passed to the TpAllocWork API, which handles thread pool work items, and the callback function WorkCallback is implemented in assembly. The WorkCallback callback-function extracts the required arguments from the structure and assigns them to the appropriate registers (in the case of x64: RCX, RDX, R8, R9), while managing the stack to accommodate for the remaining arguments. This approach allows for precise control over the call to NtAllocateVirtualMemory, ensuring that the stack appears clean and legitimate to an outside observer - i.e. a security solution. By carefully adjusting the stack without altering the stack frame, the call stack remains clean and can be properly unwound. This method maintains a legitimate-looking stack, upon inspection by security solutions, and avoids common pitfalls associated with stack manipulation The function definitions can be found in Process Hacker's Windows definitions here. VEH Abuse Vectored Exception Handlers (VEHs) are an extended feature of Structured Exception Handlers (SEH) in Windows, and can be abused for a variety of scenarios, primarily function hooking and process injection. Benefits of using VEH For Hooking No Code Modification: VEHs do not require changes to the actual function code. Traditional hooking methods often involve modifying function prologues or inserting trampolines, which can be detected by security tools. VEHs work by triggering exceptions and redirecting execution at runtime without altering the code in memory. Temporary and Context-Sensitive: VEHs can be registered and unregistered dynamically, allowing hooks to be applied only when needed and removed afterwards. This dynamic application minimizes the hook's footprint and reduces the likelihood of detection by security solutions. Practical Approach Of Abusing VEH For Hooking Use AddVectoredExceptionHandler to register a vectored exception handler. This handler will be invoked when an exception occurs in the process, providing an opportunity to manage control flow redirection. Applying Page Protection: Protect the target function or memory page with PAGE_GUARD using VirtualProtect. This protection causes an exception to be triggered when the guarded page is accessed, creating an opportunity for the VEH handler to intercept and redirect execution. Exception Handling: When a guarded page is accessed, a STATUS_GUARD_PAGE_VIOLATION exception is raised. The VEH handler catches this exception and checks if it occurred at the target function's address. If so, it modifies the instruction pointer (i.e. EIP or RIP) to redirect execution to the hook function. Single-Step Exception: To ensure that the guard page protection is reapplied, the VEH handler sets the single-step flag in the EFlags register. This triggers a STATUS_SINGLE_STEP exception after the redirected instruction is executed, allowing the handler to reapply PAGE_GUARD protection and maintain stealth. Disabling Instrumentation Callbacks (Nirvana) Instrumentation Callbacks (also called Nirvana internally by Microsoft) allows the tracing of system calls from inside a process - given that the required permissions are set, and was first present in the Windows 7 SDK. This is used by a few security solutions, however is not widely adopted. In the context of the current process, the callback can be set to NULL: Figure 1 - Disabling Nirvana Hooks Etw Event Neutralisation Event Tracing for Windows (ETW) is a feature that enables the collection of system-wide events from both user-mode and kernel-mode applications. ETW is often used by security solutions (however, in the form of EtwTi) to monitor system activity, providing visibility into various events, such as process creation, network activity, and DLL loads. For attackers, ETW presents both a challenge and an opportunity. While ETW can be used to detect malicious activity, it can also be manipulated to hide or obscure such activity. By carefully controlling which events are generated or by tampering with the ETW infrastructure, attackers can reduce their visibility to security tools. The most common approach is to patch the first byte of the target function with a ret (x64 instruction encoding:0xC3), on the following candidates: Taking the approach of the lowest-call API, the function NtTraceControl can be patched within kernel32.dll when a consumer attempts to gather a HANDLE to Etw Again, the function NtTraceEvent is used for writing Etw events and can be patched. NTFS Stream Self-Deletion NTFS (New Technology File System) streams are lesser-known features of the Windows file system that allow for alternative data streams (ADS) to be associated with a file. These streams are not immediately visible in standard file listings, making them useful for various purposes—both legitimate and malicious. One interesting and practical use of NTFS streams is in deleting locked executables or files currently in use by the operating system. This technique can be particularly useful in scenarios where a file cannot be deleted through conventional means because it is locked by a running process. The method, initially discovered by Jonas Lykkegaard, has been demonstrated through a proof of concept (POC) that showcases how NTFS streams can be manipulated to bypass such restrictions. LdrRegisterDllNotification Commonly, anti-virus solutions leverage a notification mechanism provided by Microsoft Windows to detect when a Dynamic Link Library (DLL) is loaded into a process. This functionality can effectively identify foreign DLLs that may be malicious, depending on the detection logic implemented by the anti-virus software. The notification is registered using the LdrRegisterDllNotification function, which allows a program to specify a callback function that will be invoked whenever a DLL is loaded or unloaded. The developer must provide a callback function that adheres to the following prototype: Figure 2 - Unregistering DLL notifications This callback function is notified whenever a DLL is loaded or unloaded, with the NotificationReason parameter indicating the action. This method is widely used by security tools to monitor DLLs within a process, adding a layer of protection by identifying potentially malicious libraries as soon as they are loaded. While LdrUnregisterDllNotification is provided to remove the notification, it requires the cookie that was originally provided during registration. However, an attacker could potentially retrieve or nullify the notification list manually by manipulating the LdrpDllNotificationList. This list is a linked list (LIST_ENTRY) containing all registered notifications, and an attacker could walk through the list to remove entries as shown in the code snippet: Figure 3 - Setting list nodes to NULL This code effectively empties the LdrpDllNotificationList, thereby removing all registered DLL notifications. By modifying the Blink and Flink pointers of each entry, an attacker can bypass DLL loading notifications, leaving subscribed consumers blind to new DLLs being loaded. Conclusion The evolving landscape of malware and command-and-control (C2) frameworks continues to present significant challenges to modern security solutions. By leveraging obscure and poorly documented (or, completely undocumented) low-level system features and bypassing traditional API and security mechanisms, attackers can create stealthy operations that are harder to detect and analyse. The findings in this analysis reflect the need for continuous adaptation in defensive strategies, particularly in expanding visibility into low-level system activities that have traditionally been overlooked or deprioritised. Security solutions must evolve to better monitor and defend against these techniques, ensuring that adversaries can no longer rely on obscure system behaviors to mask their malicious activity to achieve their operational objectives. For assistance analysing the latest malware or binary executables, join our community of researchers and security engineers using the AI Binary Analysis Platform today.

Introduction In this post, we explore a vulnerability in the Windows IOMap64.sys driver (CVE-2024-41498) RevEng.AI researchers discovered with the help of our AI Binary Analysis Platform. We perform a technical analysis of the IOMap64.sys driver, cover the software fault leading to the vulnerability which under the hood allow a malicious user to read / write the entire physical memory (RAM), and finally provide a PoC to demonstrate exploitability. Alongside the core analysis, RevEng.AI has provided a YARA rule and Indicators of Compromise (IOCs) at the end of the post to aid detection. Background It is known that threat-actors modus operandi has evolved over time, in response to the increase in the complexity of defensive technologies. One of those challenges is called "vulnerable drivers"; drivers are pieces of software that run deep in the kernel (ring-0). They usually provide an interface between the OS and the hardware itself. They are of particular interest to bad actors due to their use in Bring Your Own Vulnerable Driver (BYOVD) exploit chains which provide a mechanism to gain kernel privileges. As we are talking about low-level code, programmers must be extremely careful; even the slightest error could lead to a Blue Screen Of Death (BSOD) in the best case, and a complete system compromise in the worse. For more information on the impact errors in Windows kernel drivers can have, please contact CrowdStrike. Obviously, not all drivers can be trivially loaded by Windows. In fact, since the 64-bit version of Windows 10 Driver Signature Enforcement (DSE) feature was enabled by default, only drivers from official vendors signed with a valid certificate can be loaded; this means that it's "no longer possible" to compile a malicious driver, without a valid signature, and load it into the kernel. That being said, Threat Actors started to use known signed drivers with known vulnerabilities (such as the infamous CAPCOM driver). To prevent such abuse, Windows has started to maintain a list of vulnerable drivers which it prevents from loading (if enabled); this setting on Windows devices can be found under the Core Isolation settings. Technical Analysis of IOMap64.sys In May 2024, RevEng.AI analysed a series of third-party Microsoft Windows kernel drivers. A notable candidate for analysis was ASUS's IOMap64.sys (SHA-256: d78d7516dbb8cad08f355a070790d6dd629dcf58d816855b958669fecb8b68b5). The driver is signed by ASUS, and holds a valid signature from ASUSTeK COMPUTER INC. (serial: 04 14 DC F7 AC 18 BE 7B 0E 5D 1D B9 A3 FE E4 69). The build time of the driver is likely 2023-11-24 10:25:39 UTC - making it fairly recent. There are many different types of drivers that can handle a wide array of low-level operations. The image in Figure 1, provided by Microsoft, splits them into three different macro categories. In our case, IOMap64.sys is a driver used by ASUS software to manage device hardware; this is quite interesting as the driver must be able to interface with the hardware directly. Thus, some interesting primitives could exist (from a threat actor perspective). Once loaded in Hex-Rays IDA Pro, we are welcomed with the DriverEntry function, the standard entry-point of Windows kernel drivers. In our case, all of the relevant logic stuff happens inside RealDriverEntry (some code was removed for brevity). As we can see from the code above, there is a large amount of logic occurring. The first thing we notice is the string \Device\IOMap. This is the DOS device path associated with the driver. To trigger I/O request packets (IRPs) and communicate with the driver, we must first open a HANDLE to that device. Another noteworthy characteristic of the driver is the device extension size 0x98. The device extension is one of the most important data structures associated with a device object. Its internal structure is driver-defined, and it is typically used to: Maintain device state information; Provide storage for any kernel-defined objects or other system resources, such as spin locks, used by the driver; and, Hold any data the driver must have resident and in system space to carry out its I/O operations This information is helpful from a reverse-engineering perspective as in some cases this structure holds buffers associated with the various IRPs, thus making it easier to understand and recover the various internal structures. Another thing we notice is that the driver is setting the various callbacks for MajorFunction; this is effectively an array wherein each index represents a different operation on the device file (Open, Close, DeviceIoControl, and so on). For more information on this topic, refer to Microsoft's official documentation. We are interested in IRP_MJ_DEVICE_CONTROL events. These events define how the driver should behave upon a DeviceIoControl IRP. After loading the target function associated with a IRP_MJ_DEVICE_CONTROL event, renamed Possible_DispatchDeviceControl_0, a lot of logic can be observed. This code first checks the parameters associated with the IRP (buffer, input and output buffer length, and the MajorFunction). It was observed from further analysis this subroutine is used by all the I/O operations, which is why many checks are performed. DeviceIoControl requests are typically identified by a number, which are defined by the developer. Think about an Remote Procedure Call (RPC); but instead of a function name, you must pass the correct IOCTL (number) to call the associated function. In our case, the large switch in Figure 2 handles the dispatch to the correct function handler. Upon analysis of the dispatch routines, RevEng.AI observed a series of interesting functions. For readability, two of the functions are renamed ProcessIrpAndMapMemory and ProcessIrpAndMapMemory2 . Both of these functions use the memory-management (Mm) API MmMapIoSpace to map a physical memory address to a virtual address. Although we won't cover how such mappings work under the hood in the scope of this blog-post, Microsoft provide a thorough explanation here. The two functions mentioned above work almost the same way. First, they check if the device extension has initialized two global pointers and, if so, unmap the memory pointed to by those pointers. Then, given the user buffer as input, they perform a few manipulations and map an address inside our buffer. Now, we don't care much about HalTranslateBusAddress as this is typically used to translate a bus address, but the case of the translation failing, it jumps into MmMapIoSpace. Once the translation succeeds, the resulting VA is stored inside the a1 parameter - the global device extension pointer - and copied into globalBaseAddress (Figure 4). Further analysis of the code revealed further IOCTLs that, without checking what address was actually mapped inside translatedPhysicalAddress (Figure 4) allows to read a BYTE/WORD/DWORD directly dereferencing a user-provided offset. The two functions together create a powerful primitive that allows reading the entire physical memory except from the lowest address (0x0 → 0x1000). Even PPL processes can be accessed this way without having to deal with PPL intricacies. An important note is that the two functions using MmMapIoSpace have a fixed mapping size, one of 0x40000 and the other 0x100000. The two pointers are both copied to the device extension. Functions other than ReadByteAtVirtualAddr (Figure 4) exist that write to these virtual addresses given an arbitrary offset, thus transforming the read primitive into a read/write primitive, which could be used to further elevate privileges, but for some reason, the MmMapIoSpace call keeps failing during our test when the size parameter is set to 0x100000. Dynamic Analysis - A Walkthrough To debug the kernel driver we first create a guest Windows 11 VM to load the driver, and connect IDA Pro from our host to the guest debugger using the WinDbg connector. After setting up our environment, the kernel eventually initialises and we hit a break-point during kernel startup. Next, we hit run to let Windows boot and suspend when the system is fully loaded. If you did everything correctly on the modules list, you should be able to see our driver mapped in memory: Now, we upload the driver for analysis to RevEng.AI and use the RevEng.AI IDA Pro plugin to find common symbols between the IOMap64.sys driver and the memory map of the loaded driver. This will generate a list of symbols for our target driver, which will allow use to match the same functions when loaded in memory. And we are ready to go! Now, you can experiment with your driver and possibly create a PoC. PoC or It Never Happened We started developing a simple PoC to open the device and call the vulnerable IOCTL to map physical memory, but it wasn't working as expected; in fact, we had a BSOD almost immediately. Upon further inspecting the parameters passed to MmIoMapSpace, everything seemed correct, so we checked the call trace. We found out that the process was crashing inside MiShowBadMapper due to a call to KeBugCheckEx. After countless hours trying to understand what was happening, we noticed that the bug was happening only when we were debugging the kernel! So we opted to bypass the check inside MiShowBadMapper by patching the assembly itself As we can see from the diff above, we remove the entire if statement which checks KdPitchDebugger and KdDebuggerNotPresent. This will prevent the kernel from calling the KeBugCheckEx function, thus making the MmMapIoSpace call work. You can find the code for the PoC attached below. Expexp.c3 KB.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}download-circle Conclusion We hope you enjoyed this reading as much as we enjoyed writing it. It is left to the reader to investigate why the mapping with a size of 0x100000 was failing and possibly write a PoC to steal the token from an elevated process and copy it to your own one. Remember that when it comes to complex drivers, you may waste days trying to reverse it and months trying to exploit it; patience is the key to success! To speed up the process, you can use our AI Binary Analysis Platform to automate the identification and understanding of binary code when performing any kind of vulnerability research or binary analysis. Indicators of Compromise IOMap64ASUS-signed IOMap64 Vulnerable DriverIOMap64.sys53 KB.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}download-circle YARA Rule

Executive Summary RevEng.AI observed a Latrodectus sample (a.k.a. Unidentified 111, Lotus, BLACKWIDOW, IceNova) delivery chain using a malicious JavaScript (JS) stager uploaded to a third-party public malware scanning service on 23 June 2024. Since early January until the present, RevEng.AI has observed versions 1.1 to 1.3 in operational use by the adversary (although earlier versions do exist, as documented by industry reporting [1]). Latrodectus is a loader typically delivered by phishing emails containing a shortened link to a likely compromised captcha-gated WordPress website, masquerading as legitimate services such as Cloudflare. The use of a captcha-gated delivery website is likely to circumvent automated payload retrieval. The user is prompted to download and execute an MSI (Microsoft Windows Installer), and in some cases, the delivery chain also contains a PDF to socially engineer the user. The malware borrows code heavily from the open-source BlackLotus malware [2], which contains a ready-to-use bot and command-and-control (C2) component. Latrodectus was one of the many malware families targeted by Operation Endgame, an international law enforcement effort against malware loader infrastructure [5]. Since the operation, Latrodectus has quickly rebuilt its infrastructure and returned to its standard mode of operation. This blog post covers the most recent distribution chain, noteably the use of Brute Ratel, and the anti-analysis methods employed by the malware. Latrodectus Delivery Chain 1. JavaScript - Download & Execute MSI The JavaScript-based stager (SHA-256: 3ac8decd825d1ed7a86ed86d7789b44c0f0c467d4f482ab0863df1b7b1e3e8cc), named Form_Ver-11-58-52.js, for this distribution campaign is configured with the msiPath of http[:]//85.208.108[.]63/BST.msi. The JS file is consistent with previous campaigns used to download and execute Latrodectus MSIs. The Installer.InstallProduct API is used [3], which natively handles the download and execution of local and remote MSIs (HTTP, UNC path, etc.). 2. MSI Package The MSI creates a child process to decompresses the contents of a bundled CAB archive (SHA-256: c89d15789fd9e0b23e62bbf038d2ddbcea5618573517f3382790b4b0434933df) named disk1.cab to %APPDATA%. The CAB file contains the malicious DLL file aclui.dll (SHA-256: c5dc5fd676ab5b877bc86f88485c29d9f74933f8e98a33bddc29f0f3acc5a5b9), which is a packed Brute Ratel C4 (BRC4) badger implant. The DLL export edit is then executed using rundll32.exe by the MSI installer, i.e: rundll32.exe C:\Users\Ivan\AppData\aclui.dll,edit. Although Brute Ratel is a legitimate red-teaming tool, it has been previously used by threat actors [4]. 3. BRC4 Badger After BRC4 unpacks itself (SHA-256: 0d3fd08d237f2f22574edf6baf572fa3b15281170f4d14f98433ddeda9f1c5b2) and self-injects, a long sleep is likely used to avoid immediate execution by automated sandbox solutions. The C2 configuration for the BRC4 badger can be observed below in Table 1. Table 1 - Badger Configuration The configuration hosts, at the time of writing, resolve to 94.232.249[.]86. 4. Latrodectus 1.3 via BRC4 The BRC4 badger is configured to execute a Latrodectus payload upon a victim connecting. The Latrodectus core bot component (dbd85d5dd501bb7fad3990f0801d32da438a5bc60bd7cf6999d5bc535291146c) is injected into the common target explorer.exe. The full configuration of this Latrodectus sample is avaliable in the Latrodectus Configuration section. The stealer module is also downloaded and injected into the current process, explorer.exe, (SHA-256: 44ccc3fbd3e15e8bdb063616d9baa37b1f9ab9121759fd467c943b7611860f72), targeting Microsoft Edge, Microsoft Internet Explorer, Microsoft Outlook, Firefox, Google Chrome, 360 Browser, Yandex Browser, and more. RevEng.AI AI binary code similarity engine was able to quickly recover SQLite debug symbols within the stealer module and aid in the reverse-engineering process: Figure 1 - SQLite Symbol Recovery Anti-Analysis Techniques Latrodectus leverages several anti-analysis methods to hinder reverse-engineering efforts and detection by security solutions. String Obfuscation The malware makes heavy use of string obfuscation to hide artifacts from analysts. Latrodectus 1.1 utilized a pseudorandom number generator (PRNG), using a hardcoded seed, to derive the XOR key for deobfuscation. Obfuscated strings within Latrodectus 1.2 and 1.3 are stored using a relatively simple structure and deobfuscated when required. The strings are stored using the following binary format in Figure 2. Figure 2 - Obfuscated string binary format The obfuscation algorithm is simple, and makes use of XOR and ADD operations. A reimplemented version in Python of the deobfuscation algorithm can be observed below in Figure 3. It is noteworthy that the actual size of the obfuscated string is derived from the XOR operation of the wSeed and dwKey fields. Figure 3 - String deobfuscation routine reimplementation Dynamic API Resolution via Windows API Hashing Latrodectus uses CRC32 to resolve Windows APIs at runtime by walking the InMemoryOrderModuleList found within the PEB to first retrieve the addresses of loaded ntdll.dll (0x26797E77) and kernel32.dll (0x2ECA438C) modules. The structure and control-flow used by Latrodectus is reminiscent of the BlackLotus' API resolution routine, which is avaliable in open-source. Figure 4 - API Table Entry The API table is built sequentially during the initialisation phase of the malware. The building of the API table in C-pseudocode can be seen below in Figure 5. Figure 5 - Building of Windows API function table based on CRC32 hash Anti-Debug Latrodectus makes use of the well-known and documented method of checking if the IsDebugged flag is set within the processes' PEB [6]. This is a common and effective method regularly employed to evade debuggers. A reimplementation in C-pseudocode can be observed in Figure 6. This is also likely borrowed code from the BlackLotus open-source project. [2] Figure 6 - PEB based anti-debugging measure employed by Latrodectus Host Environment Process Count Checks The host environment checks within Latrodectus includes checking the number of processes which are running, with different thresholds per Windows NT version. It is likely these checks are used to evade sandbox, emulation-based approaches, or other analysis environments in which the number of processes would be irregular. If the number of processes and version constraint matches, as defined in Table 2, the malware will simply exit. This anti-analysis technique is not unique to Latrodectus, and has been used by the likes of the EvilBunny implant. Table 2 - Process check constraints per version NTFS Visibility Obscured via Alternate Data Stream (ADS) Latrodectus makes use of a trick to delete itself while the process is still running, making use of an alternative data stream (ADS) and a specific chain of API calls. The following sequence of events occurs to achieve this: The path to the current running process is gathered using GetModuleFileName. A HANDLE to the file is then acquired via CreateFile with DELETE access. Call SetFileInformationByHandle with the FileRenameInfo class FileName member set to :wtfbbq. Again, call SetFileInformationByHandle, however with the FileDispositionInfo class member DeleteFile set to TRUE Close the HANDLE to trigger the DeleteFile The HANDLE is duplicated, then renamed to an ADS - in this instance, :wtfbbq. This code has likely been borrowed from an open-source project [7] and slightly modified to use Latrodectus' API resolution and string obfuscation routine. The ADS name (:wtfbbq) remains unchanged. This is also used by RaspberryRobin, HelloXD Ransomware, DarkPower Ransomware and implemented in the Offensive Nim project. Conclusion Latrodectus is a now-prominent malware loader that leverages a variety of anti-analysis techniques to avoid detection and thwart reverse engineering efforts. Its reliance on open-source projects like BlackLotus, combined with the ability to quickly adapt and incorporate such code, demonstrates the adversary's commitment to leveraging publicly available resources to enhance their malware capabilities. The adversary behind Latrodectus has operational resilience in the face of takedowns, demonstrating the adversary's commitment to maintaining a robust malware delivery infrastructure. RevEng.AI Platform RevEng.AI cuts down on the reverse-engineering time in the analysis stage by using our state of the art binary AI model. Using our AI Binary Analysis platform, analysts were quickly able to identify the differences between each Latrodectus sample based at a function-level and binary-level to provide an overview of differences implemented by the malware developer version-to-version. Alongside this, the overlaps with BlackLotus. Figure 7 - RevEng.AI-identified Latrodectus and BlackLotus similarity analysis RevEng.AI allows analysts to quickly and easily cluster malware samples based on the code similarity observed between binaries. IOCs (Indicators of Compromise) Host IOCs Table 3 - Host IOCs Network IOCs Table 4 - Network IOCs Latrodectus Configuration Table 5 - Latrodectus Configuration Footnotes [1] - https://medium.com/walmartglobaltech/icedid-gets-loaded-af073b7b6d39, https://www.proofpoint.com/uk/blog/threat-insight/latrodectus-spider-bytes-ice [2] - https://github.com/ldpreload/BlackLotus https://decoded.avast.io/janvojtesek/raspberry-robins-roshtyak-a-little-lesson-in-trickery/ [3] - https://learn.microsoft.com/en-us/windows/win32/msi/installer-installproduct [4] - https://news.sophos.com/en-us/2023/05/18/the-phantom-menace-brute-ratel-remains-rare-and-targeted/ [5] - https://www.operation-endgame.com/ [6] - https://github.com/ldpreload/BlackLotus/blob/main/src/Bot/antidebug.c [7] - https://github.com/LloydLabs/delete-self-poc/tags

Scenario Let's say we have a binary that we have either reverse engineered or have the associated debugging information. We will refer to this executable as binary S. We also have another executable called P that we know is similar to binary S in some way but it is stripped and has no debugging information. Binary P could have a similar source code to binary S or could be made from the same statically linked library. However, S and P are sufficiently different that they cannot be diffed with BinDiff. We want to use information from binary S to transpose symbols from S to P. This blog post will explain how the AI we are developing at RevEng.AI does exactly that. We will look at one technique that explores our program comprehension embeddings API to locate symbols in P given a single known anchor point in it. Setting the scene As a running example, S will be the binary miredo from the Debian Sid package miredo/amd64. Miredo is a Teredo client (RFC 4380) that tunnels IPv6 packets in IPv4 UDP packets. We don't have access to the source code but we can access symbol information via the miredo-dbgsym/amd64 package. It is important to note that RFC 4380 uses a combination of MD5 and HMAC in its protocol design. You can download binary S below. miredoThe miredo binary from the miredo/amd64 Debian Sid package. With debug_info.miredo162 KB.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}download-circle As for executable P, we will be using the binary fdupes from the Debian Sid package fdupes/amd64. FDupes is a program that identifies duplicate files within directories using MD5 hashes and byte by byte comparisons. You can download binary P below. fdupesFDupes binary from the fdupes/amd64 Debian Sid package.fdupes27 KB.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}download-circle Our intuition informs us that, given both binaries contain code for processing MD5, can we port MD5 related symbols from S to P? An inspection of our source binary S reveals the following symbols: A quick internet search for "md5" and "fdupes" reveals that FDupes has the same md5_process, md5_finish, md5_append, and md5_init functions in its source code (Github search). md5_init is a small 42 byte function and should be easy to identify in Ghidra. For the rest of this blog post we are going to assume that we know the location of a single symbol in P - md5_init. This will be our known anchor point in P. We are going to use this anchor point with the relative difference between symbols' embedded representations from S to locate another symbol in P. Before we explain exactly what that means or why we would want to do that, take a look at md5.c and you will see that md5_process is not a direct caller or callee of md5_init and therefore cannot easily be identified by simple static rules. We will now explain how to locate md5_process in the stripped binary P using md5_init and the relative difference between md5_process and md5_init in our source executable S. Machine Learning Background RevEng.AI's BinNet model uses a deep multi-output transformer architecture to capture the semantic meaning of binary code. It represents binary code (and all strings, XREFS, constants, etc.) as real-valued vectors that can be used for downstream ML tasks. To understand this properly you need to have a basic understanding of Word2Vec and the concept of word representations. We recommend pausing here and reading the following blog post if you are unfamiliar with modern NLP techniques. The famous example from Word2Vec allowed for the vector representations of the words king - man + woman ≈ queen. This proved that the vector space generated by Word2Vec captured semantic information beyond what was previously possible by Neural Network Language Models (NNLMs). You might be able to guess where this is going... Recovering Symbol Information using Embedded Symbol Representations We are going to use RevEng.AI's BinNet model and embeddings API to locate md5_process in P by calculating the difference between the embedded representations of md5_process - md5_init in S and adding the representation of md5_init in P. We will use the following Python code: Our query vector is a real-valued numpy vector of 32 bit floats: We now need to find the closest symbol in P to our query vector. We do this using the cosine similarity against all functions that Ghidra found in P. The closest BinNet embeddings in P to our query can then be accessed by looking up the row corresponding to the closest index: And now we can find the matched symbols virtual address: The symbol md5_process should have a virtual address of 14068 (0x36f4) in our binary P. If we now download symbol information from the fdupes-dbgsym/amd64 Debian package and merge the ELF files we indeed find this is correct. How difficult was this? OK, BinDiff doesn't work but can't we just hash a function's disassembly code and find matches between the two functions? Can we use IDA FLIRT? Let's download the debugging package and inspect P with symbols. The first thing to note is that md5_process is 2113 bytes in S and 2067 bytes in P. Matching hashes clearly won't work. Is the disassembly similar? Let's take a look with Rizin: fdupes miredo The disassembly is significantly different and would not match with hashing or semantic hashing. IDA FLIRT works by matching the first 32 bytes of each function and would fail to find the match. Discussion Why would we want to do this? RevEng.AI's BinNet embeddings aren't just useful for matching symbol names. They inherently capture the semantic meaning of binary code and could be used for: Porting symbols between different malware samples or OS versions Identifying known vulnerabilities in closed source software Detecting malware embedded inside proprietary software Aiding reverse engineering of never seen before programs Speeding up fuzzing by directing path selection to heap allocation functions or other defined behaviour There are far more efficient ways to use RevEng.AI's API to identify symbols in stripped binaries but this has been a fun example. If you are interested in using our API please contact us via the contact page at https://reveng.ai. No source code was analysed by our AI in the development of this example.
We use essential cookies for security and site functionality, plus optional tracking cookies to understand how you use our site and improve it. Privacy policy.