DotRunpeX – demystifying new virtualized .NET injector used in the wild

March 15, 2023
Research by: Jiri  Vinopal.


  • Check Point Research (CPR) provides an in-depth analysis of the dotRunpeX injector and its relation to the older version
  • DotRunpeX is protected by virtualization (a customized version of KoiVM) and obfuscation (ConfuserEx) – both were defeated
  • Investigation shows that dotRunpeX is used in the wild to deliver numerous known malware families
  • Commonly distributed via phishing emails as malicious attachments and websites masquerading as regular program utilities
  • We confirmed and detailed the malicious use of a vulnerable process explorer driver to disable the functionality of Anti-Malware services
  • CPR introduces several PoC techniques that were approved to be effective for reverse engineering protected or virtualized dotnet code


During the past few months, we have been monitoring the dotRunpeX malware, its usage in the wild, and infection vectors related to dozens of campaigns. The monitoring showed that this new dotnet injector is still evolving and in high development. We uncovered several different methods of distribution where in all cases, the dotRunpeX was a part of the second-stage infection. This new threat is used to deliver numerous different malware families, primarily related to stealers, RATs, loaders, and downloaders.

The oldest sample related to the new version of dotRunpeX is dated 2022-10-17. The first public information about this threat is dated 2022-10-26.

The main subject of this research is an in-depth analysis of both versions of the dotRunpeX injector, focusing on interesting techniques, similarities between them, and an introduction to the PoC technique used to analyze a new version of dotRunpeX as it is being delivered virtualized by a customized version of KoiVM .NET protector.

Background & Key Findings

DotRunpeX is a new injector written in .NET using the Process Hollowing technique and used to infect systems with a variety of known malware families. Although this injector is new, there are some connections to its older version sharing some similarities. The name of this injector is based on its version information which is the same for both dotRunpeX versions, consistent across all samples we analyzed and containing ProductName – RunpeX.Stub.Framework.

While we have been monitoring this threat, we spotted a few publicly shared pieces of information, mainly by independent researchers, that were related to the functionality of dotRunpeX but misattributed to a different well-known malware family.

We are aware of a publication about one campaign delivering this threat, but our findings and conclusions based on the report below slightly differ. By monitoring this threat for a few months, we got enough information to differentiate the first-stage loaders from the second stage (dotRunpeX) with no signs of the relation between them. We revealed the connections to its older version, the distribution of numerous malware families, and several different techniques used as a vector of infection.

Among the variety of downloaders and cryptocurrency stealers, we spotted these known malware families delivered by dotRunpeX:

Figure 1: Malware Families Delivered by DotRunpeX

From the timeline perspective, based on the compilation timestamps of dotRunpeX samples that did not appear to be altered, this new threat became popular mainly during November 2022 and January 2023. What could be just an interesting coincidence or just some kind of sign of attackers waiting under the Christmas tree is that we did not see a lot of samples compiled during December 2022.

Figure 2: DotRunpeX Timeline – Compilation Timestamps

Vector of infection

DotRunpeX injector commonly comes as a second stage of the original infection. The typical first stages are very different variants of .NET loaders/downloaders. The first-stage loaders are primarily being delivered via phishing emails as malicious attachments (usually as a part of “.iso”, “.img”, “.zip”, and “.7z”) or via websites masquerading as regular program utilities. Apart from the most common infection vectors, the customers of dotRunpeX are not ashamed to abuse Google Ads or even target other potential attackers via trojanized malware builders.

Example phishing email Transaction Advice 502833272391_RPY - 29/10/2022 delivering the first stage loader as a part of malicious “.7z” attachment that results in loading of dotRunpeX (SHA256: “457cfd6222266941360fdbe36742486ee12419c95f1d7d350243e795de28200e”).

Figure 3: Phishing email “Transaction Advice 502833272391_RPY – 29/10/2022”

Example phishing websites – masquerading regular program utilities (Galaxy Swapper, OBS Studio, Onion Browser, Brave Wallet, LastPass, AnyDesk, MSI Afterburner) and delivering the first stage loaders that result in dotRunpeX infection in a part of the second stage.

Website masquerading as Galaxy Swapper: https://www.galaxyswapper[.]ru/

Figure 4: Google search for the utility Galaxy Swapper leads to “https://www.galaxyswapper[.]ru/”

Download redirects to https://gitlab[.]com/forhost1232/galaxyv19.11.14/-/raw/main/

Figure 5: Download button on “https://www.galaxyswapper[.]ru/” redirects to a trojanized program

Website masquerading as LastPass Password Manager: http://lastpass[.]shop/en/

Figure 6: Website “http://lastpass[.]shop/en/” masquerading as LastPass Password Manager

The fake website of LastPass Password Manager was already down at the time of the investigation. Still, we can confirm that the fake software was downloaded from the “Final URL” https://gitlab[.]com/forhost1232/lastpassinstaller/-/raw/main/

Figure 7: Download button on “http://lastpass[.]shop/en/” redirects to a trojanized program

The GitLab page https://gitlab[.]com/forhost1232 contained dozens of programs trojanized by dotRunpeX malware.

Figure 8: Dozens of trojanized programs on GitLab repository “https://gitlab[.]com/forhost1232”

All of the trojanized programs on the previously mentioned GitLab page contain the main .NET application enlarged with an overlay to avoid scanning with sandboxes very likely.

Figure 9: Examples of trojanized programs served by the GitLab repository “https://gitlab[.]com/forhost1232”

The mentioned .NET applications with overlay are the typical first stages, behaving as dotnet loaders with simple obfuscation. These different variants of loaders use reflection to load the dotRunpeX injector in the second stage. Some of them are very simple, and some are more advanced.

Simple first-stage loader (direct usage of method System.Reflection.Assembly.Load()):

Figure 10: Simple first-stage loader

An example of a more advanced first-stage loader (using AMSI Bypass and DynamicMethod to load and execute the second stage via reflection) can be seen below. The advantage of this kind of advanced loader is that there is no direct reference to System.Reflection.Assembly.Load() method so it could possibly avoid detection of engines relying on static parsing of .NET metadata.

Figure 11: More advanced first-stage loader using AMSI bypass and DynamicMethod

Deobfuscated form of the latter one could be seen in the picture below:

Figure 12: A deobfuscated form of a more advanced first-stage loader

Programmatic way of second-stage extraction (dotRunpeX stage) from these kinds of loaders could be simply implemented using AsmResolver and reflection as shown below.

Figure 13: Extraction of dotRunpeX from first-stage loader using AsmResolver and reflection

Important to note that those examples of phishing websites leading to the GitLab page were related to just one campaign where the dotRunpeX injector was always responsible for injecting Redline malware with C2 –

In addition to the most common vectors of infection mentioned earlier, we observed quite an interesting case of infection vector, where a customer of dotRunpeX was probably bored enough to target ordinary victims and decided to target other potential attackers. Something that is supposed to be a Redline builder Redline_20_2_crack.rar (SHA256: “0e40e504c05c30a7987785996e2542c332100ae7ecf9f67ebe3c24ad2468527c”) was trojanized with a downloader that uses a reflection to load dotRunpeX as a hidden “added feature” of the builder.

Figure 14: Folder structure of trojanized Redline builder

It turned out that during the building process of the Redline, configured to your needs, one will also get another Redline sample, probably the one that you didn’t desire, as a gift embedded in the dotRunpeX.

Figure 15: Downloader that uses a reflection to load dotRunpeX delivering another Redline malware

Technical Analysis: Highlights

The old version of dotRunpeX:

  • Using custom obfuscation – only obfuscations of names
  • Configurable but limited (target for payload injection, elevation + UAC Bypass, XOR key for payload decryption)
  • Only one UAC Bypass technique
  • Using simple XOR to decrypt the main payload to be injected
  • Using D/Invoke similar technique to call native code (based on using GetDelegateForFunctionPointer()) – but using decoy syscall routine
  • Using D/Invoke for remapping of “ntdll.dll

The new version of dotRunpeX:

  • Protected by a customized version of the KoiVM virtualizer
  • Highly configurable (disabling Anti-Malware services, Anti-VM, Anti-Sandbox, persistence settings, key for payload decryption, UAC bypass methods)
  • More UAC Bypass techniques
  • Using simple XOR to decrypt the main payload to be injected (omitted in the latest developed versions)
  • Abusing procexp driver (Sysinternals) to kill protected processes (Anti-Malware services)
  • Signs of being Russian based – procexp driver name Иисус.sys translated as “jesus.sys

Similarities between both versions:

  • 64-bit executable files “.exe” written in .NET
  • Used to inject several different malware families
  • Using simple XOR to decrypt the main payload to be injected
  • Possible usage of the same UAC bypass technique (the new version of dotRunpeX has more techniques available)
Figure 16: UAC bypass technique
  • Using the same version information
Figure 17: DotRunpeX version information
  • Using the same .NET resource name BIDEN_HARRIS_PERFECT_ASSHOLE to hold the encrypted payload to be injected
Figure 18: Dotnet resource name of new version vs. old version
  • Using the same code injection technique – Process Hollowing
  • Using the same structured class for definitions of Native delegates
Figure 19: The same structured class for definitions of Native delegates

Full technical analysis – old version of dotRunpeX

For the analysis of the older version of dotRunpeX, sample SHA256: “65cac67ed2a084beff373d6aba6f914b8cba0caceda254a857def1df12f5154b” was used. This sample is a 64-bit executable file “.exe” written in .NET, implementing custom obfuscation – only obfuscations of names. The version information is consistent across all samples we analyzed, and we can notice the ProductName – RunpeX.Stub.Framework that could be some kind of first hint that we are dealing with a dotnet injector.

Figure 20: Consistent version information of the old dotRunpeX version

For simplicity, we partly deobfuscated the names of methods, their arguments, and local variables. Right in the Main() method, we can see simple XOR decryption of the resource BIDEN_HARRIS_PERFECT_ASSHOLE that contains an encrypted payload to be injected. The resource name was consistent across all samples we analyzed.

Figure 21: The main method leads to simple XOR decryption of the embedded payload

We can also see the namespace UACBypass with the class name UAC. This class implements UAC (User Account Control) bypass method, but it is not configured to use in this sample.

Figure 22: UAC bypass method

Method Inject() is implementing a code injection technique called “Process Hollowing”. We can notice spawning a process in a suspended state right in the picture below.

Figure 23: Creation of suspended process as a part of the Process Hollowing technique

This technique is nothing new in the world of malware development. Still, there is something interesting we can immediately spot once we check P/Invoke (technology that allows access to structs, callbacks, and functions in unmanaged libraries from managed code) defined methods of this sample. These methods can be seen in the ImplMap table, which is a part of .NET metadata.

Figure 24: The ImplMap table – the old version of the dotRunpeX

Certain WIN APIs or NT APIs must be used to perform the Process Hollowing technique. And as we saw in the ImplMap table, some of the most crucial APIs are missing. To be more specific, we cannot see any APIs related to unmapping and writing to remote process memory. The reason behind this is the usage of the D/Invoke framework to call certain NT API routines that could usually trigger attention.

D/Invoke contains powerful primitives that may be combined intelligently to dynamically invoke unmanaged code from disk or memory with careful precision. It relies on the usage of the dotnet method GetDelegateForFunctionPointer() and corresponding delegates definitions.

In this case, NT APIs ZwOpenSection, ZwMapViewOfSection, ZwUnmapViewOfSection, NtClose, NtWriteVirtualMemory, NtResumeThread, and RtlMoveMemory are implemented via D/Invoke. The corresponding definitions of delegates can be seen below.

Figure 25: The class for definitions of Native delegates

What is even more interesting, 4 NT APIs (ZwUnmapViewOfSectionNtWriteVirtualMemoryNtResumeThreadRtlMoveMemory) implemented via D/Invoke are using something that could be considered as an added PoC technique and is not a part of the original D/Invoke framework – syscall patching. For example, we can check how NtWriteVirtualMemory invocations are implemented via a method called CallNtWriteVirtualMemory().

Figure 26: Example of D/Invoke implementation that leads to syscall patching

First, what we can see is an altered usage of the D/Invoke framework in the method MapDllandGetProcAddress(). Each time this method is invoked, it will remap the specified library and obtain the desired function’s address. Before returning the address of the desired function, pointer arithmetic is used to move the pointer by 4 bytes so it points to the address of the syscall number. In this case, the “ntdll.dll” module gets remapped, returning the address of the NT API routine NtWriteVirtualMemory altered by 4 bytes offset.

Figure 27: Altered usage of the D/Invoke that returns the address pointing to the syscall number
Figure 28: NtWriteVirtualMemory address altered by 4 bytes offset points to its syscall number

The remapping of the module is used as an AV-evasion and Anti-Debug technique, as it results in unhooking and removing all set software breakpoints. The obtaining address of a certain native function is implemented via typical D/Invoke implementation – DynGetProcAddress(), which is responsible for in-memory parsing of the PE structure to find the address of the specified routine.

Figure 29: Typical in-memory parsing of the PE structure implemented via D/Invoke

Now back to the exciting part. As we can see in this case, DynGetProcAddress() is also used to find the address of NT API NtAddBootEntry, and we can call it a decoy routine. The decoy routine address will be used for syscall patching.

Figure 30: Decoy routine NtAddBootEntry used for syscall patching
  • Getting the address of the NtWriteVirtualMemory routine altered by 4 bytes offset (address of syscall number)
  • Getting the address of the decoy routine NtAddBootEntry
  • Copying 2 bytes from the altered address of NtWriteVirtualMemory (even though the syscall number is DWORD, these 2 bytes are enough and represent the syscall number of NtWriteVirtualMemory) to byte field SyscallStub (this field contains syscall stub code)
  • Patching address of NtAddBootEntry with byte field SyscallStub

Disassembling the default value of the SyscallStub makes it even more apparent why exactly 2 bytes are getting replaced with bytes from the altered address of the NtWriteVirtualMemory routine. These 2 bytes represent the syscall number of certain real function to be called.

Figure 31: Disassembling the default value of the byte field SyscallStub

Simply said, once the NtWriteVirtualMemory function is called, the only thing we will see from user mode will be an invocation of NtAddBootEntry.

We can use WinDbg “kernel mode debugging” to verify the mentioned execution flow. We can see that NT API NtAddBootEntry with the original syscall number 0x6a (on our target system) is used as a patched decoy routine. In the case where NtWriteVirtualMemory needs to be called, the syscall number of the decoy routine is patched with syscall number 0x3a (NtWriteVirtualMemory syscall number on our target system) and gets called.

Figure 32: WinDbg “kernel mode debugging” shows the execution flow caused by syscall patching

Full technical analysis – new version of dotRunpeX

For the analysis of the new version of dotRunpeX, sample SHA256: “44a11146173db0663a23787bffbb120f3955bc33e60e73ecc798953e9b34b2f2” was used. This sample is a 64-bit executable file “.exe” written in .NET, protected by KoiVM. The version information is the same as in the case of an older version of dotRunpeX and is consistent across all samples we analyzed. We can notice the ProductName – RunpeX.Stub.Framework again.

Figure 33: Consistent version information of the new dotRunpeX version

Right after opening the sample in dnSpyEx and leading to the entrypoint function – _sb() method, we can immediately confirm that this new version of dotRunpeX is protected by the KoiVM virtualizer. Despite the fact that most of the IL code is virtualized, we can still spot invocation of P/Invoke defined method CreateProcess that is used in a way to create a process in a suspended state – typically used for code injection technique “Process Hollowing”.

Figure 34: Creation of suspended process as a part of the Process Hollowing technique

After investigating more what was left lying around in .NET metadata, specifically in the ImplMap table, to find out what other methods are defined as P/Invoke and very likely used by this sample, we are getting surprisingly even more exciting findings than in the case of the older version of dotRunpeX. Apparently, the sample will perform not just code injection but also loading and communicating with the driver.

Figure 35: The ImplMap table – the new version of the dotRunpeX

The next that we immediately noticed is the usage of the same resource name as in the case of the older version – BIDEN_HARRIS_PERFECT_ASSHOLE – that contains an encrypted payload to be injected. The resource name was consistent across all samples we analyzed. Obviously, the decryption routine is hidden behind the code virtualization, but an educative guess will lead us to a simple XOR decryption routine using a password expressing the secret desires of the author – I_LOVE_HENTAIU2.

Figure 36: Simple XOR decryption of the .NET resource using password “I_LOVE_HENTAIU2”

Unfortunately, as dotRunpeX is still in high development and adding new features, the latest samples utilizing this injector changed the decryption scheme (no more simple XOR) to omit static extraction of embedded payloads.

As we pointed out before, the IL code is protected by the KoiVM virtualizer, so to continue with our analysis, we needed to come up with some approach to deal with the protected code and get something meaningful from that in a reasonable time. First, what came to our mind was to use a publicly available open-source KoiVM de-virtualizer called OldRod. This tool is fully workable for the vanilla version of KoiVM. It is even developed in a way that defeats some simple modifications of the original version of KoiVM (such as signature modifications of the methods in VMEntry class or changes in the default #Koi stream name).

Unfortunately for us, we are dealing with a customized version of KoiVM that modified the protector in a way that is not so simple to defeat. The original implementation of KoiVM defines 119 constant variables that are used to virtualize the code. These constants are used to define registers, flags, opcodes, etc. Assigned values of these constants are used for the proper execution of the virtualized code and are also needed for the de-virtualization process.

Figure 37: The original implementation of KoiVM defines 119 constants

When using the vanilla version of KoiVM, the resulting constants appear in the compiled, protected sample inside the Constants class as fields in the exact same order with ascending values of tokens. The order of constants and their corresponding tokens inside the compiled binary is something OldRod depends on.

Figure 38: The OldRod source code – automatic detection of constants

Although the OldRod tool is an absolute masterpiece and can deal with a custom order of constants when providing a custom constants mapping via configuration file (--config option), finding out the correct mapping of those constants could not be as simple as it sounds. Sometimes when a constant’s order is handmade change, it could be not so hard to map them correctly by analyzing their usage in code. Unfortunately, in the case of dotRunpeX, we can immediately see that values of those constants are affected by runtime arithmetic assignments (no problem to defeat this programmatically), but even worse is that they are scrambled in a very effective way that makes the correct mapping hard enough to consider this approach as not usable for getting some results in a reasonable time.

Figure 39: Runtime arithmetic assignments of scrambled constants

Even though we pointed out several facts about the extreme hardness of devirtualization, with precise code analysis and some hard moments during the constants mapping via their appropriate handlers, we were able to fully devirtualize the code. Despite the fully devirtualized code, we were still left with a non-fully runnable .NET Assembly that was still obfuscated with ConfuserEx obfuscator. To continue our madness, we were able to get rid of this obfuscation too.

To give a little spoiler about the functionality of the dotRunpeX injector and its use of procexp driver, fully devirtualized and deobfuscated code related to driver routines can be seen below.

Driver loading/unloading:

Figure 40: Devirtualized and deobfuscated code responsible for loading/unloading the driver

Communication with procexp device:

Figure 41: Devirtualized and deobfuscated code responsible for communication with procexp device

The process of devirtualization and deobfuscation is a subject to consider for its own blog post and won’t be covered further.

Normally, when it is impossible to devirtualize the code in a reasonable time, we are still left with few other options. The first of the options, quite a common approach when dealing with virtualized code, is to go with dynamic analysis using a debugger, DBI (Dynamic Binary Instrumentation), hooking, and WIN API tracing. As we are dealing with dotnet code, another approach to come out with could be some PoC using some knowledge from the .NET internals world. As researchers who love to bring something new to the community, we decided to combine both of these approaches, which resulted in developing new tools that were approved to be very effective.

To get more information about the code functionality, we started with the dynamic analysis approach using x64dbg. As we pointed out before, the ImplMap table containing P/Invoke-defined methods seems to be a good starting point for setting breakpoints in the debugger. Automating the process of parsing out the P/Invoke defined methods and converting it to x64dbg script leads us to the first tool we developed, called “ImplMap2x64dbg”.


Python script that uses dnfile module to properly parse .NET executable files and their metadata. This tool creates an x64dbg script for setting breakpoints on defined ImplMap (P/Invoke) methods of the .NET executable. This script can be downloaded in the last section of the article.

import dnfile, sys, os

def Main():
    if(len(sys.argv) != 2 or sys.argv[1] == '-h' or sys.argv[1] == '--help'):
        print("Description: Creates x64dbg script for setting breakpoints on defined ImplMap (PInvoke) methods of .NET executable")
        print(f"Usage: {os.path.basename(sys.argv[0])} <filepath>\n")

    file_path = sys.argv[1]
    script_path = file_path + "_x64dbg.txt"
    dn_file = dnfile.dnPE(file_path)

    if( is None or is None):
        print(f"{sys.argv[1]} is NOT a .NET executable !!!\n")
    if( is None):
        print(f".NET executable '{sys.argv[1]}' has NO ImplMap !!!\n")

    # Getting all ImplMap methods and module scope
    implmap_table =
    implmap_modules = []
    implmap_methods = []
    [implmap_modules.append(row.ImportScope.row.Name.lower().replace(".dll", "")) for row in implmap_table if (row.ImportScope.row.Name.lower().replace(".dll", "") not in implmap_modules)]
    [implmap_methods.append(row.ImportName) for row in implmap_table if (row.ImportName not in implmap_methods)]

    # Creation of x64dbg script
    x64dbg_script = "; Replace charset depending APIs - ex. CreateProcess -> CreateProcessA or CreateProcessW !!!\n"
    for module in implmap_modules:
        x64dbg_script += f"loadlib {module}\n"
    for method in implmap_methods:
        x64dbg_script += f"SetBPX {method}\n"
    with open(script_path, "wt",encoding="utf-8") as f_scr:f_scr.write(x64dbg_script)
    print(f"x64dbg script created: '{script_path}'")

if __name__ == '__main__':

Processing our dotRunpeX sample with “ImplMap2x64dbg” will result in the x64dbg script:

; Replace charset depending APIs - ex. CreateProcess -> CreateProcessA or CreateProcessW !!!
loadlib kernel32
loadlib ntdll
loadlib user32
loadlib advapi32
SetBPX VirtualAllocEx
SetBPX CreateProcessA
SetBPX CreateProcessW
SetBPX CreateRemoteThread
SetBPX Wow64SetThreadContext
SetBPX Wow64GetThreadContext
SetBPX NtResumeThread
SetBPX ZwUnmapViewOfSection
SetBPX NtWriteVirtualMemory
SetBPX MessageBoxA
SetBPX MessageBoxW
SetBPX GetModuleHandleA
SetBPX GetModuleHandleW
SetBPX FindWindowA
SetBPX FindWindowW
SetBPX GetProcAddress
SetBPX GetFileAttributesA
SetBPX GetFileAttributesW
SetBPX ShowWindow
SetBPX SetForegroundWindow
SetBPX Wow64DisableWow64FsRedirection
SetBPX Wow64RevertWow64FsRedirection
SetBPX CreateFileA
SetBPX CreateFileW
SetBPX RtlInitUnicodeString
SetBPX NtLoadDriver
SetBPX NtUnloadDriver
SetBPX OpenProcessToken
SetBPX LookupPrivilegeValueA
SetBPX LookupPrivilegeValueW
SetBPX AdjustTokenPrivileges
SetBPX CloseHandle
SetBPX NtQuerySystemInformation
SetBPX DeviceIoControl
SetBPX GetProcessHeap
SetBPX HeapFree
SetBPX HeapAlloc
SetBPX RtlCopyMemory

We focused mainly on certain WIN/NT APIs such as CreateProcessW, NtWriteVirtualMemory,CreateFileA, CreateFileW, NtLoadDriver, NtQuerySystemInformation, and DeviceIoControl as they are the interesting ones related to driver and process injection routines.

The first interesting WIN API call we can see is CreateFileW which is used to create a file in path C:\Users\XXX\AppData\Local\Temp\Иисус.sys.

Figure 42: CreateFileW used to create a file “Иисус.sys”

If we check the created file Иисус.sys (from the Russian language translated as “jesus.sys”), we will immediately find out it is a valid Process Explorer driver, version 16.43.

Figure 43: Created file “Иисус.sys” is a valid Process Explorer driver, version 16.43

We can see routine NtLoadDriver responsible for loading this driver where the argument points to DriverServiceName – \Registry\Machine\System\CurrentControlSet\Services\TaskKill that specifies a path to the driver’s registry key.

Figure 44: NtLoadDriver used to load procexp driver via its associated registry key
Figure 45: Content of the driver’s registry key “\Registry\Machine\System\CurrentControlSet\Services\TaskKill”

Connecting to the process explorer device follows.

Figure 46: Obtaining the handle of the process explorer device

One of the dotRunpeX AV-evasion techniques is killing a hardcoded list of Anti-Malware services with the help of a process explorer driver (procexp.sys). The reason behind the usage of process explorer driver is that the Anti-Malware service usually runs as a protected process, more specifically as PPL, to avoid disabling protection on the system caused by malicious activity. It is possible to abuse vulnerable versions of the procexp driver to close object handles of the protected process. Once enough handles are closed, the specific protected process will be killed. All samples we analyzed were abusing version 16.43 of this driver which is also the latest version vulnerable to this technique.

To obtain information about object handles, dotRunpeX uses NT API NtQuerySystemInformation with specified SystemInformationClass 0x10 that points to the undocumented structure [SYSTEM_HANDLE_INFORMATION]. This way, it finds all handles that belong to the protected process.

Figure 47: NtQuerySystemInformation used to obtain undocumented structure SYSTEM_HANDLE_INFORMATION

To process object handles of protected process, dotRunpeX uses WIN API DeviceIoControl to send IOCTL directly to the vulnerable procexp driver. The IOCTL “2201288708” (IOCTL_CLOSE_HANDLE) is in RDX register, and procexp driver routine processing this request is responsible for closing certain object handle of the specified process, regardless of whether the specified process is protected or not. Once enough object handles are closed, the Anti-Malware service is killed.

Figure 48: DeviceIoControl used to send the IOCTL “2201288708” to close the object handle of the protected process

We could also see that register R8 (lpInBuffer) points to data required to close the object handle. This data structure could be defined as follows:

typedef struct _ioControl
    PVOID lpObjectAddress;
    ULONGLONG ulSize;
    ULONGLONG ulHandle;

Let’s compare the procexp driver version used by all samples of dotRunpeX (version 16.43 – compiled 2021-08-17) and the latest version of the procexp driver (version 17.02 – compiled 2022-11-10). We can immediately spot the added patching code that is responsible for disabling the possibility of closing object handles of protected processes.

Figure 49: Process Explorer driver version 16.43 vs. 17.02

This technique of closing object handles of protected processes using the process explorer driver is well documented and part of an open-source project called Backstab. Process explorer drivers version 17.0+ are already patched.

After killing specific protected processes, Process Hollowing is what follows using WIN API CreateProcessW to start the process as suspended (in this case C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe) and direct NT API NtWriteVirtualMemory to write embedded payload of dotRunpeX into the newly created remote process.

It turned out that with an approach of dynamic analysis that focused on the native layer and certain usage of WIN/NT APIs, we got some interesting findings of this virtualized dotnet injector that could be used for automation and mass processing:

  • Each dotRunpeX sample has an embedded payload of a certain malware family to be injected
  • Each dotRunpeX sample has an embedded procexp driver to kill protected processes
  • There is very likely some kind of config hidden behind the virtualized code that specifies the target process for Process Hollowing, a protected process list to be killed (Anti-Malware services), and probably other interesting configurable things.

Encouraged by these findings, we can move forward to some automation using knowledge from the .NET internals world. When we are talking about dotnet, we can immediately think of code being managed by .NET runtime. More things are being managed, and among them is one very important for our further process, and that is so-called “Memory Management”. The types of memory in dotnet are stack and .NET heap. In the dotnet world, we do not need to bother with memory allocation/deallocation because these routines are handled by .NET runtime and garbage collector. Memory management of dotnet somehow needs to know what to allocate, where, and how; the same goes for deallocation/freeing of memory. Allocation on the .NET heap occurs once we talk about reference types inheriting from System.Object (class, object, string…). These objects are saved on the .NET heap, and for the purpose of their automatic management, they are accompanied by certain metadata information such as their type, references, and size. Even better, the automatic memory deallocation of no longer referenced objects does not occur immediately – the garbage collector takes care of this in some time intervals, which could be several minutes. Particular objects like “static objects” survive garbage collections and live till the application ends.

This means that if we could enumerate objects on the .NET heap, we could also get information related to their types and size that can serve for their appropriate reconstruction. Creating this kind of tool would be very likely time-consuming, but luckily for us, there is already created dotnet process and crash dump introspection open-source library ClrMD Microsoft.Diagnostics.Runtime developed by Microsoft that could be used precisely for object reconstruction from .NET heap. Why is that so important?

In a certain moment of dotRunpeX execution, embedded payload, procexp driver, and some kind of config must appear in a decrypted state. Their content will likely be assigned to some object allocated on the .NET heap. For these, we could expect byte array byte[] or string. That also means that if we could control the execution of dotRunpeX and suspend it in a state we assume to be the right moment for those object reconstructions, we would be able to get all that we need in a decrypted state.

One of the right moments for suspending and introspecting the dotRunpeX process could be an invocation of WIN API CreateProcessW used for Process Hollowing. This was approved to be the correct assumption and led us to develop the hooking library “CProcessW_Hook” exactly for this purpose.


Native hooking library using minhook framework (The Minimalistic x86/x64 API Hooking Library for Windows). The code provided below serves the purpose of hooking the WIN API function CreateProcessW, which is used in the dotRunpeX injector for process creation that is later used as a target for code injection (PE Hollowing). Once the CreateProcessW function is hooked and called in the target process, the whole process gets suspended to introspect. Certain process creations are filtered (powershell, conhost) as they can be spawned for other functionalities of dotRunpeX according to config (example modification of Windows Defender settings). We need to suspend the process only in a state before performing code injection (where all required objects are already decrypted on the .NET heap).

#include <windows.h>
#include <string.h>
#include "pch.h"
#include "MinHook.h"

#pragma warning(disable : 4996)

#if defined _M_X64
#pragma comment(lib, "libMinHook.x64.lib")
#elif defined _M_IX86
#pragma comment(lib, "libMinHook.x86.lib")

typedef LONG (__stdcall* NTSUSPENDPROCESS)(HANDLE ProcessHandle);


__declspec(dllexport) void __cdecl Decoy() 

int __stdcall DetourCreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
    LPCWSTR ignoredProcess[2] = { L"powershell", L"conhost" };
    for (int i = 0; i < 2; i++)
        if (wcsstr(_wcslwr(lpApplicationName), ignoredProcess[i]))
            return fpCreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation);

    HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
    if (!hNtdll)
    NTSUSPENDPROCESS NtSuspendProcess = (NTSUSPENDPROCESS)GetProcAddress(hNtdll, "NtSuspendProcess");
    if (!NtSuspendProcess)
    HMODULE cProcess = GetCurrentProcess();
    if (!cProcess) 
    return  1;

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
    switch (ul_reason_for_call)
        case DLL_PROCESS_ATTACH:
            if (MH_Initialize() != MH_OK)
                return 1;
            if (MH_CreateHook(&CreateProcessW, &DetourCreateProcessW, (LPVOID*)(&fpCreateProcessW)) != MH_OK)
                return 1;
            if (MH_EnableHook(&CreateProcessW) != MH_OK)
                return 1;
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
    return TRUE;

We could see that all hooking logic is executed right upon loading this library inside the function DllMain(). Another important thing to note is that we defined the export function Decoy(), which won’t be ever executed or called but is needed later on for our preinjection technique.

With the hooking library “CProcessW_Hook.dll” in its place, we can move on to create an injector and extractor. This points to the main tool provided below – dotRunpeX extractor “Invoke-DotRunpeXextract”.


PowerShell module that enables the extraction of payload, procexp driver, and config from dotRunpeX. The tool is written in PowerShell scripting language using preinjection of native hooking library “CProcessW_Hook.dll” (using AsmResolver) and .NET objects reconstruction from .NET heap (using ClrMD). It uses a dynamic approach for extraction, so samples are executed in a managed way (use only in VM). Using PowerShell 7.3+, clrMD v2.2.343001 (net6.0), AsmResolver v5.0.0 (net6.0).

We provide two versions of this tool that can be downloaded along with the hooking library in the last section of this article. One is created multi-threaded as a PowerShell module for the best performance and usage. The second version of this tool is a single-threaded script with the same functionality that could be used for simple debugging and troubleshooting and can more easily serve to create several snippets with similar functionality.

The whole code of this PowerShell module is annotated and commented on in a way to be easy to understand its core features. We will briefly describe the core functionality of this tool, like the preinjection technique of the hooking library using AsmResolver and implemented logic behind the extraction.

At first, this tool modifies the PE structure of dotRunpeX using AsmResolver. AsmResolver is well known for its capability to inspect dotnet executables and their related metadata, but it also allows access to low-level structures of PE to modify them. These PE structure modifications are used to implement our so-called PoC technique for the purpose of dll preinjection to a 64-bit dotnet executable. We are talking about adding a new import entry for the native hooking library into the .NET Assembly. Since dotRunpeX is a 64-bit executable, and it turned out that, unlike the 32-bit dotnet executables, the 64-bit ones don’t even have an import directory, we started building one from scratch right inside the function PatchBinaryWithDllInjection(). In this function, we can see that we are creating new data sections, .idata and .data, where our newly built IDT (Import Directory Table) and IAT (Import Address Table) will be placed. To get our hooking library “CProcessW_Hook.dll” preinjected right upon process start and let the windows loader do for us the hard work, we are creating an import entry with exported function Decoy() that was defined in the hooking library. As we are dealing with dotnet and adding native import, IL Only flag inside the .NET Directory is not true anymore and needs to be patched.

function PatchBinaryWithDllInjection($pathToSample, $patchedSample, $dllHookingName) 
    # Exported function name "Decoy" from hooking library will be used for Import Directory creation 
    $symbolNameToImport = [AsmResolver.PE.PEImage]::FromFile($dllHookingName).Exports.Entries[0].Name # Decoy
    # We need to work with pefile layer to expose sections - creation of Import Directory and IAT
    $pefile = [AsmResolver.PE.File.PEFile]::FromFile($pathToSample)

    # Creation of Import Directory from scratch
    $impDirBuff = [AsmResolver.PE.Imports.Builder.ImportDirectoryBuffer]::new($false)
    $impModule = [AsmResolver.PE.Imports.ImportedModule]::new($dllHookingName)
    $symbol = [AsmResolver.PE.Imports.ImportedSymbol]::new(0,$symbolNameToImport)

    # Creation of ".idata" section where Import Directory will be placed
    $idataSection = [AsmResolver.PE.File.PESection]::new(".idata", [AsmResolver.PE.File.Headers.SectionFlags]::MemoryRead -bor [AsmResolver.PE.File.Headers.SectionFlags]::ContentInitializedData)
    $idataSection.Contents = $impDirBuff

    # Creation of ".data" section where IAT will be placed
    $dataSection = [AsmResolver.PE.File.PESection]::new(".data", [AsmResolver.PE.File.Headers.SectionFlags]::MemoryRead -bor [AsmResolver.PE.File.Headers.SectionFlags]::MemoryWrite -bor [AsmResolver.PE.File.Headers.SectionFlags]::ContentInitializedData)
    $dataSection.Contents = $impDirBuff.ImportAddressDirectory

    # Remove ASLR (no reloc)
    $pefile.OptionalHeader.DllCharacteristics = $pefile.OptionalHeader.DllCharacteristics -bxor [AsmResolver.PE.File.Headers.DllCharacteristics]::DynamicBase
    # Update offsets and RVA of newly created data sections (so we can work with them later on)

    # Update info about new data directories in context of pefile - Import Directory, IAT
    $pefile.OptionalHeader.DataDirectories[[AsmResolver.PE.File.Headers.DataDirectoryIndex]::ImportDirectory] = [AsmResolver.PE.File.Headers.DataDirectory]::new($idataSection.Rva, $idataSection.GetPhysicalSize())
    $pefile.OptionalHeader.DataDirectories[[AsmResolver.PE.File.Headers.DataDirectoryIndex]::IatDirectory] = [AsmResolver.PE.File.Headers.DataDirectory]::new($dataSection.Rva, $dataSection.GetPhysicalSize())

    # We need to do some custom patching of IL only flag inside .NET Directory (it is easier than making custom writer preserving all PE sections and meta) - we are adding native imports so IL only is not true anymore
    $dotnetDirectoryRVA = $pefile.OptionalHeader.DataDirectories[[AsmResolver.PE.File.Headers.DataDirectoryIndex]::ClrDirectory].VirtualAddress
    $dotnetDirectoryFileOffset = $pefile.RvaToFileOffset($dotnetDirectoryRVA)
    $dotnetDirectoryILFlagsFileOffset = $dotnetDirectoryFileOffset + 16
    $filestream = [System.IO.FileStream]::new($patchedSample, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite)
    $filestream.Position = $dotnetDirectoryILFlagsFileOffset
    $filestream.Write([byte[]]::new(4), 0, 4) # Wipe the IL only flags

A comparison of the dotRunpeX sample before and after the described modification of the PE structure can be seen in the picture below.

Figure 50: PE structure of the dotRunpeX sample before and after modification used for dll preinjection

Now, we get to the state where our modified binary could be executed. With the hooking library in its place, the dotRunpeX process gets suspended right during the call to WIN API CreateProcessW. This exact routine is implemented in the function StartProcessWaitSuspended().

function StartProcessWaitSuspended($patchedSample)
    $process = [System.Diagnostics.Process]::Start($patchedSample)
    while ($process.Threads.Where{$_.ThreadState -ne [System.Diagnostics.ThreadState]::Wait -and $_.WaitReason -ne [System.Diagnostics.ThreadWaitReason]::Suspended}) 
        Start-Sleep -Milliseconds 500
    return $process  

Once the process is suspended, it is ready to be introspected. The whole logic behind the introspection of the dotRunpeX process can be seen in the function GetPayloadAndConfig(). In this function, we use the clrMD library to attach to the desired process and enumerate all System.Byte[] objects that are currently allocated on the .NET heap. To reconstruct the payload intended to be injected, we have implemented some dummy logic to find byte array objects larger than 1KB and starting with the “MZ” header. Despite the fact how it sounds, it has proven to be enough to fulfill our needs.

The logic behind finding the object corresponding to the process explorer driver and config is slightly different. First of all, the procexp driver and constants related to the config are saved in the same object. We assume that this is a result of the combination of usage KoiVM virtualizer and ConfuserEx obfuscator together as ConfuserEx usually puts defined constants to one blob of byte array and resolves them during the runtime once they are needed. After the logic finds this kind of byte blob, it separates the process explorer driver and config and pushes the config for further processing.

function GetPayloadAndConfig($process) 
{   # DataTarget is our suspended process
    $dataTarget = [Microsoft.Diagnostics.Runtime.DataTarget]::AttachToProcess($process.Id, $false)  
    Start-Sleep -Seconds 1 # Better to wait for ClrMD - to properly initialize DataTarget 
    $clrInfo = $dataTarget.ClrVersions[0]
    $clrRuntime = $clrInfo.CreateRuntime()
    # Getting all byte array objects from .NET Heap and sort them by size descending
    $objects = $clrRuntime.Heap.EnumerateObjects().ToArray().Where{$_.Type.Name -eq "System.Byte[]"} | Sort-Object -Property Size -Descending
    # Find payload to be injected - should be the largest byte array containing PE
    $payload = @()
    foreach ($object in $objects)
        # Check if byte array possible valid PE
        if($object.AsArray().Length -gt 1024)
            if((Compare-Object ($object.AsArray().ReadValues[byte](0,2)) ([byte[]] 0x4d,0x5a)).Length -eq 0)
                $payload = $object.AsArray().ReadValues[byte](0, $object.AsArray().Length)
    if(-not $payload){Write-Host "Payload to be injected NOT found in sample:"$process.MainModule.ModuleName"!!!" -ForegroundColor Red}
    # Find procexp driver + config (first 8 bytes of byte array skipped -> should be related to procexp PE size)
    $procexpAndConfig = @()
    foreach ($object in $objects)
        # Check if byte array possible procexp PE and config
        if($object.AsArray().Length -gt 1024)
            if((Compare-Object ($object.AsArray().ReadValues[byte](8,2)) ([byte[]] 0x4d,0x5a)).Length -eq 0)
                $procexpAndConfig = $object.AsArray().ReadValues[byte](0, $object.AsArray().Length)
    if(-not $procexpAndConfig)
        Write-Host "Procexp driver + config NOT found in sample:"$process.MainModule.ModuleName"!!!" -ForegroundColor Red
        $procexp = $null
        $config = $null
        return $payload, $procexp, $config
    # Process procexp and config
    $procexpSize = [bitconverter]::ToInt32($procexpAndConfig[4..7], 0)
    $procexp = $procexpAndConfig[8..($procexpSize+7)]
    $config = $procexpAndConfig[($procexpSize +8)..$procexpAndConfig.Length]
    return $payload, $procexp, $config

The so-called config is actually a bunch of constants where some of them serve as a configuration of dotRunpeX. This config needs to be parsed in the function ParseConfig() as it appears to be in some kind of structure where every string is preceded with its length and if needed, padded to have length divisible by 4, as shown in the picture below.

Figure 51: Unparsed config structure
function ParseConfig($config)
    $memStream = [System.IO.MemoryStream]::new($config, $true)
    $strLength = [byte[]]::new(4)
    $parsedConfig = ""

    while ($memStream.Position -lt $memStream.Length) 
        $memStream.Read($strLength, 0, 4) | Out-Null
        $length = [bitconverter]::ToInt32($strLength, 0)
        $buffer = [byte[]]::new($length)
        $memStream.Read($buffer , 0, $length) | Out-Null
        $parsedConfig += [System.Text.Encoding]::UTF8.GetString($buffer) + "`n"
        if(($memStream.Position % 4) -ne 0)
            $memStream.Position += 4 - ($memStream.Position % 4)
    return $parsedConfig

Once we have properly parsed the config, it is saved with extracted payload and process explorer driver, the suspended process gets killed, and the modified dotRunpeX sample is removed.

Example execution of “Invoke-DotRunpeXextract” and mass processing of samples could be seen below (2min GIF):

Figure 52: Execution of “Invoke-DotRunpeXextract” (2min GIF)

As pointed out before, “Invoke-DotRunpeXextract” will produce a payload to be injected, procexp driver, and parsed constants values where some of them could be referred to as config. Example config file content for our analyzed sample of the dotRunpeX:

/C computerdefaults.exe
Run without emulation
Select * from Win32_ComputerSystem
microsoft corporation
This file can't run into Virtual Machines.
SELECT * FROM Win32_VideoController
Run using valid operating system
HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0
SOFTWARE\Oracle\VirtualBox Guest Additions
SOFTWARE\VMware, Inc.\VMware Tools
HARDWARE\DEVICEMAP\Scsi\Scsi Port 1\Scsi Bus 0\Target Id 0\Logical Unit Id 0
HARDWARE\DEVICEMAP\Scsi\Scsi Port 2\Scsi Bus 0\Target Id 0\Logical Unit Id 0
Device Description
VM Additions S3 Trio32/64
S3 Trio32/64
VirtualBox Graphics Adapter
Fatal 'Error
/K "fodhelper.exe"
; Commands Here will be run Before Setup Begins to install
taskkill /IM cmstp.exe /F
49000,49001=AllUSer_LDIDSection, 7
"HKLM", "SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\CMMGR32.EXE", "ProfileInstallPath", "%UnexpectedError%", ""
Windows 1
Windows 8
Windows 7
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths
Add-MpPreference -ExclusionPath "
" -Force

We can easily spot configuration strings related to persistence settings, resource name and its decryption key (where .NET resource contains payload to be injected), target binary for the payload to be injected in, Anti-Malware service names to be killed, UAC bypass, Anti-VM, Anti-Sandbox, procexp driver installation path and its name, etc.

We provide two versions of this tool that can process just one sample or mass-process the directory of samples. For the best performance, the multi-threaded PowerShell module is recommended use. Still, for troubleshooting, simple modification, and easy debugging, we are also providing a single-threaded script with the same functionality as we expect soon some modification in dotRunpeX code where appropriate changes in the code of the tool or hooking library would be needed.


By monitoring this new threat for several months, we got deep insight into its evolution, delivery methods, and how it was abused to deliver a wide scale of different malware families.

Over time, we consider dotRunpeX to be in high development adding new features on regular bases and getting more popularity and attention every day. Because of the rising usage of this injector, we developed and provided several tools to automate the analysis of this virtualized dotnet code.

Some of the developed tools described in this report introduced PoC methods and can serve for developing other tools with similar functionality. We showed how open-source libraries such as AsmResolver and clrMD could be used in a real-world example to support the research and to help with the reverse engineering of protected code.

In this report, we provided an in-depth analysis of both versions of the dotRunpeX injector, the similarities between them, and described the main interesting techniques they use, such as abuse of the vulnerable process explorer driver, code virtualization caused by the usage of KoiVM protector, modification of D/Invoke framework with decoy syscall patching.

Our analysis and conclusions are based on dozens of campaigns we spotted in the wild and hundreds of samples that were mass processed.

Because of the high development of dotRunpeX, we believe that provided tools would need some modification soon as a reaction to changes in dotRunpeX. Still, with provided source codes, it should be relatively easy to work around these changes for other researchers.

Check Point customers remain protected from the threats described in this blog, including all its variants. Check Point’s Threat Emulation protects networks against unknown threats in web downloads and e-mail attachments. The Threat Emulation engine picks up malware at the initial phase before it enters the network. The engine quickly quarantines and runs the files in a virtual sandbox environment, which imitates a standard operating system, to discover malicious behavior at the exploit phase.

Harmony Email & Office deploys between the inbox and its native security. The solution secures inbound, outbound, and internal email from phishing attacks that evade platform-provided solutions and email gateways. It works with these other solutions and doesn’t require any MX record changes that broadcast security protocols to hackers.


SHA256 HashVersionMalware family of embedded payload
13081992c0ef5c52c2b6224f3ff1ab38160bca9424e7c0470e0c175c920bdc9dNEWCryptocurrency Stealer
17af8118607b9fc1f7b6aa82fd72f4fc115320d293e103dfe356706bb7c581b7NEWRecordBreaker – Raccoon Stealer 2.0
366284c1a0577937c86744349ac47e6e578da500ada3deb857ff233d9851ee6bNEWRecordBreaker – Raccoon Stealer 2.0
3e50f0eaf02d12653d5f757372240adcb5c16a5ab647a667637ba4c50d37aaadNEWRecordBreaker – Raccoon Stealer 2.0
47849f610a30d72660b1725a0b18d78c5204257b3740641727bdcbfd1ebd466aNEWRecordBreaker – Raccoon Stealer 2.0
507f413ac42df115988df498a90fc1ae610cafb66cb30a3a7de53e71ec90e7cdNEWRecordBreaker – Raccoon Stealer 2.0
57f261cc442dd9a4f1cd4ffd281c9855f4f9a736abffaf539d9df2a6ea0dd409NEWRecordBreaker – Raccoon Stealer 2.0
76eed1849d0a0474f9e0a58afcda2cc1ea7af316535b4b4b27ff810a162d4f8fNEWRecordBreaker – Raccoon Stealer 2.0
855b2e04c323a269d3731c093f0bc80ab3497a69ab8d2967847451a87f04fb0aNEWRecordBreaker – Raccoon Stealer 2.0
87134629723b2c6f4d0a74c35fdce89653471d9880b23f4faea6664ae151db0eNEWRecordBreaker – Raccoon Stealer 2.0
8bcc23ec881d61839fc57e8ec7425ac5ed625425fbf265fcb53ad73a73825b18NEWRecordBreaker – Raccoon Stealer 2.0
9177ba0c649f08fa6367d04091a7672fedb82215b26e08346645544f0631ebfdNEWRecordBreaker – Raccoon Stealer 2.0
9246ed27032429f234888b2713529001344850c608cab9f5ab7274195d330becNEWRecordBreaker – Raccoon Stealer 2.0
a487e959e59bc9500c43ac270eaf345eaf28173b07ed7dd82b2495aa19cdab88NEWRecordBreaker – Raccoon Stealer 2.0
ada1679a193c9b17b206b3d9ff2a19d64c6c8c5f882a321381c9d5347a8b4b3eNEWRecordBreaker – Raccoon Stealer 2.0
c1be6f792bd51d23d848e54cd217bdf9edcbb2b89df741190929f6fa327a10cbNEWRecordBreaker – Raccoon Stealer 2.0
db8ed3e6dd7e6818046e7ee1e9c6c91f98aa5ce3113b14fb1c85a50a45569b18NEWRecordBreaker – Raccoon Stealer 2.0
ddae8737d7cc35a87274a26b886e6b48ae947aa849c3d7ecb84de6f6d553aa96NEWRecordBreaker – Raccoon Stealer 2.0
efa9a303af112ffb6737846755e3a995510fd65b6ced9032dc68cd7bbe4c307dNEWRecordBreaker – Raccoon Stealer 2.0
20b5c7f210320cf23a63ac7f76086a6e257dd0c248d77deff444cb3dcf624799NEWRecordBreaker – Raccoon Stealer 2.0
f0ee1ddb789207c2000f728f6adabbe344ded7cba0804926a7cfc53bdbbc54ebNEWRecordBreaker – Raccoon Stealer 2.0
f440309e372551fb6ee00ecca71a70a1b8b7e077fe61b0687411147b582ab415NEWRecordBreaker – Raccoon Stealer 2.0
21a570237cdacdb8c69679e59c4dba6aa05f123f9db7470ec34e2f4024c3646bNEWRecordBreaker – Raccoon Stealer 2.0
4e8bf8c770727a3b0f551adcff2716c941234708e679c868ce42532714a29d27NEWRecordBreaker – Raccoon Stealer 2.0
3c0c55b4ce2d90448949980fbca1fa447832f67fb864472551513b6e4eff5304NEWRecordBreaker – Raccoon Stealer 2.0
61b5b6a513be380d50282c1c8391a5362d746bd70506343d04bda3751c3b25deNEWRecordBreaker – Raccoon Stealer 2.0
a4d455f65bb4d2dde03a0686433b6d515c71b5655fa78b86a4f9bdae503c1295NEWRecordBreaker – Raccoon Stealer 2.0
c9d36fcce70893aa16a846b48009bbd8b46fc11c6821b750083a9c89669038ccNEWRecordBreaker – Raccoon Stealer 2.0


rule injector_ZZ_dotRunpeX {
        description = "Detects new version of dotRunpeX - configurable .NET injector"
    author = "Jiri Vinopal (jiriv)"
    date = "2022-10-30"
    hash1 = "373a86e36f7e808a1db263b4b49d2428df4a13686da7d77edba7a6dd63790232" // injects Formbook
        hash2 = "41ea8f9a9f2a7aeb086dedf8e5855b0409f31e7793cbba615ca0498e47a72636" // injects Formbook
        hash3 = "5e3588e8ddebd61c2bd6dab4b87f601bd6a4857b33eb281cb5059c29cfe62b80" // injects AsyncRat
        hash4 = "8c451b84d9579b625a7821ad7ddcb87bdd665a9e6619eaecf6ab93cd190cf504" // injects Remcos
        hash5 = "8fa81f6341b342afa40b7dc76dd6e0a1874583d12ea04acf839251cb5ca61591" // injects Formbook
        hash6 = "cd4c821e329ec1f7bfe7ecd39a6020867348b722e8c84a05c7eb32f8d5a2f4db" // injects AgentTesla
        hash7 = "fa8a67642514b69731c2ce6d9e980e2a9c9e409b3947f2c9909d81f6eac81452" // injects AsyncRat
        hash8 = "eb2e2ac0f5f51d90fe90b63c3c385af155b2fee30bc3dc6309776b90c21320f5" // injects SnakeKeylogger
    // Used ImplMap imports (PInvoke) 
        $implmap1 = "VirtualAllocEx"
        $implmap2 = "CreateProcess"
        $implmap3 = "CreateRemoteThread"
        $implmap4 = "Wow64SetThreadContext"
        $implmap5 = "Wow64GetThreadContext"
        $implmap6 = "NtResumeThread"
        $implmap7 = "ZwUnmapViewOfSection"
        $implmap8 = "NtWriteVirtualMemory"
        $implmap9 = "MessageBox" // ImplMap not presented in all samples - maybe different versions?
        $implmap10 = "Wow64DisableWow64FsRedirection"
        $implmap11 = "Wow64RevertWow64FsRedirection"
        $implmap12 = "CreateFile"
        $implmap13 = "RtlInitUnicodeString"
        $implmap14 = "NtLoadDriver"
        $implmap15 = "NtUnloadDriver"
        $implmap16 = "OpenProcessToken"
        $implmap17 = "LookupPrivilegeValue"
        $implmap18 = "AdjustTokenPrivileges"
        $implmap19 = "CloseHandle"
        $implmap20 = "NtQuerySystemInformation"
        $implmap21 = "DeviceIoControl"
        $implmap22 = "GetProcessHeap"
        $implmap23 = "HeapFree"
        $implmap24 = "HeapAlloc"
        $implmap25 = "GetProcAddress"
        $implmap26 = "CopyMemory" // ImplMap added by KoiVM Protector used by this injector
        $modulerefKernel1 = "Kernel32"
        $modulerefKernel2 = "kernel32"
        $modulerefNtdll1 = "Ntdll"
        $modulerefNtdll2 = "ntdll"
        $modulerefAdvapi1 = "Advapi32"
        $modulerefAdvapi2 = "advapi32"

        $regPath = "\\Registry\\Machine\\System\\CurrentControlSet\\Services\\TaskKill" wide // Registry path for installing Sysinternals Procexp driver
        $rsrcName = "BIDEN_HARRIS_PERFECT_ASSHOLE" wide
        $koiVM1 = "KoiVM"
        $koiVM2 = "#Koi"
        uint16(0) == 0x5a4d and uint16(uint32(0x3c)) == 0x4550 and ($regPath or $rsrcName or 1 of ($koiVM*)) and
        24 of ($implmap*) and 1 of ($modulerefKernel*) and 1 of ($modulerefNtdll*) and 1 of ($modulerefAdvapi*) 

rule injector_ZZ_dotRunpeX_oldnew {
		description = "Detects new and old version of dotRunpeX - configurable .NET injector"
    author = "Jiri Vinopal (jiriv)"
    date = "2022-10-30"
    hash1_New = "373a86e36f7e808a1db263b4b49d2428df4a13686da7d77edba7a6dd63790232" // injects Formbook
		hash2_New = "41ea8f9a9f2a7aeb086dedf8e5855b0409f31e7793cbba615ca0498e47a72636" // injects Formbook
		hash3_New = "5e3588e8ddebd61c2bd6dab4b87f601bd6a4857b33eb281cb5059c29cfe62b80" // injects AsyncRat
		hash4_New = "8c451b84d9579b625a7821ad7ddcb87bdd665a9e6619eaecf6ab93cd190cf504" // injects Remcos
		hash5_New = "8fa81f6341b342afa40b7dc76dd6e0a1874583d12ea04acf839251cb5ca61591" // injects Formbook
		hash6_New = "cd4c821e329ec1f7bfe7ecd39a6020867348b722e8c84a05c7eb32f8d5a2f4db" // injects AgentTesla
		hash7_New = "fa8a67642514b69731c2ce6d9e980e2a9c9e409b3947f2c9909d81f6eac81452" // injects AsyncRat
		hash8_New = "eb2e2ac0f5f51d90fe90b63c3c385af155b2fee30bc3dc6309776b90c21320f5" // injects SnakeKeylogger
		hash1_Old = "1e7614f757d40a2f5e2f4bd5597d04878768a9c01aa5f9f23d6c87660f7f0fbc" // injects Lokibot
		hash2_Old = "317e6817bba0f54e1547dd9acf24ee17a4cda1b97328cc69dc1ec16e11c258fc" // injects Redline
		hash3_Old = "65cac67ed2a084beff373d6aba6f914b8cba0caceda254a857def1df12f5154b" // injects SnakeKeylogger
		hash4_Old = "68ae2ee5ed7e793c1a49cbf1b0dd7f5a3de9cb783b51b0953880994a79037326" // injects Lokibot
		hash5_Old = "81763d8e3b42d07d76b0a74eda4e759981971635d62072c8da91251fc849b91e" // injects SnakeKeylogger
	// Used ImplMap imports (PInvoke) 
		$implmap1 = "VirtualAllocEx"
		$implmap2 = "CreateProcess"
		$implmap3 = "CreateRemoteThread"
		$implmap4 = "Wow64SetThreadContext"
		$implmap5 = "Wow64GetThreadContext"
		$implmap6 = "RtlInitUnicodeString"
		$implmap7 = "NtLoadDriver"
		$implmap8 = "LoadLibrary"
		$implmap9 = "VirtualProtect"
		$implmap10 = "AdjustTokenPrivileges"
		$implmap11 = "GetProcAddress"
		$modulerefKernel1 = "Kernel32"
		$modulerefKernel2 = "kernel32"
		$modulerefNtdll1 = "Ntdll"
		$modulerefNtdll2 = "ntdll"

		$regPath = "\\Registry\\Machine\\System\\CurrentControlSet\\Services\\TaskKill" wide // Registry path for installing Sysinternals Procexp driver
		$koiVM1 = "KoiVM"
		$koiVM2 = "#Koi"
		uint16(0) == 0x5a4d and uint16(uint32(0x3c)) == 0x4550 and ($regPath or $rsrcName or 1 of ($koiVM*)) and
		9 of ($implmap*) and 1 of ($modulerefKernel*) and 1 of ($modulerefNtdll*) 


  1. KoiVM protector:
  2. Reflection in .NET:
  3. P/Invoke:
  4. D/Invoke:
  5. Backstab:
  6. MinHook:
  7. ClrMD:
  8. AsmResolver:
  9. OldRod:

Tools to Download



  • Check Point Research Publications
  • Global Cyber Attack Reports
  • Threat Research
February 17, 2020

“The Turkish Rat” Evolved Adwind in a Massive Ongoing Phishing Campaign

  • Check Point Research Publications
August 11, 2017

“The Next WannaCry” Vulnerability is Here

  • Check Point Research Publications
January 11, 2018

‘RubyMiner’ Cryptominer Affects 30% of WW Networks