Skip to content

Security1 publisher2 min readPublished

2CLoader slips Vidar, Remus and XWorm past EDR with Hell's Gate indirect syscalls

Zscaler ThreatLabz says 2CLoader, found in August 2026, drops the Vidar and Remus stealers and XWorm RAT while routing six ntdll calls around EDR's inline hooks. Detection tuned only to those payloads misses the loader stage.

The Watch · Security desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

Illustration accompanying 2CLoader slips Vidar, Remus and XWorm past EDR with Hell's Gate indirect syscalls
Generated illustration

What happened

  • A fallback resolves the same six functions with an ordinary GetProcAddress lookup when the syscall setup fails on the host.
  • The encrypted payload and its decryption material travel inside the binary itself, stored as a Portable Executable resource.
  • Before running, 2CLoader checks for virtual machines, debuggers and genuine user activity, and its configuration picks among several execution and persistence options.

Compiled by The WatchSomething wrong?How this is made

Why it matters

  • constraint Endpoint tools that watch behavior by hooking ntdll in userland get no signal on those six operations, so injection that would normally trip a rule runs unseen.
  • decision Detection has to be built at the loader stage, on its resource layout and syscall behavior, because the Vidar, Remus and XWorm payloads it carries are already well-signatured.
  • capability One evasion-hardened loader gives operators a single delivery layer for two stealers and a RAT, so the same tradecraft now moves three payload families.

Inline hooking is how many endpoint tools watch a process: the agent rewrites the first bytes of sensitive ntdll functions with a jump into its own inspection code. 2CLoader steps around that for six native calls using the Hell's Gate technique. It maps a clean copy of ntdll straight from disk, reads the syscall number out of each unhooked stub, and confirms the stub by its opening bytes (mov r10, rcx; mov eax, ssn, with a CET endbr64 variant handled too). [7][8] It then walks the loaded module's Process Environment Block to find a bare syscall; ret gadget (0F 05 C3) in executable memory, stores the six syscall numbers and gadget addresses in globals, and drives every later call through the gadget. [9]

The six functions are NtProtectVirtualMemory, NtUnmapViewOfSection, NtQueryInformationProcess, NtDelayExecution, NtSetContextThread and NtGetContextThread. [7] They cover memory protection changes, section unmapping, and thread-context reads and writes.

There is a fallback. If the syscall setup fails, 2CLoader resolves the same six APIs with GetProcAddress and stores the export addresses instead. [10] That path runs through the hooked functions, so on a host where Hell's Gate breaks the loader is visible to the same hooks it was avoiding.

The resource is laid out in order: the final payload, an optional dropped payload, an optional MessageBoxW string, then the configuration. [11] The config is the trailing 0xDC-byte block, 220 bytes, opening with the magic value 2C 3D 4E 5F. [12][1] It holds the AES-GCM nonce and tag plus two XOR masks that derive the AES-256 key for decrypting the payload, with a byte-sum checksum to validate that key. [13]

Across the samples Zscaler has seen, the loader has mostly delivered Vidar and Remus, it says. [6]

What to watch

  • Whether 2CLoader's operators expand beyond Vidar, Remus and XWorm to other payloads.
  • Whether the 2C 3D 4E 5F config magic and PE-resource layout stay stable enough to detect on.
  • Whether affected shops move detection from userland hooks toward syscall or kernel telemetry.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories