Overview
SleepWalker is a Windows x64 backdoor built around passive task reception rather than a conventional outbound beacon. Its embedded bootstrap watches raw IPv4 traffic for authenticated tasking and routes accepted bytecode into a 23-command interpreter spanning scheduling, staged execution, files, sockets, ICMP, and named pipes. The interpreter also implements a DNS-encoded carrier that later tasking can enable.
Analyzed sample
R136a1 published SLEEPWALKER: A Passive Backdoor With Its Own Command Language on August 24, 2026. Proofmetry independently examined one variant and traced its startup, task authentication, shared interpreter, transport abstractions, and command-driven host effects.
| Field | Value |
|---|---|
| SHA-256 | d347170752a28e2b8c4b8b9f3cab2e3a6541ba11682c94498d26eb9002779d60 |
| File type | Windows x64 DLL |
| Required host name | ERAAgent.exe |
| Command count | 23 |
Host activation and proxy behavior
SleepWalker does not enter its backdoor worker unconditionally. On process attach, it reconstructs the UTF-16 basename ERAAgent.exe and compares it with the current executable name. Only an exact match starts the detached worker. The startup logic closes the new thread handle immediately and has no duplicate-worker guard.
A separate proxy path resolves one of seven forwarded exports from dpapisvc.dll. If the dependency cannot be loaded, the selected export is invalid, or resolution fails, the DLL terminates the host process. The proxy role and the process-name test are therefore part of the implant's activation design, not incidental loader behavior.
The worker allocates a 128 KiB process-wide staging area, initializes Winsock, recovers the embedded task, and enters the interpreter. Process detach sets a shared stop flag consulted by the interpreter, sleep helpers, schedulers, and long-running receive operations.
A passive opening move
The embedded bootstrap does not identify an initial remote C2 endpoint. Its active instruction places the sample into passive raw-packet capture across eligible local IPv4 interfaces. Subsequent endpoints, files, pipe names, delays, and nested work arrive as authenticated operands rather than fixed bootstrap values.
Incoming traffic is filtered before it reaches the task decoder. Structurally valid candidates must then pass authenticated decryption; only accepted plaintext is handed to the shared interpreter. A second receive mode extracts a compact authenticated task from specially formed DNS queries and feeds it into the same dispatch path.
The passive start is the sample's defining operational difference. Activity begins with an inbound packet rather than a process-originated connection, and consequential endpoint behavior appears only after that packet is accepted. Detection logic that looks only for an outbound beacon can miss the start of this chain.
Authentication and transport architecture
SleepWalker uses AES-CCM to authenticate inbound tasks. Its general transport paths use one envelope form, while the DNS carrier uses a more compact form suited to query-name transport. Both converge on the same rule: unauthenticated bytes are discarded before command interpretation.
The receive side spans TCP clients and listeners, UDP, named-pipe clients and servers, file-backed tasks, passive raw packets, and DNS-carried tasks. Selected socket handlers can also use VMware VMCI after querying \\.\VMCI for the provider's address family.
Outbound commands are intentionally separate from the authenticated input layer. TCP, UDP, ICMP, and named-pipe send handlers pass the task-supplied buffer to their respective transport without adding task-channel protection. For network monitoring, that means the data emitted after a valid task may be plainly recognizable even though the inbound control material was authenticated and encrypted.
The 23-command language
The compact interpreter covers lifecycle control, delays, schedules, nested programs, staged data, file-backed tasks, and several local and network communication modes.
Lifecycle and timing
EXIT, SPAWN_THREAD_SCRIPT, SLEEP_SECONDS, SLEEP_RANDOM_SECONDS, CRON_SCHEDULE, REPEAT_N, and LOOP_FOREVER.
Staging and execution
DECOMPRESS_RUN, STAGE_WRITE, STAGE_VERIFY_EXEC, RUN_FILE_SCRIPT, and RUN_SHELLCODE.
Outbound transport
TCP_SEND, UDP_SEND, ICMP_SEND, and PIPE_SEND.
Authenticated receive
TCP_CONNECT_RECV, TCP_LISTEN_RECV, UDP_BIND_RECV, PIPE_CLIENT_RECV, and PIPE_SERVER_RECV.
Passive reception
SNIFF_MAGIC_PACKET and SNIFF_MAGIC_PACKET_DNS watch for authenticated task carriers without an initial outbound beacon.
Because every route returns to the same interpreter, a passive trigger can lead to any behavior implemented by the command language.
Timing, scheduling, and nested execution
Detached child programs run on separate threads. Fixed and random delays, schedules, bounded repeats, infinite loops, and EXIT all share the process-wide stop state, allowing the stop command to unwind long-running work without terminating the host process. The random-delay branch applies modulo using the supplied upper bound without rejecting zero.
Staging, decompression, and native execution
SleepWalker maintains one 128 KiB staging allocation for incremental task assembly. Its staged-execution branch requires SHA-256 verification and clears the complete shared staging area after processing.
The command set can also expand LZMA-compressed bytecode and load an authenticated task from disk.
RUN_SHELLCODE treats its operand as native code: it allocates writable memory, copies the buffer, changes the region to executable-read, invokes it synchronously, and releases the allocation after control returns.
Network, named-pipe, and file behavior
The TCP and UDP command families support both sending data and receiving additional authenticated tasks. TCP can initiate a connection or bind a listener, while the ICMP branch places supplied content into echo requests.
Named pipes mirror the same send-and-receive split. The optional server-security branch changes the pipe ACL and related registry settings. Teardown restores the saved LSA value but removes the selected NullSessionPipes entry, including one that already existed.
Command walkthrough
Each walkthrough follows a decrypted interpreter record from handler parsing to its native effect. Exact carrier-construction instructions and authentication material are intentionally omitted.
Across the receive modes, SleepWalker validates the carrier, authenticates its encrypted contents, recovers a command record, and passes that record to the shared interpreter. The DNS path uses a compact carrier suited to query-label transport.
Lifecycle and timing
0x06 · EXIT
The dispatcher sets the process-wide stop flag to one. Interpreter iterations, schedulers, receive loops, and interruptible sleeps consult that value, allowing the active worker to unwind while the host process remains running.
0x06 fall-through path sets the process-wide stop state and rejoins the interpreter’s shared remaining-byte check.Open full size
01 00 00 00, confirming the native effect of EXIT.Open full size0x0B · SPAWN_THREAD_SCRIPT
The handler moves an owned nested program into a detached worker thread. That worker re-enters the common interpreter, securely clears the program bytes after execution, and frees the descriptor while the parent continues independently.
SPAWN_THREAD_SCRIPT hands an owned child program to a detached worker and closes the successful thread handle; the worker interprets and frees the program, while creation failure returns it to dispatcher cleanup. Composite of the dispatch branch, thread-creation helper, worker entry, failure cleanup, and interpreter continuation.Open full size
0C 00 00, confirming execution of the nested program.Open full size0x0C · SLEEP_SECONDS
The command reads a 16-bit interval and calls the interruptible sleep helper. Long waits are divided into chunks of at most 60 seconds so the helper can recheck the shared stop state between calls to Sleep.
SLEEP_SECONDS parses a big-endian 16-bit interval, limits each Sleep call to 60 seconds, and checks the shared stop state between chunks. Composite of the dispatch branch, interruptible-sleep helper, and interpreter continuation.Open full size
RCX is 1, matching the decoded one-second interval.Open full size0x0D · SLEEP_RANDOM_SECONDS
The random-delay helper obtains four random bytes, reduces the value modulo the supplied 16-bit upper bound, and passes the remainder to the interruptible sleeper. The parser accepts zero, leaving a divide-by-zero condition in the modulo operation.
SLEEP_RANDOM_SECONDS reduces a four-byte random value modulo the parsed bound before invoking the interruptible sleeper. Composite of the dispatch branch, random-delay helper, and interpreter continuation.Open full size
0x0E · CRON_SCHEDULE
The scheduler compares minute, hour, day-of-month, and weekday masks with UTC. It masks the owned child program while waiting, restores it for a matching minute, interprets it, and masks it again; duplicate suppression is based only on the last minute-of-hour.
CRON_SCHEDULE parses four UTC masks and an owned child program, suppresses duplicate minutes, and unmasks the child only for a matching interpreter call before returning it to the masking path. Composite of the command parser, scheduler setup and matching logic, owned-buffer cleanup, and interpreter continuation.Open full size
0x0F · REPEAT_N
The repeat helper runs an owned nested program up to the parsed 16-bit count and tests the shared stop flag between iterations. A count of zero is a no-op; each nonzero iteration enters the same bounded interpreter used for top-level tasks.
REPEAT_N interprets the owned child up to the parsed count and stops early when the process-wide stop state changes. Composite of the command parser, repeat helper, owned-buffer cleanup, and interpreter continuation.Open full size
0x10 · LOOP_FOREVER
The loop helper repeatedly re-enters one owned nested program until the process-wide stop flag changes. It checks that flag before every iteration and retains the same program descriptor for the loop's lifetime.
LOOP_FOREVER checks the shared stop state before every re-entry into the same owned child program. Composite of the command parser, loop helper, owned-buffer cleanup, and interpreter continuation.Open full size
LOOP_FOREVER reaches the nested-interpreter call. The repeated-loop and stop-state semantics are shown in Figure 17.Open full sizeDecompression, transport, and staging
0x1F · DECOMPRESS_RUN
The handler parses a declared output size, five raw-LZMA property bytes, and a compressed blob. It allocates the declared buffer, records the decoder's actual output length, and sends the recovered bytes back to the SleepWalker interpreter; the decompressed content is bytecode, not native code.
DECOMPRESS_RUN decodes the supplied raw-LZMA blob into a declared-size buffer, records the actual output length, adopts the resized result, and passes it to the nested-program path. Composite of the inline parser, LZMA helper, and shared nested-program continuation.Open full size
0C 00 00, confirming the LZMA output was decoded as SLEEP_SECONDS(0).Open full size0x29 · TCP_SEND
The stream sender parses local and remote host/service pairs, an arbitrary blob, and a deadline. It optionally binds the local tuple, connects to the remote endpoint, and sends the original buffer without AES-CCM wrapping. A vm: destination selects VMware VMCI; ordinary host and service pairs use IPv4/TCP.
TCP_SEND selects the stream transport from the remote endpoint, optionally binds the local tuple, connects it, and forwards the parsed data through a send-all loop backed by direct socket sends. Composite of the command parser, stream-sender core, send-all loop, and single-send operation.Open full size
43101, the command buffer contains the complete marker, and its authoritative length is 0x1E. This confirms execution of the decoded TCP_SEND operands that produced Figure 22.Open full size0x2A · UDP_SEND
The datagram sender uses the same endpoint grammar as TCP_SEND, optionally binds the local tuple, and transmits the command-owned blob in one datagram without task-envelope protection. A vm: destination selects VMware VMCI; ordinary destinations use IPv4/UDP.
UDP_SEND optionally binds the selected local tuple and passes the command-owned buffer to a resolved destination in one sendto operation. Composite of the command parser, datagram-sender core, endpoint-resolution path, and resolved-send operation.Open full size
43102, the command buffer contains the complete marker, and the length is 0x1E. This confirms execution of the UDP_SEND operands that produced Figure 25.Open full size0x2B · ICMP_SEND
The handler resolves the destination as IPv4 and calls IcmpSendEcho with the supplied blob. The parsed source string is never used, the parsed deadline does not reach the handler, the API timeout is fixed at one millisecond, and the request length is truncated to 16 bits.
ICMP_SEND leaves the parsed source and deadline unused, resolves an IPv4 destination, calls IcmpSendEcho with a one-millisecond timeout, and frees the allocated reply buffer. Composite of the command parser, IPv4-resolution path, and echo-call cleanup.Open full size
192.168.189.20 with the reply from 192.168.189.10 using identifier one and sequence three. The lower callout identifies the complete 30-byte marker in the selected request bytes.Open full size
0x1E. These are the effective operands that produced Figure 28; the parsed source and deadline are absent from the call.Open full size0x2C · PIPE_SEND
The pipe sender parses server and pipe names, optional username and password strings, a blob, and a deadline. It opens the selected named-pipe client and writes the supplied bytes unchanged.
PIPE_SEND supplies the parsed credentials to a write-mode pipe client, writes the original command buffer, and destroys the transport after the write attempt. Composite of the command parser and cleanup, dispatcher call site, pipe-sender core, and overlapped write-all loop.Open full size
0x1E. Those operands match the bytes written to the selected pipe.Open full size0x32 · STAGE_WRITE
The inline branch parses a 32-bit offset and a blob, rejects offsets outside the 128 KiB shared stage, checks the computed end for overflow and capacity, and copies only an accepted range. The temporary command buffer is freed on both success and failure paths.
STAGE_WRITE rejects offsets outside the stage and copies only when offset + length remains within capacity without wrapping.Open full size
0C 00 00 while following stage bytes remain populated. The visible state confirms the requested write; range preservation is established by the bounds and copy length in Figure 32.Open full size0x33 · STAGE_VERIFY_EXEC
The branch hashes the selected staging prefix with SHA-256 and requires the supplied digest blob to be exactly 32 bytes. A match copies the verified prefix into an owned program before interpretation; both match and mismatch paths clear the complete 128 KiB staging region.
STAGE_VERIFY_EXEC requires a 32-byte digest, compares it with the staged prefix’s SHA-256 value, clears the complete stage on either outcome, and sends only a verified owned copy to the interpreter. Composite of the verification branch and shared nested-program continuation.Open full size
RBX retains the owned program descriptor while R8 and R9 hold the 0x20000-byte clear length and stage base. The foreground dump is not the child buffer; this stop establishes the matched copy-and-clear path.Open full size
0x20000 extent comes from the runtime argument in Figure 35 and the implementation in Figure 34.Open full size0x65 · RUN_SHELLCODE
The native-code handler allocates read/write memory, copies the supplied blob, changes the region to execute/read with VirtualProtect, calls it synchronously, and releases the allocation after control returns. It does not request a writable and executable page at the same time.
RUN_SHELLCODE copies the supplied blob into a read/write allocation, changes the region to execute/read, invokes it synchronously, and frees it after return. Composite of the dispatch branch and native-code execution helper.Open full size
WinExec with C:\Windows\System32\calc.exe as the command line. The return address remains inside the transient allocation, confirming that the 0x65 blob reached native execution.Open full size0x66 · RUN_FILE_SCRIPT
The file-backed handler reads the complete named file into owned memory and passes it to the normal authenticated-task sink. The file must contain a normal AES-CCM envelope rather than plaintext bytecode; after processing, the handler clears the owned bytes but does not delete or rewrite the source file.
RUN_FILE_SCRIPT reads the complete file into owned memory, submits those bytes to the authenticated-task sink, and securely clears the buffer afterward. Composite of the dispatch branch, file-task wrapper, and whole-file reader.Open full size
0C 00 00, confirming authentication and nested dispatch.Open full sizeAuthenticated receive paths
0x6F · TCP_CONNECT_RECV
The connect-style receiver parses local and remote endpoint pairs plus a deadline, optionally binds the local tuple, opens the selected stream, and accumulates at most 1 MiB. The complete stream must authenticate as one normal AES-CCM envelope before its plaintext reaches the interpreter. A vm: remote host selects VMware VMCI; ordinary remote endpoints use IPv4/TCP.
TCP_CONNECT_RECV optionally binds the selected local tuple, connects to the remote endpoint, receives into a one-megabyte-capped buffer, destroys the transport, and submits the accumulated bytes to the authenticated-task sink. Composite of the command parser, connect-and-receive core, and nonblocking connect helper.Open full size
0–11 to the nonce, 12–27 to the authentication tag, and 28–30 to ciphertext.Open full size
0C 00 00. The retained endpoint state ties that plaintext to the TCP_CONNECT_RECV scenario.Open full size0x70 · TCP_LISTEN_RECV
The listener parses a bind endpoint and deadline, accepts one connection with backlog one, and receives a stream capped at 1 MiB. The accumulated bytes enter the normal authenticated-task sink only after the listening transport is replaced by the accepted connection. A vm: bind host selects VMware VMCI; ordinary bind addresses use IPv4/TCP.
TCP_LISTEN_RECV binds a stream listener, accepts one connection with backlog one, replaces the listener with the accepted transport, and submits the capped receive buffer to the authenticated-task sink. Composite of the command parser, listen-and-receive core, and single-accept helper.Open full size
[12], tag [16], and ciphertext [3] layout matches Figure 41.Open full size
0C 00 00, and the listener service remains visible in the caller state. Together with Figure 44, this confirms delivery, authentication, and dispatch.Open full size0x73 · UDP_BIND_RECV
The datagram receiver binds the supplied endpoint and performs one receive into a 1,500-byte buffer. It does not assemble fragments across application datagrams: the single received datagram must contain a complete normal AES-CCM envelope before dispatch.
UDP_BIND_RECV binds the selected datagram endpoint, performs one recvfrom into a 1,500-byte buffer, and passes the received datagram directly to the authenticated-task sink. Composite of the command parser, bind-and-receive core, and single-datagram receive helper.Open full size
[12], tag [16], and ciphertext [3] layout matches Figure 41; the single-datagram transport is specific to UDP_BIND_RECV.Open full size
0x1F, and authentication produces an owned length-three script containing 0C 00 00, linking the datagram in Figure 47 to SLEEP_SECONDS(0).Open full size0x7D · PIPE_CLIENT_RECV
The pipe client parses server and pipe names, optional credentials, and a deadline, then reads through a pipe transport into an owned buffer capped at 1 MiB. Wildcard credentials suppress the explicit credentialed WNetAddConnection2W step, but the client still connects to the named pipe. The accumulated bytes must authenticate as one normal task envelope before interpretation.
PIPE_CLIENT_RECV opens a read-mode pipe transport, applies the selected connection mode, retries a busy pipe through WaitNamedPipeW, receives a capped buffer, and submits it to the authenticated-task sink. Composite of the command parser, receiver core, credential and access-mode selection, client connection path, and busy-pipe retry path.Open full size
0C 00 00.Open full size0x7E · PIPE_SERVER_RECV
The server creates one named-pipe instance, accepts one local client, and reads at most 1 MiB for normal-envelope authentication. The optional = security value changes the pipe ACL and related anonymous-access registry settings; an empty value retains the default local security path. Teardown restores the saved LSA value but removes the pipe from NullSessionPipes after either a successful add or an already-present result.
PIPE_SERVER_RECV parses the pipe name, security option, and deadline; server setup may adjust NullSessionPipes and everyoneincludesanonymous before creating and accepting one named-pipe instance. Teardown restores the saved LSA value but removes the pipe entry after either a successful add or an already-present result. Composite of the command parser, receive wrapper, security-option selection, server-creation path, and transport teardown.Open full size
0C 00 00.Open full sizePassive receive modes
0x87 · SNIFF_MAGIC_PACKET
The passive listener expands the interface selector into eligible local IPv4 capture contexts and enables broad raw reception. A candidate frame must satisfy the recovered-length relation, header relation, bounded prefix, CRC-32, and process-wide three-second acceptance guard before its normal AES-CCM envelope is authenticated.
SNIFF_MAGIC_PACKET validates the trailer-derived frame length, header relation, 16-to-64-byte prefix, CRC-32, and process-wide three-second acceptance guard before handing the embedded envelope to the authenticated-task sink. Composite of the command parser, passive-listener call, carrier-validation block, and authentication handoff.Open full size
[0x00–0x03], 17-byte prefix [0x04–0x14], CRC-32 [0x15–0x18], normal envelope [0x19–0x37], and two trailer words [0x38–0x3B].Open full size
0x1F bytes, and successful authentication yields an owned three-byte descriptor containing 0C 00 01, linking the final frame in Figure 54 to SLEEP_SECONDS(1).Open full size0x88 · SNIFF_MAGIC_PACKET_DNS
This mode retains the normal raw-frame path and additionally inspects eligible port-53 queries. It accepts a one-question message with no resource records, verifies the checksum markers around each carrier label, decodes the lowercase Base32 interiors, concatenates them within a 256-byte bound, and authenticates the result as a compact DNS envelope.
SNIFF_MAGIC_PACKET_DNS accepts the DNS-specific mode, filters one-question queries with no resource records, concatenates decoded QNAME labels, and validates each label’s g-through-v CRC markers before lowercase Base32 decoding. Composite of the command branch, DNS-header prefilter, QNAME assembly loop, and label decoder.Open full size
0x5357, one question and no resource records; the 25-byte CRC-marked lowercase-Base32 carrier label; the example.invalid suffix; and A/IN type and class. The query is one-way because SleepWalker observes it passively rather than acting as a DNS server.Open full size
0C 00 00. This confirms that the DNS query in Figure 57 reached the interpreter as SLEEP_SECONDS(0).Open full sizeDetection opportunities
No single API call uniquely identifies SleepWalker. The strongest detections correlate the activation context, passive network access, interpreter-driven follow-on behavior, and short-lived memory or registry changes.
- Host and proxy sequence: a DLL running under an executable named
ERAAgent.exe, resolving forwarded exports fromdpapisvc.dll, and ending the host when that resolution fails. - Raw capture behavior: raw IPv4 sockets placed into broad receive mode with
SIO_RCVALLacross local interfaces. - Passive-to-active transition: unusual packet or DNS activity subsequently followed by file, memory, socket, ICMP, or named-pipe operations from the same process.
- VMware communication: access to
\\.\VMCIbefore socket-style activity. - Staging lifecycle: a 128 KiB allocation followed by content placement, SHA-256 verification, interpreter re-entry, and complete clearing.
- Native execution pattern: writable memory changing to executable-read immediately before an indirect call.
- Named-pipe policy changes: a new null-session pipe entry or a change to anonymous-user inclusion near pipe-server activity.
- Control and output split: direct TCP, UDP, ICMP, or pipe content appearing after authenticated inbound tasking but without the same task-channel wrapping.
Sequence is the useful discriminator. Raw socket access, scheduled execution, hashing, named pipes, or an executable memory transition can each appear in legitimate software. Their ordering around the exact host gate and a passive activation event is much more specific.
Indicators and hunting pivots
| Indicator | Type | Context |
|---|---|---|
d347170752a28e2b8c4b8b9f3cab2e3a6541ba11682c94498d26eb9002779d60 | SHA-256 | Analyzed DLL |
ERAAgent.exe | Process name | Exact host gate for this build |
dpapisvc.dll | Module name | Proxy dependency and forwarded-export resolution |
\\.\VMCI | Device path | VMware transport discovery |
SIO_RCVALL | Socket control | Broad raw IPv4 packet reception |
NullSessionPipes | Registry value | Optional named-pipe configuration |
everyoneincludesanonymous | Registry value | Optional anonymous-access policy change |
The names above are strongest when combined with the behavioral sequence described in the detection section. Several refer to legitimate Windows, ESET, or VMware components and are weak indicators on their own.
Conclusion
SleepWalker combines an unusually quiet opening move with a broad, internally consistent command language. Its bootstrap waits on passive network input, while authenticated child tasks can introduce schedules, staged programs, files, sockets, ICMP, named pipes, and VMware-oriented communication. The same interpreter underpins each route.
The receive and send sides should not be treated as one protocol. Inbound task bytes cross a shared authentication boundary before dispatch; outbound commands can transmit supplied content directly. That separation creates useful detection opportunities when passive network activity can be correlated with the native endpoint and network behavior that follows.
Proofmetry Platform
Proofmetry Platform includes a variant-specific SleepWalker workflow that reconstructs the tasking expected by this analyzed build. The workflow provides a predefined test for each of its 23 opcodes. The original malware executes the selected behavior inside a customer-controlled lab, while the customer's existing sensors capture the resulting endpoint and network activity.