Assessment. The attachment contains a self-contained delivery chain for a 32-bit Remcos implant. An obfuscated JScript drops LuaJIT and a companion Lua script. The Lua establishes a scheduled-execution path, attempts to interfere with inspection and prepares embedded Donut shellcode. The Donut instance contains a complete Remcos executable and its controller configuration. The recovered core supports remote command execution, surveillance, file and registry access, process manipulation and additional modules supplied through feature channels. Successful infection or communication with a controller was not observed during this investigation.
Visual evidence: 28 diagrams, 14 annotated disassembly excerpts and four decoding comparisons. Detection material includes four YARA rules, two experimental behavioral templates and an evidence-qualified ATT&CK mapping. Windows execution remains unvalidated.
Contents
- Executive assessment
- Evidence and analytical scope
- Delivery through JScript
- The Lua loader
- Recovering the Donut instance
- The recovered Windows executable
- Configuration and startup decisions
- Controller communication
- Command dispatch and feature channels
- Surveillance and remote interaction
- Files, registry and host control
- Browser data and recovery modules
- In-memory modules, hollowing and elevation
- Restart, update, watchdog and removal
- Forensic interpretation and detection
- Verification and remaining limits
- Appendices
- Visual evidence index
1. Executive assessment
The most useful way to understand this sample is to separate its delivery machinery from the implant it carries. The JScript and Lua layers make the payload inconvenient to inspect and arrange for it to run again. Remcos supplies the remote-control functionality. Donut sits between those layers, packaging the Windows executable for memory-based loading.
The initial Lua decoding produces 568,907 bytes of high-entropy data. That result can easily be mistaken for an unresolved encrypted payload. Its first instruction explains why that interpretation is incomplete: a relative CALL skips over a large embedded instance and reaches loader code. Following that structure exposes a Donut decryptor and, inside the decrypted instance, an intact Remcos PE.
The recovered executable identifies itself as Remcos 7.3.1 Pro. Its SETTINGS resource decrypts into 58 positional elements, including a trailing empty element. The populated configuration includes a controller at zxaswedryyhfrdswaaaaaacvbnmjgffds.ydns.eu:20901, a TLS certificate and matching private key, a separate peer certificate, a mutex name, installation names and surveillance settings.
The core is capable of substantially more than connecting and polling. Code paths support keyboard capture, clipboard access, JPEG screen capture, remote input, microphone capture, redirected command shells, file transfers, registry editing, process termination and suspension, browser-data clearing, and loading additional executable modules. The primary dispatcher has 82 active command entries. Some feature channels expose further dispatchers, while others accept a DLL from the controller and resolve its exports at runtime.
Those capabilities should not be confused with the sample's startup defaults. The embedded configuration disables Remcos's own installation route, ordinary offline keylogging, automatic screenshot branches, background microphone recording, automatic browser clearing, and watchdog startup. The Lua wrapper nevertheless attempts its own VBS launcher and a scheduled task every ten minutes. An operator can also enable features after connection, and saved registry state can override some defaults.
The finding is therefore a recovered remote-access implant with concrete persistence and control mechanisms. The evidence does not establish an affected victim, a successful TLS session, operator activity, or exfiltration. No endpoint logs, memory image or packet capture accompanied the sample.
Figure D01. Delivery, payload and persistence boundaries
The solid relationships are reconstructed from code and artifacts. The diagram is not a record of a successful Windows execution. Optional feature DLLs may be supplied later and are not all present in the submitted evidence.
2. Evidence and analytical scope
2.1 Evidence identity
The long hexadecimal basename belongs to the JScript: its SHA-256 is b036c2455322547e0624c6e824b3622d83f558e1ce3cd8be3a2751662082c16d. The ZIP itself has a different hash. Maintaining that distinction matters when sharing indicators or comparing submissions.
| Artifact | Size, bytes | SHA-256 |
|---|---|---|
| Submitted ZIP | 2,841,837 | 3ffd2ce4714a11e3f38750019e9f667613d44709bb8490240f30e6a69302b7ab |
| JScript dropper | 5,988,774 | b036c2455322547e0624c6e824b3622d83f558e1ce3cd8be3a2751662082c16d |
| Lua companion | 1,492,733 | f394756899743eb04304fbe313bd8f44a59a57d87743dad62fa6a223db311159 |
| LuaJIT host | 511,061 | d447a06cba8d3dbe80e09d73dd64576836b258961ee0d3ba56cf26cea7962b49 |
| Decoded Donut shellcode | 568,907 | 77ea7df41b60a9edeafbec70964c9964472e312f8d81c56e3f52b48f616e3578 |
| Decrypted Donut instance | 543,680 | 77594f79542a200ada3868abf28f75e51a19ebdc28c84579e9d4328b381d343a |
| Exact Remcos PE | 537,600 | 79179155729934442e02925d2a97ded40e739216352159c536c47643bfe53fda |
The decrypted-instance hash includes the counter after the decryptor has advanced it. A tool that preserves the original counter in its output can therefore produce a different instance hash while recovering identical plaintext and an identical PE. The included independent implementation reproduces the emulator's complete output, including the updated counter.
2.2 What was done
The ZIP was extracted with password infected. JScript data-processing code was evaluated with inert replacements for ActiveXObject and WScript. The replacements logged requested Windows operations and captured base64 object contents. They did not implement COM, launch processes or write to Windows persistence locations.
The Lua was inspected as source. Selected pure-data decoding functions were evaluated separately; the loader itself was not invoked. A cleaned analytical copy was syntax-checked after repeated VM blocks were removed. That copy is for review and is not a replacement malware launcher.
The Donut decryptor was emulated in isolation, starting directly at its decryption function and stopping at a sentinel return address. A separate pure Python implementation then reproduced the same bytes without executing shellcode. PE metadata, resources and configuration were parsed independently. Certificates were parsed and their signatures and key relationship checked.
The Remcos core was disassembled with Capstone and indexed for imports, calls and string references. Primary command tables and selected secondary routes were recovered from the code. One packet-header routine was emulated with synthetic bytes and local memcpy/memcmp substitutes. No controller command handler was run.
2.3 How to read the findings
A confirmed artifact is present in recovered bytes and independently parsed or reproduced. A code-backed behavior has an identified implementation or call path, but has not necessarily succeeded on Windows. An interpretation explains the likely role of that code and is bounded by the available evidence.
All Remcos addresses in this report use preferred virtual addresses with image base 0x400000. Donut addresses are offsets within the decoded shellcode. Lua identifiers are retained where they help correlate the narrative with the recovered source. Addresses are not live addresses from a victim machine.
Figure D02. Independent evidence supporting payload recovery
Two different implementations produce the same decrypted instance. The image boundary is then checked independently against the module length and the PE section table.
3. Delivery through JScript
3.1 What the outer script hides
The archive contains one JScript file. Its size is dominated by embedded content and obfuscation. Large amounts of multilingual filler, repeated functions and string-table indirection obscure a short delivery sequence.
The script also includes a recursive debugger trap. The extraction harness neutralizes that trap while preserving the decoding logic. This is an analytical modification to the harnessed copy, not a change to the submitted evidence.
The meaningful operations reconstructed through the logging interfaces are:
| Step | Requested operation | Analytical significance |
|---|---|---|
| 1 | cmd /c echo. > "<script>:Zone.Identifier" |
Attempts to overwrite the zone stream associated with the script |
| 2 | SpecialFolders("MyDocuments") |
Selects the drop directory |
| 3 | File-existence checks | Controls whether embedded companions need to be written |
| 4 | Create a .mui marker |
Supplies the condition later used by Lua persistence code |
| 5 | XMLDOM base64 decoding and ADODB.Stream writes | Materializes the Lua script and interpreter |
| 6 | Shell Run request | Starts the interpreter with the Lua companion as its argument |
The zone-stream command is sometimes described too loosely as “removing Mark of the Web.” The actual code attempts to overwrite the stream. The resulting Windows trust behavior was not tested, and the script is already executing by the time that command is issued.
Figure D03. JScript delivery branch
The instrumented extraction harness forces the missing-companion branch to expose embedded data. File writes and process launches in this figure are requested operations, not observed Windows events.
3.2 Dropped files
The reconstructed names are:
ZGNICVGKWHUBRJMP.exe: the LuaJIT host.PMJRBUHWKGVCINGZ: the Lua source and embedded payload.ZGNICVGKWHUBRJMP.mui: the marker used by subsequent logic.
The companion name is the executable basename reversed. That relationship is later reused rather than stored as an unrelated path.
The host contains LuaJIT identification material and the normal runtime interface. Its PE subsystem is console, despite the earlier supplied notes calling it a GUI executable. This analysis establishes its interpreter role and identity strings; it does not claim a byte-for-byte match to a particular official LuaJIT distribution.
The harness records a symbolic Documents path. It cannot establish a compromised username, workstation name, original delivery email, download URL or execution time. None of those facts is inferred from the local extraction directory.
4. The Lua loader
4.1 The visible complexity and the real logic
The Lua begins with a large encoded string named ZGNICV. The remainder contains many repeated implementations of a Lua 5.1 virtual machine: opcode names, prototype readers, constants and interpreter handlers.
Those blocks initially suggest that the actual malicious program must be recovered from custom VM bytecode. That is not necessary here. The script also contains direct source implementing decoding, Windows API interaction, persistence and shellcode execution. Removing 62 structurally repeated VM blocks leaves approximately 52 KB of analytical source that still passes a Lua 5.1 syntax compilation check.
The practical consequence is important: a large collection of VM-related strings is not evidence that those VMs mediate the payload's execution. The relevant execution path can be followed directly through the source.
Figure D05. Separating meaningful Lua from camouflage
The analytical reduction does not prove the entire loader works. It establishes that the directly recoverable source remains syntactically coherent after the repeated blocks are removed.
4.2 Scheduled execution
The Lua obtains its executable path, removes the extension from the basename, and derives the companion and marker paths. If the matching .mui file exists, it creates a VBS launcher if one is not already present.
For this sample the launcher is PMJRBUHWKGVCINGZ.vbs. It uses WScript.Shell to run the LuaJIT executable with the extensionless companion file and requests a hidden window.
The Lua queries for a scheduled task named ZGNICVGKWHUBRJMP. When the query indicates that the task is absent, it constructs a task with /sc minute /mo 10, a wscript.exe action, the current USERNAME, and /f /it.
This provides a recurring launch attempt. It does not prove that task registration succeeded, and its account/session options should not be described as an unconditional boot-time service. The source supports a ten-minute scheduled-task mechanism, not a confirmed victim persistence event.
Figure D04. Lua scheduled-task persistence conditions
The task is named ZGNICVGKWHUBRJMP and runs PMJRBUHWKGVCINGZ.vbs through wscript.exe. The source conditions explain the persistence attempt; task creation has not been validated on Windows.
4.3 Native patches and inspection interference
The early G routine attempts to alter functions exported by ntdll. Its target list includes ETW functions and native process/thread operations. The replacement starts with RET and fills the rest of the patch region with NOPs.
Later routines attempt a different three-byte patch, 31 C0 C3, at ETW and AMSI entry points. In x86 terms this clears EAX and returns. Targets include EtwEventWrite, EtwEventWriteFull, EtwEventRegister, NtTraceEvent, AmsiScanBuffer, AmsiOpenSession and AmsiScanString.
These are concrete patch attempts. They are not sufficient grounds to call the bypass complete or reliable. Returning a nominal success code is not equivalent to implementing a function's output parameters. Broadly patching native functions can also interfere with the loader's own dependencies. That concern is especially relevant because the later implant contains native section-mapping code. No runtime compatibility claim is made.
Other routines inspect debugger-related state, query process debug information, attempt to hide a thread, inspect or clear hardware-breakpoint registers, and introduce delays. The source also mutates global tables and allocates distracting buffers.
One routine, fr, allocates a 1 MiB buffer filled with API names such as VirtualAllocEx, WriteProcessMemory, CreateRemoteThread, QueueUserAPC and NtCreateThreadEx. Those names are intentionally manufactured data in that routine. Counting them as separately implemented injection techniques would be a false positive in the analysis.
4.4 The three decoding layers
The meaningful decode path is short enough to describe precisely:
u0Zw356QS2JNchecks for a leading|. The second character selects one of 25 substitution alphabets. Ten symbols map toA-Fanda-d; all other characters pass through. The result is reversed. This sample uses selectorN, or zero-based table index 23.mrbase64-decodes that result.m0amT8SSscq8Js9Mreads the first byte as an encoded rotation key and reverses a printable-ASCII rotation over characters 33-126. The first byte is 159, so the effective key is 31. Nonprintable bytes are preserved and the key byte is discarded.
The result is the 568,907-byte Donut shellcode identified in the evidence table. The entire remaining payload is already present; these stages do not need to fetch it from the network.
Figure D06. Payload decoding decisions
Nonprintable bytes survive the rotation step. The leading key byte is removed. The decoded shellcode hash is 77ea7df4...616e3578; full hashes appear in the evidence table.
Byte comparison B01. Lua input and decoded shellcode prefixes
Encoded ZGNICV value, offset 0x0:
7c 4e 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 77 77 36 2d 73 53 4a 34 37 29 28 34
|N&&&&&&&&&&&&&&&&&&ww6-sSJ47)(4
Decoded Donut shellcode, offset 0x0:
e8 c0 4b 08 00 c0 4b 08 00 50 11 c0 04 b1 46 91 79 19 ec 11 b5 db c5 cb 86 73 53 be 1b 39 38 0c
..K...K..P....F.y........sS..98.
These are prefixes at two different transformation stages, not an equal-length block-cipher pair.
4.5 Page protection and the exception handler
The loader requests an allocation of the decoded length plus 8,192 plus 600,000,000 bytes. That is a large virtual allocation request. It is not evidence that the same amount was resident in physical memory.
After copying the shellcode, it appends 256 NOP bytes and calls Kr. This routine obtains the system page size and prepares page-level protection and exception handling. xr transforms each page by XORing 64-bit words with an evolving mask derived from a runtime value and page index. The exception callback checks for access violations within the tracked region, changes protection, applies the matching XOR transformation and restores executable access.
The mechanism is designed to conceal the in-memory shellcode until a page is needed. It is a protection layer applied after the embedded data has been decoded. Recovering the original Donut instance did not require executing that mechanism or guessing its runtime-generated key.
A limited FFI declaration audit accepted the three literal declaration blocks under a Linux x64 LuaJIT parser. This only checks declaration syntax in that runtime. It does not validate Windows x86 calling conventions, structure layout, callback behavior or successful native patching.
Figure D07. VEH page-protection cycle
This is the protection sequence expressed by the source. It was not executed as a Windows exception-handling test. It is separate from decrypting the original embedded Donut instance.
5. Recovering the Donut instance
5.1 The instruction that changes the analysis
The decoded blob starts:
E8 C0 4B 08 00 call 0x84bc5
The CALL pushes the address immediately after the instruction and transfers execution to offset 0x84bc5. The skipped region begins at offset 5 and is 0x84bc0 bytes long. That region is the embedded instance.
At the target, the loader retrieves the instance pointer and selects its architecture path. In 32-bit interpretation, xor eax,eax; dec eax; js ... reaches the x86 loader at 0x880bb. The shellcode also contains a 64-bit loader region. The embedded executable recovered in this case is x86.
This structure explains the high entropy. Much of the blob is encrypted data inside a valid executable wrapper. A lack of an MZ header at offset zero is expected and does not imply that the analysis has reached an inaccessible final layer.
Figure D08. Donut shellcode regions and architecture selection
The recovered executable is x86. The CALL target is calculated as 5 + 0x84bc0. The data between the instruction and its target accounts for most of the high-entropy blob.
Evidence E01. The CALL that exposes the embedded instance
Offsets from the beginning of decoded shellcode. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
00000000 call 0x84bc5 ; target = next instruction + 0x84bc0; pushed return address points at instance
5.2 Locating the decryptor
The x86 loader identifies the decryption function at shellcode offset 0x8aaa9. Its call site at 0x8946d supplies:
| Argument | Instance-relative value |
|---|---|
| Key | +0x04 |
| Counter | +0x14 |
| Encrypted data | +0x23c |
| Data length | Instance length minus 0x23c |
The instance length is the first DWORD, 0x84bc0. Its encryption/entropy mode at +0x234 is 3.
The isolated emulator mapped the shellcode and a stack, supplied those arguments and started at the decryptor itself. It stopped on return. It did not start the shellcode entry point, resolve Windows APIs or invoke the embedded image.
Evidence E02. Arguments supplied to the instance decryptor
Offsets from the beginning of decoded shellcode. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
00089456 mov eax, dword ptr [edi]
00089458 sub eax, 0x23c ; exclude the unencrypted 0x23c-byte prefix
0008945d push eax ; fourth argument: encrypted data length
0008945e lea eax, [edi + 0x23c] ; third argument: encrypted data pointer
00089464 push eax
00089465 lea eax, [edi + 0x14] ; second argument: counter
00089468 push eax
00089469 lea eax, [edi + 4] ; first argument: key
0008946c push eax
0008946d call 0x8aaa9 ; call the standalone decryptor at shellcode offset 0x8aaa9
5.3 Independent cipher reconstruction
The decryptor uses a 16-byte key and a 16-byte counter. It XORs four little-endian words of counter state with the key, applies a 16-round add/rotate/XOR permutation, XORs the key again and uses the resulting 16 bytes as a keystream block. The counter is incremented from its final byte toward its first, equivalent to a 128-bit big-endian increment.
The permutation follows the Chaskey-style round structure visible in the instructions:
v0 += v1; v2 += v3
v1 = rol(v1, 5) XOR v0
v3 = rol(v3, 8) XOR v2
v0 = rol(v0, 16)
v2 += v1; v0 += v3
v1 = rol(v1, 7) XOR v2
v3 = rol(v3, 13) XOR v0
v2 = rol(v2, 16)
All additions are modulo 2^32. The sequence repeats sixteen times for each counter block. The independent Python reconstruction in verify_donut_cipher.py reproduces all 543,680 bytes of the emulator output, including the advanced counter. This is an independent check of the decryption logic, not merely another invocation of the same machine code.
Figure D09. Independent reconstruction of the Donut cipher
The increment is equivalent to a 128-bit big-endian counter. The Python reconstruction matches all 543680 bytes of emulator output, including the advanced counter.
Evidence E03. The add-rotate-XOR permutation
Offsets from the beginning of decoded shellcode. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
0008ab0d mov dword ptr [esp + 0x30], 0x10 ; round counter = 16
0008ab15 add ecx, edi
0008ab17 add eax, esi
0008ab19 rol edi, 5 ; rotation constant 5
0008ab1c xor edi, ecx
0008ab1e rol esi, 8 ; rotation constant 8
0008ab21 xor esi, eax
0008ab23 rol ecx, 0x10 ; rotation constant 16
0008ab26 add eax, edi
0008ab28 add ecx, esi
0008ab2a rol edi, 7 ; rotation constant 7
0008ab2d rol esi, 0xd ; rotation constant 13
0008ab30 xor edi, eax
0008ab32 xor esi, ecx
0008ab34 rol eax, 0x10
0008ab37 sub dword ptr [esp + 0x30], 1 ; decrement round counter
0008ab3c jne 0x8ab15 ; repeat the round
Evidence E04. Counter carry propagates from the final byte
Offsets from the beginning of decoded shellcode. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
0008ab84 mov ebx, dword ptr [esp + 0x10]
0008ab88 sub edx, ecx
0008ab8a add ebp, ecx
0008ab8c mov ecx, dword ptr [esp + 0x34]
0008ab90 add byte ptr [ecx], 1 ; increment one counter byte
0008ab93 jne 0x8ab9d ; stop carrying when the increment did not wrap
0008ab95 dec ecx ; otherwise move to the preceding byte
0008ab96 lea eax, [ebx + ecx]
0008ab99 test eax, eax
0008ab9b jg 0x8ab90 ; continue carry propagation
Byte comparison B02. Instance bytes before and after decryption
Ciphertext instance, offset 0x240:
e7 f9 dc c3 ca 29 19 ce 28 04 18 91 6b 50 d3 43 df a0 a3 77 4c 9d 6a a1 c7 36 f7 8a d9 52 23 b4
.....)..(...kP.C...wL.j..6...R#.
Decrypted instance, offset 0x240:
6f 6c 65 33 32 3b 6f 6c 65 61 75 74 33 32 3b 77 69 6e 69 6e 65 74 3b 6d 73 63 6f 72 65 65 3b 73
ole32;oleaut32;wininet;mscoree;s
Both excerpts use instance-relative offset 0x240. The decrypted bytes begin the embedded DLL-name list.
5.4 Extracting the exact PE
Decryption exposes readable instance content such as ole32;oleaut32;wininet;mscoree;shell32. The embedded-module branch uses an instance type of 1 at +0x920. The module begins at +0xd60.
Within the module, the payload length at +0x524 is 0x83400, or 537,600 bytes. The PE begins at +0x528. Extracting exactly that range produces a valid Windows executable whose final raw section ends at the same length.
Extracting all bytes from the MZ header to the end of the instance would append 1,336 unrelated bytes. That produces a plausible-looking PE with the wrong hash. The module length and the section table independently establish the correct boundary.
Figure D10. Exact executable boundary inside the instance
The container length and raw-section end are independent boundary checks. A valid-looking MZ header alone does not justify copying every remaining instance byte.
Byte comparison B03. The PE header inside the encrypted instance
Ciphertext instance, offset 0x1288:
13 0b 6f 1a 39 d4 6a ae ee 10 85 91 63 26 44 88 e1 9f cb 7c 80 aa a3 2c 86 c4 c5 51 8b fd cf 2f
..o.9.j.....c&D....|...,...Q.../
Decrypted instance, offset 0x1288:
4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00 b8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00
MZ......................@.......
0x1288 = module offset 0xd60 + PE data offset 0x528. The MZ header becomes visible after instance decryption.
6. The recovered Windows executable
6.1 Identity and layout
The final image is a PE32 x86 executable with image base 0x400000, entry RVA 0x3ebe4, GUI subsystem 2 and image size 589,824 bytes. Its recorded linker timestamp is 22 August 2026 at 13:35:27 UTC. The timestamp is attacker-controlled metadata and is not a deployment date.
The Remcos identity is supported by several strings in operational code paths, including Remcos Agent initialized (, Remcos restarted by watchdog!, Remcos v and the nearby version 7.3.1 Pro. The identification is also consistent with its configuration format and control architecture.
| Section | RVA | Virtual size | Raw offset | Raw size |
|---|---|---|---|---|
| .text | 0x1000 | 0x60697 | 0x400 | 395,264 |
| .rdata | 0x62000 | 0x1890c | 0x60c00 | 100,864 |
| .data | 0x7b000 | 0xadfc | 0x79600 | 3,584 |
| .rsrc | 0x86000 | 0x4ab0 | 0x7a400 | 19,456 |
| .reloc | 0x8b000 | 0x4298 | 0x7f000 | 17,408 |
The resources include four icon images, an icon group and the 1,186-byte SETTINGS object. There is no version-information resource supplying an independently authenticated product identity.
6.2 What the imports do and do not establish
The import table includes file/process APIs, keyboard and clipboard functions, GDI screen-capture operations, registry access, Windows audio capture, Winsock, WinINet and URL download functions. These make useful entry points for reverse engineering, but imports alone do not establish when a feature runs.
This report follows those entry points into handlers where possible. That distinction separates the evidence for a working code path from a broad family description copied from a product feature list.
7. Configuration and startup decisions
7.1 SETTINGS decryption
The substantive startup routine is 0x41426b. It records the module handle, resolves additional APIs, gets its executable path and obtains SETTINGS through 0x41fa84.
The resource format is simple:
one byte: key length
key-length bytes: RC4 key
remaining bytes: RC4 ciphertext
Here the key length is 23. The key is f6dc4c855207805cdd97676cd40fcaeb57f9e2e641ab09. The decrypting path at 0x414e65 initializes the RC4 helper and decrypts the remaining resource bytes. Splitting the plaintext on 7c 1e 1e 1f 7c gives 58 elements; element 57 is empty because of the trailing delimiter.
The configuration vector is stored at 0x47f0ac. The indexed accessor at 0x401e70 returns 24-byte string objects. The same accessor serves other vectors, including command arguments, so an immediate index is not sufficient to identify a configuration field. The object passed in ECX must be tracked as well.
Figure D11. SETTINGS resource and positional fields
The index count is 58 because the final split element is empty. Certificate and private-key fields are binary and must not be decoded as ordinary text.
Evidence E05. Locating the named configuration resource
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
0041fa84 push ebx
0041fa85 push edi
0041fa86 push 0xa ; RT_RCDATA = 10
0041fa88 push 0x476754 ; pointer to ASCII SETTINGS
0041fa8d push dword ptr [0x47ce4c] ; module handle retained by startup
0041fa93 mov ebx, ecx
0041fa95 call dword ptr [0x462178] ; FindResourceA
Byte comparison B04. SETTINGS key prefix, ciphertext and recovered endpoint
Resource key-length byte and key, offset 0x0:
17 f6 dc 4c 85 52 07 80 5c dd 97 67 6c d4 0f ca eb 57 f9 e2 e6 41 ab 09
...L.R..\..gl....W...A..
Encrypted configuration prefix, offset 0x18:
61 d1 e9 2e e9 a9 22 1f 9a cf 87 7d 79 cb 0f 65 bb 8b cd c6 de 93 f9 d1 e2 6e 49 bc 91 d8 72 36
a....."....}y..e.........nI...r6
Decrypted configuration prefix, offset 0x0:
7a 78 61 73 77 65 64 72 79 79 68 66 72 64 73 77 61 61 61 61 61 61 63 76 62 6e 6d 6a 67 66 66 64
zxaswedryyhfrdswaaaaaacvbnmjgffd
The first resource byte is 0x17, or 23. Only the remaining ciphertext is decrypted; the private TLS key is not shown in this excerpt.
7.2 Identity, saved state and paths
Field 14 supplies the mutex chrome-B1MV8Z. The call at 0x413303 uses CreateMutexA and tests GetLastError against 183, ERROR_ALREADY_EXISTS. This confirms the single-instance role.
The core constructs registry state locations under Software\ using related identifiers, with suffixed forms including -Sys. Values observed in these paths include name, group, hlight, licence, exepath, okmode, elev, SysPw and SysPwR. Some are configuration state; others support coordination with feature or helper processes. Their existence in code does not establish that they were created on a victim.
Path selector 0x413d32 subtracts ASCII 0 and selects among nine roots. Selector 8 chooses ProgramData, falling back to ProgramFiles; 6 chooses AppData; 7 chooses UserProfile; 1 uses the current executable directory. The full selector table is retained in the configuration appendix.
The sample uses Chrome.exe and chrome as installation-related names. The keylogger is configured with remcos and logs.dat as path/name components and selector 8 as its root. Because installation is disabled in the embedded defaults, an installation filename should not be reported as a file known to have been written.
Evidence E06. The mutex consumer confirms field 14
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
00413303 mov ecx, 0x47f5ec ; string object populated from configuration field 14
00413308 call 0x401f97
0041330d push eax ; lpName argument
0041330e push 1
00413310 push 0
00413312 call dword ptr [0x46228c] ; CreateMutexA
00413318 mov dword ptr [0x47ce48], eax
0041331d call dword ptr [0x462180] ; GetLastError
00413323 xor ecx, ecx
00413325 cmp eax, 0xb7 ; ERROR_ALREADY_EXISTS = 183
0041332a setne cl ; return false when another instance owns the name
0041332d mov eax, ecx
0041332f ret
7.3 Defaults that change the interpretation
| Feature or setting | Embedded value | Consequence in the traced startup path |
|---|---|---|
| Remcos installation, field 3 | raw 00 | Core installation route is skipped |
| Offline logger, field 15 | ASCII 0 | No ordinary offline mode selected unless saved okmode overrides it |
| Screenshot gates, fields 20 and 22 | raw 00 | Automatic screenshot threads are skipped |
| Background microphone, field 35 | raw 00 | Automatic recording thread is skipped |
| Browser clearing, field 43 | raw 00 | Automatic clearing path is skipped |
| Watchdog, field 50 | raw 00 | Watchdog thread is not launched by this gate |
| Repair monitor, field 51 | raw 00 | Repair/reinstallation monitor is skipped |
| Console mode, field 40 | ASCII 0 | No automatic console display through this setting |
| Initial connection delay, field 41 | ASCII 0 | No configured initial sleep |
| Retry delay, field 2 | ASCII 1 | One-second delay after the endpoint list is exhausted |
Fields 4 and 5 are enabled persistence selectors, but they do not override the separate installation gate. Field 44 is an enabled subordinate browser-clearing option, but field 43 still controls whether that startup path is entered.
The offline logger also reads a stored registry value named okmode. The code at 0x40bf07 can replace the embedded mode with that saved value before choosing a logging path. A recovered configuration therefore describes defaults, not a complete snapshot of an already-running implant's state.
Figure D12. Startup defaults versus later activation
This map does not include the Lua scheduled task, which is independent of the Remcos installation flag. A saved registry value can change the offline logger decision.
8. Controller communication
8.1 Endpoint selection and transport
The connection manager is 0x4190c4. It applies the optional initial delay, splits the configured endpoint list and parses the selected hostname, port and transport flag. It explicitly compares the third component against ASCII 0; zero chooses TLS off and any other value chooses TLS on.
The recovered record is:
zxaswedryyhfrdswaaaaaacvbnmjgffds.ydns.eu:20901:1
The 1 therefore requests TLS. This is confirmed by the comparison and the corresponding “TLS On” branch, rather than inferred from a generic TLS string elsewhere in the binary.
Socket creation at 0x404287 uses SOCK_STREAM and IPPROTO_TCP. WSAStartup requests 2.2. getaddrinfo supplies the address family and resolved endpoint; 0x404329 performs the connection and enters the TLS setup path when enabled.
On failure or disconnection the core closes the transport and advances to the next endpoint record. When it reaches the end of the list, it resets the index and sleeps for field 2 multiplied by 1,000. Only one endpoint record was recovered in this configuration.
The hostname was not resolved or contacted. Its current IP address, availability and ownership are outside the evidence.
Figure D13. Connection selection and retry control
Field 41 is zero and field 2 is one in this sample. This is a code-derived retry model; no DNS lookup, TCP connection or TLS session was performed.
Evidence E07. The endpoint flag selects TLS
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
0041925c push 2 ; third parsed endpoint component, index 2
0041925e lea ecx, [ebp - 0xc]
00419261 call 0x401e70
00419266 mov edx, 0x47075c ; pointer to ASCII 0
0041926b mov ecx, eax
0041926d call 0x405aba ; string comparison
00419272 test al, al
00419274 je 0x419284
00419276 mov byte ptr [0x47cb8a], 0 ; zero flag selects TLS off
0041927d mov eax, 0x4761b8
00419282 jmp 0x419290
00419284 mov byte ptr [0x47cb8a], 1 ; nonzero flag selects TLS on
0041928b mov eax, 0x4761b0
Evidence E08. Retry delay is seconds converted to milliseconds
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
00419d95 push 2 ; configuration field 2
00419d97 mov ecx, edi
00419d99 xor ebx, ebx
00419d9b call 0x401e70
00419da0 mov ecx, eax
00419da2 call 0x401f97
00419da7 push eax
00419da8 call 0x445737 ; atoi-like conversion of the field string
00419dad imul eax, eax, 0x3e8 ; seconds to milliseconds
00419db3 pop ecx
00419db4 push eax
00419db5 call dword ptr [0x4622c8] ; Sleep; sample value 1 requests a 1000 ms sleep
8.2 Certificate material
The configuration contains a local certificate in field 54, an EC private key in field 55 and a separate certificate in field 56. The initialization routine at 0x404197 loads certificate, key and peer-certificate material through distinct functions. Associated error strings identify failures at those stages.
Independent parsing establishes:
| Property | Field 54, local certificate | Field 56, peer certificate |
|---|---|---|
| Curve | secp256r1 | secp256r1 |
| Subject | Empty | Empty |
| Issuer | Empty | Empty |
| Self-signature | Verified | Verified |
| Matches field 55 private key | Yes | No |
| Not before, UTC | 2026-10-11 06:09:00 | 2026-10-11 06:08:59 |
| Not after, UTC | 2147-10-11 06:09:00 | 2147-10-11 06:08:59 |
| SHA-256 | 1bb977b55395b73e5abd4d29c274b6d5e16f1d5e30ec04f270e6f01c96326fb5 |
9f14d9fc276cab30a47b557db642644a49c7faf58005f1a8c192e40173212455 |
The fields are consistent, parseable TLS material, and the local certificate/key relationship is cryptographically verified. Their dates are embedded values, not independent observations of campaign activity. The presence of a peer certificate does not, by itself, prove strict certificate pinning; the complete validation policy was not reconstructed.
The private key is preserved in the encrypted evidence archive. It is not reproduced in this report.
Figure D14. TLS object relationships and verification limits
Both certificates use P-256 and empty subjects/issuers, with no DNS SAN names found. Correct key matching does not establish strict certificate pinning or a successful handshake.
8.3 Registration and application framing
After connection, the manager sends registration command 0x4b (75). The record includes the configured label, version, access level and host/user-related values. Several fields have been traced, but the complete registration schema is not presented as fully named.
The socket constructor at 0x404151 initializes DWORD 0x08ff0424. In little-endian representation this provides the three-byte marker 24 04 FF and an initial ordinary-header length of eight.
| Offset | Meaning |
|---|---|
| 0-2 | Marker 24 04 FF |
| 3 | Compression flag |
| 4-7 | Length of the transmitted body, little-endian uint32 |
| 8-11, compressed frames only | Uncompressed body length, little-endian uint32 |
| Body | Four-byte command ID followed by command data, optionally compressed |
The sender at 0x4045eb constructs the body and attempts compression for sufficiently large messages. It resolves RtlCompressBuffer dynamically and passes compression format 2, LZNT1. Failed or unsuitable compression falls back to plain framing. The inverse path uses RtlDecompressBuffer.
The receive loop at 0x40483a accumulates TCP bytes and tracks incomplete frames. 0x4048f8 extracts complete messages, decompresses them when needed, dispatches them and retains any remaining buffered data. The protocol is not dependent on recv calls returning one message at a time.
The header parser at 0x404b9e was checked independently with three synthetic inputs: a plain frame header, a compressed frame header and an invalid marker. The valid inputs returned the expected eight- and twelve-byte header lengths; the invalid marker was rejected. These checks confirm the header layout, not the behavior of every malformed frame or a working controller session.
Figure D15. Plain and compressed application-frame layouts
The graph explains the alternatives rather than byte widths. The adjacent offset table is the authoritative byte layout. The isolated parser accepted both layouts and rejected an invalid marker.
Figure D16. TCP buffering and message dispatch
The receiver handles split frames and multiple frames in one TCP buffer. Exact error/disconnect behavior depends on the caller; the diagram does not claim exhaustive malformed-input validation.
Evidence E14. Ordinary packet header versus compressed extension
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
00404be8 cmp byte ptr [esi], 0 ; test compression flag copied from the header
00404beb je 0x404c06
00404bed push 4 ; compressed frames carry four more header bytes
00404bef lea eax, [edi + 8] ; extra field begins at byte offset 8
00404bf2 push eax
00404bf3 push dword ptr [esp + 0x2c]
00404bf7 call 0x440c20
00404bfc add esp, 0xc
00404bff push 0xc ; compressed header size = 12
00404c01 pop eax
00404c02 mov byte ptr [ebx], al ; store selected header size
00404c04 jmp 0x404c08
00404c06 mov al, byte ptr [ebx]
8.4 Structured argument encoding
Many commands split their structured data on 7c 1e 1e 1f 7c. The stored reversed literal at 0x4703a0 is reconstructed by 0x4203b0 into the global separator object at 0x47f13c.
This delimiter is shared by several structured-data paths, including configuration processing. It should not be applied indiscriminately to every payload: file contents, image data and DLLs can contain arbitrary binary bytes and may use dedicated feature channels.
9. Command dispatch and feature channels
9.1 Primary dispatcher
The callback at 0x419dc8 reads the four-byte command ID, removes it from the message and prepares the remaining arguments. It subtracts one from the command value and bounds-checks the result against 0xd7, covering original IDs 1 through 216.
A byte translation table at 0x41b804 selects a target index. A DWORD table at 0x41b6b8 supplies the corresponding handler address. Eighty-two IDs choose active entries; the remaining 134 choose the default path at 0x41b689.
The number 216 is therefore a range bound, not a count of implemented features. Nor are 82 primary entries necessarily 82 independent capabilities: some start a secondary channel, some update state, and several implement closely related window or process operations.
The full primary map appears in Appendix A. The accompanying CSV preserves all 216 slots, including defaults. JSON records retain handler addresses, calls, string references and analyst labels.
Figure D17. Primary command translation table
82 is the number of active primary IDs. It is not the total number of nested commands or independent capabilities. Slot and handler inventories accompany the report.
Evidence E09. The two-stage primary command table
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
00419e2f dec esi ; convert command IDs 1..216 into indices 0..215
00419e30 pop ecx
00419e31 cmp esi, 0xd7 ; upper bound is 215, not a feature count
00419e37 ja 0x41b689 ; out-of-range IDs select the default path
00419e3d movzx eax, byte ptr [esi + 0x41b804] ; byte translation table
00419e44 xor ebx, ebx
00419e46 jmp dword ptr [eax*4 + 0x41b6b8] ; indexed DWORD target table
9.2 Secondary namespaces
Feature channels reuse the common transport while defining their own message layouts and dispatchers. A primary command can therefore open a socket, send a feature identifier and transfer subsequent control to a different callback.
The additional work in this investigation maps routes in:
- File management at
0x408fbd. - Registry operations at
0x41893d. - Camera control at
0x403e64. - Recovery coordination at
0x417afe. - Service control at
0x41e9d4. - Remote input at
0x41dc35.
The command namespace matters. Service commands use a leading byte and a small table; the screen-input callback uses ASCII operation characters. Those should not be merged with the four-byte primary IDs into one ambiguous command list.
9.3 Modules supplied after connection
The executable includes a manual PE loader and several module adapters. Camera, chat, tunnelling, recovery, service and startup-enumeration paths resolve named exports from received DLLs.
That design explains why a core executable can expose a feature without containing its full implementation. The adapter proves how the core expects to call the module. It does not recover a DLL that is absent from the submitted evidence. This distinction is particularly important for credential and cookie recovery.
Figure D18. Feature channels and shared module loader
These adapters establish module interfaces. They do not supply the internals of DLLs absent from the original evidence. The transport and loader are shared across several features.
10. Surveillance and remote interaction
10.1 Keyboard capture
The keyboard implementation can be followed from dispatch to Windows APIs. Primary commands 19 and 20 reach the online start/stop routines at 0x40cd00 and 0x40ce56. Commands 204 and 205 configure or disable offline mode and persist okmode.
The hook setup at 0x40c0b9 calls SetWindowsHookExA with hook ID 13, WH_KEYBOARD_LL, and callback 0x40c0a5. Processing reaches 0x40c16c, which separates key events and routes them to ordinary or special-key logic. ToUnicodeEx in 0x40c1e5 translates key information using keyboard-layout state.
Separate routines record modifiers and readable tokens including [Enter], [BckSp], [Ctrl+V], [Alt] and function-key names. The foreground-window monitor at 0x40cae3 polls window text and adds application context. Clipboard retrieval is integrated into relevant shortcut handling.
The result is not just a stream of scan codes. It is an attempt to create readable, contextual records of user input. Such records could contain passwords or other sensitive text if capture is enabled and the user types it, but this investigation does not contain captured victim input.
The offline writer at 0x40c51d handles buffered data and uses the RC4 helper family in stored-data paths. The limit is enforced by 0x40c46c, which compares GetFileSize against the configured threshold. Field 7 is multiplied by 1,000, making this sample's limit 100,000,000 bytes. It is a file-size setting, not a timing delay.
Online logging opens a separate channel, sends identifying command 176 and transmits buffered key data using command 90. Primary commands 21-23 read, decode or remove stored logs. The embedded mode is zero, but saved registry state or subsequent commands can change that state.
Figure D19. Keyboard capture, context and output paths
Hook setup is at 0x40c0b9; offline size checking is at 0x40c46c. Embedded mode zero does not automatically start ordinary offline logging, but saved okmode can override it.
Evidence E10. Installing the low-level keyboard hook
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
0040c0d4 push edi
0040c0d5 push edi
0040c0d6 call dword ptr [0x4620a4] ; GetModuleHandleA
0040c0dc push eax
0040c0dd push 0x40c0a5 ; hook callback address
0040c0e2 push 0xd ; WH_KEYBOARD_LL = 13
0040c0e4 call dword ptr [0x462358] ; SetWindowsHookExA
0040c0ea mov dword ptr [esi], eax ; retain returned hook handle
10.2 Screen capture and control
Primary command 16 initializes capture through 0x41cd49 and 0x41cf69. The bitmap acquisition routine at 0x41d480 creates display and compatible device contexts, creates a bitmap, performs StretchBlt/BitBlt operations and retrieves pixels with GetDIBits. Cursor-related routines can add cursor information.
The frame-encoding path at 0x41d0bd selects image/jpeg through GDI+ and streams. Background capture paths use timestamped names such as wnd_YYYYMMDD_HHMMSS and time_YYYYMMDD_HHMMSS, and can write transformed image data to disk.
Remote-input callback 0x41dc35 reads a leading ASCII operation character. It routes screen-update requests, keyboard input, coordinate-bearing mouse operations and wheel events to the appropriate helpers. The wheel paths explicitly pass ±120 with mouse-event flag 0x800. Other input helpers use SendInput.
The implant can therefore interact with the desktop as well as observe it. The source also contains an attempt to alter PromptOnSecureDesktop under the Windows system-policy key. That is an attempted privileged policy change; its presence does not establish that the required access was available.
Figure D20. Screen observation and remote input
Frame acquisition and input injection are separate paths. The diagram shows how they support remote interaction, not an observed interactive session.
10.3 Audio capture and playback
Primary command 29 reaches 0x40167a, which opens and starts a wave-input device. Buffer setup uses waveInPrepareHeader and waveInAddBuffer; the stop/release path is selected by command 30. Background recording at 0x40181b is separately gated by field 35, which is off in this configuration.
Media playback is another feature. The helper at 0x41f18a uses mciSendStringW with open, play, pause, resume, status, stop and close requests. The alarm.wav string belongs to this workflow. It is not evidence that microphone recordings were collected or that an alarm was played on a victim.
10.4 Camera access
Primary command 27 establishes the camera feature channel and command 28 closes it. The secondary adapter receives a DLL and resolves OpenCamera, CloseCamera, GetFrame and FreeFrame.
Within that channel, command 60 loads the adapter module; 62 closes the camera; 63 opens it and continues toward frame acquisition; 64 opens it and enters a capture path with a delay. Command 61 reaches frame acquisition only while the camera-open state is set. The export names and call sites establish the interface, not the behavior of a missing camera DLL on a particular device.
11. Files, registry and host control
11.1 Command execution
Primary command 14 reaches the shell implementation at 0x4056da. It creates pipes, launches cmd.exe with CreateProcessA and handles redirected input/output using PeekNamedPipe, ReadFile and WriteFile. This gives the operator a command-execution path in the implant's current security context.
Commands 13 and 15 provide ShellExecute-based launch routes. The current privilege level remains important: a remote shell capability does not itself imply administrator or SYSTEM access.
11.2 File management and transfers
Primary command 152 establishes a file-management channel through 0x408de5, with callback 0x408fbd. The recovered secondary routes cover drive enumeration, directory browsing, execution, creation, renaming, deletion, wallpaper changes, attributes, archive operations and transfers.
Representative routes include:
| Secondary ID | Handler | Observed role |
|---|---|---|
| 135 | 0x40917f | Enumerate logical drives |
| 136 | 0x4090ec | Browse directory contents |
| 137 | 0x40906d | Execute/open a selected file |
| 138 | 0x40926c | File-transfer helper; logs uploaded-file results through its callee |
| 140 | 0x40968d | Delete selected file/directory paths |
| 141 | 0x4094a7 | Rename selected path |
| 142 | 0x4092f8 | Create a directory |
| 145 | 0x4097dc | Set wallpaper through the file view |
| 194 / 195 | 0x409896 / 0x40984d | ZIP-module-backed archive operations |
| 199 | 0x409bab | Set file attributes |
| 212 | 0x409c46 | Receive a file through the dedicated transfer receiver |
| 218 / 219 | 0x409c26 / 0x409c06 | File-operation helpers; move/copy semantics not fully named here |
The receiver at 0x408935 creates an output file and receives data on a dedicated socket. Error handling reports failures to create or receive the file. The upload-oriented helper 0x40a2c9 logs “Uploaded file” and “Failed to upload file” outcomes. Those labels are retained as the application's perspective rather than used to guess an unobserved traffic direction.
Primary command 143 starts a separate search/enumeration path; command 144 cancels it. Command 178 coordinates an additional download/write/execute workflow. These routes expand the operator's ability to obtain, replace or stage files after the initial implant is present.
Figure D21. File-management channel and transfer feedback
The primary channel is a bootstrap. Secondary IDs 135, 136, 137, 140, 141, 142 and 212 map to drives, browsing, execution, deletion, renaming, directory creation and receiving a file respectively.
11.3 Registry and startup entries
Primary command 47 establishes the registry-management channel. Its secondary dispatcher at 0x41893d handles IDs 53-57:
| Secondary ID | Observed role |
|---|---|
| 53 | Enumerate a registry location through the shared helper |
| 54 | Delete a registry value through the root-resolution/value-delete path |
| 55 | Delete a key using SHDeleteKeyW loaded from Shlwapi.dll |
| 56 | Write a value through the registry write helper |
| 57 | Create a key with RegCreateKeyExW |
The code refreshes views and reports results around these operations. Root resolution and value-type handling are separate helpers. Startup enumeration is modular, while primary commands 215 and 216 modify startup/registry entries and request updated data.
11.4 Process, window and power control
Process enumeration uses CreateToolhelp32Snapshot, Process32FirstW and Process32NextW through 0x4152e7. Command 7 reaches OpenProcess and TerminateProcess. Commands 200 and 201 resolve to NtSuspendProcess and NtResumeProcess through their stored API pointers.
Window commands enumerate, minimize, maximize, restore, hide or rename windows, and can terminate the process owning a selected window. Clipboard commands read text, write content or clear the clipboard. These are implemented control paths, not merely string indicators.
Power-control command 39 attempts to enable SeShutdownPrivilege, then selects ExitWindowsEx or a dynamically resolved SetSuspendState path. The code supports logoff, shutdown/restart and suspend/hibernate-style requests, subject to normal privilege and platform constraints.
11.5 Host discovery and external lookups
The core collects Windows version/build and architecture, user/access level, processes, windows and installed-software information. Command 206 reaches a feature that uses GetExtendedTcpTable and GetExtendedUdpTable, including IPv4/IPv6 variants, to enumerate network connections.
Command 151 starts dxdiag, reads a temporary sysinfo.txt and deletes it. Command 17 retrieves an IP-geolocation service URL:
hxxps://pro.ip-api[.]com/line/?key=QPVvv1rHQJD2pd2&fields=25948155
That embedded URL is a separate service, not the configured controller. It was not requested during analysis. Its presence is not evidence of ownership or abuse of the service by the sample's operator.
12. Browser data and recovery modules
12.1 Clearing browser state
The browser-clearing path is a concrete built-in capability. Primary command 24 starts 0x41230a, which launches worker 0x41201a. The worker processes Chromium-family Login Data/StoredLogins and Cookies/Network\Cookies paths, along with Firefox profiles containing cookies.sqlite, logins.json and key3.db.
The covered path families include Chrome, Edge, Brave, Yandex, Opera, Opera GX and Firefox. Firefox-specific routines call DeleteFileW directly. Chromium routines traverse the corresponding profile/data paths through their helper chain. A completion message states “Cleared browsers logins and cookies.”
The code supports an attempt to remove saved login and session data. It does not show that this clearing routine exfiltrates credentials. A hypothesis that clearing forces the user to reauthenticate would require additional evidence about operator intent and execution.
The automatic startup path is disabled by field 43. Field 44 is subordinate to that gate, and field 45 supplies a repeat interval converted from minutes. The feature remains available to an operator-issued command.
12.2 Recovery through received code
Cookie recovery is a separate workflow. Primary command 198 reaches 0x406fa6; its module setup at 0x40727c resolves RecoverCookies and FreeData. Related orchestration includes CooLib staging, browser executable discovery and a werfault.exe helper path.
Other adapters resolve recovery_run_str and recovery_free_str. Recovery coordination uses stored values such as SysPw and SysPwR. The orchestration and module interface are present in the core; the complete received DLL implementations are not established as present in the submitted evidence.
This is the boundary of the current recovery analysis. The core tells us how recovery is requested and consumed. It does not justify inventing a browser-password decryption algorithm for a DLL that has not been recovered.
Figure D22. Browser clearing versus module-based recovery
The built-in clearing worker is not evidence of exfiltration. Module interfaces indicate recovery orchestration, while the missing DLL implementation remains a separate analytical target.
13. In-memory modules, hollowing and elevation
13.1 Manual module loading
Primary commands 43 and 44 accept executable material and reach 0x416d6d / 0x416d79. The loader checks MZ/PE signatures and machine 0x14c, validates the image layout, allocates space, handles sections, relocations and imports, sets protections and performs initialization. Export lookup is at 0x416fed; cleanup is at 0x417122.
Adapters use that loader to obtain feature exports:
| Feature | Expected exports |
|---|---|
| Camera | OpenCamera, CloseCamera, GetFrame, FreeFrame |
| Chat | DisplayMessage, GetMessage, FreeMessage, CloseChat |
| Tunnelling | StartForward, StartReverse, StopForward, StopReverse, GetDirectListeningPort |
| Services | GetSvc, StartSvc, StopSvc, PauseSvc, ResumeSvc, RestartSvc, SetSvcStartup |
| Startup inventory | GetStartups |
| Recovery | recovery_run_str, recovery_free_str |
| Cookie recovery | RecoverCookies, FreeData |
| Miscellaneous control | FunFunc |
The service adapter provides a useful example of the distinction between interface and implementation. Its channel dispatches eight leading-byte operations: module setup, enumeration, start, stop, pause, resume, restart and startup-mode changes. The core resolves the required exports and then calls the stored pointers. The service-management DLL itself is a separate analytical target when available.
Figure D23. Manual PE loader control flow
The loader is at 0x416d79, export resolution at 0x416fed and cleanup at 0x417122. The presence of the loader does not mean every future module has been recovered.
Evidence E11. A PE loader, rather than only suggestive strings
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
00416da0 mov eax, 0x5a4d ; MZ signature
00416da5 cmp word ptr [ebp], ax ; read signature from incoming bytes
00416da9 jne 0x416fc4
00416daf mov edx, dword ptr [ebp + 0x3c]
00416db2 mov ecx, ebx
00416db4 add edx, 0xf8
00416dba call 0x416819
00416dbf test eax, eax
00416dc1 je 0x416fcf
00416dc7 mov ebx, dword ptr [ebp + 0x3c]
00416dca add ebx, ebp
00416dcc cmp dword ptr [ebx], 0x4550 ; PE signature at the parsed header
00416dd2 jne 0x416fc4
00416dd8 mov eax, 0x14c ; IMAGE_FILE_MACHINE_I386 = 0x14c
00416ddd cmp word ptr [ebx + 4], ax ; reject the wrong machine type
00416de1 jne 0x416fc4
13.2 Process hollowing
A separate native implementation exists at 0x41c6f4, reached through helper 0x412b3b. Unlike the Lua's manufactured API-name buffers, this routine contains an actual process-replacement sequence.
It resolves ZwCreateSection, ZwMapViewOfSection, ZwUnmapViewOfSection and ZwClose. It checks the incoming image, creates a process suspended, obtains the initial thread context and reads the remote image-base location. It then creates/maps a section, copies headers and sections, applies supported relocation fixups, updates the image-base pointer with WriteProcessMemory, adjusts the thread context and calls ResumeThread.
Failure handling includes unmapping/freeing state and terminating the newly created process. The implementation is therefore more specific than a generic “injection” label: it is a section-backed hollowing/replacement path. No instance of that path was executed during this investigation.
Figure D24. Section-backed process hollowing path
Based on routine 0x41c6f4 and its failure paths. This is genuine implementation evidence in the Remcos core, distinct from API names planted as decoys in the Lua source.
Evidence E12. Creating the replacement process suspended
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
0041c80b push 4 ; dwCreationFlags = CREATE_SUSPENDED, 4
0041c80d push ebx
0041c80e push ebx
0041c80f push ebx
0041c810 push dword ptr [esp + 0x4c]
0041c814 push dword ptr [esp + 0x54]
0041c818 call dword ptr [0x462144] ; CreateProcessW; actual process-creation call in hollowing routine
0041c81e test eax, eax
Evidence E13. Redirecting and resuming the initial thread
Preferred image virtual addresses, with image base 0x400000. Comments annotate the selected instructions; this is a local excerpt rather than a full function listing.
0041ca0e mov eax, dword ptr [esi + 0x28] ; incoming image entry RVA
0041ca11 add eax, ecx ; combine entry RVA with the mapped image base
0041ca13 push ebp
0041ca14 mov dword ptr [ebp + 0xb0], eax ; update initial thread context
0041ca1a push dword ptr [edi + 4]
0041ca1d call dword ptr [0x462148] ; SetThreadContext; install the modified context
0041ca23 test eax, eax
0041ca25 je 0x41ca43
0041ca27 push dword ptr [edi + 4]
0041ca2a call dword ptr [0x462138] ; ResumeThread; resume the primary thread
0041ca30 cmp eax, -1
0041ca33 je 0x41ca43 ; failure path if resume returns -1
13.3 Elevation-related code
The core contains an elevation moniker at 0x408493, combining Elevation:Administrator!new: with CLSID {3E5FC7F9-9A51-4367-9063-A120244FBEC7} and passing it to CoGetObject. Other helpers manipulate current-process identity/PEB-related information and use an elev registry flag for relaunch coordination.
These establish an elevation-related attempt. They do not demonstrate that the technique succeeds on any given Windows release, security configuration or user account. The report deliberately stops short of describing the sample as invariably elevated.
14. Restart, update, watchdog and removal
The core supports its own lifecycle operations in addition to the Lua's recurring task.
- Command 33 exits through
0x415501. - Command 34 invokes uninstall helper
0x413330, which removes persistence state and builds a script to retry deletion of the current file/directory. - Command 35 restarts through a generated VBS helper.
- Command 36 uses URLDownloadToFileW and update helper
0x4136eeto replace the current image and launch the replacement through a script. - Primary command 37 opens a secondary update-related channel; its callback can accept replacement data and pass it into the same update lifecycle.
The watchdog thread at 0x4171a8 uses PID/mutex state and launch helpers, with messages for watchdog activation, failure and restart. A separate repair/reinstallation monitor exists at 0x417804. Both are gated off in the embedded defaults.
Uninstall behavior should not be treated as proof that all artifacts would disappear. The core and the outer Lua establish different mechanisms. Removing the core's Run-key state does not, on the evidence available, prove removal of the Lua wrapper's scheduled task or its dropped interpreter and script.
Figure D25. Core lifecycle operations and wrapper boundary
The core uninstall helper is not proof that the outer Lua task or all dropped files are removed. The two persistence layers must be assessed independently.
15. Forensic interpretation and detection
15.1 What to look for in an affected environment
The strongest evidence is a combination of content identity, execution relationships and configuration-specific artifacts. A lone generic filename such as Chrome.exe or logs.dat is weak evidence on its own.
| Observable | Expected relationship | Confidence and qualification |
|---|---|---|
| JScript hash | Original delivery script | Exact sample identity |
| LuaJIT and Lua hashes | Dropped interpreter and companion | Exact recovered objects |
Task ZGNICVGKWHUBRJMP |
Ten-minute task pointing to the VBS launcher | Concrete source-defined attempt; creation not observed |
PMJRBUHWKGVCINGZ.vbs |
Starts the interpreter and Lua companion | Source-defined launcher |
Mutex chrome-B1MV8Z |
Single-instance test | Confirmed consumer in core |
Related Software\ registry state |
Client state, saved mode and helper coordination | Exact full path/root depends on the traced branch |
| Controller hostname and TCP port 20901 | TLS-enabled configured endpoint | Configuration only; no resolution or connection observed |
| Certificate fingerprints | Local/peer certificate material | Exact recovered DER objects |
Packet marker 24 04 FF |
Application framing | Normally inside TLS for this configuration |
| ProgramData-related remcos/logs.dat components | Offline log path construction | Logger disabled by default; saved state can override |
Content hashes will change if an operator rebuilds or repackages the sample. The command framing and module interfaces are broader indicators, but they need context. The packet marker alone is not sufficient for attribution, and a network sensor cannot assume it sees plaintext application framing inside TLS.
15.2 Interpreting possible telemetry
If the chain ran, useful corroboration would include script-host execution, creation of the interpreter and companion, task-registration activity, VBS execution, LuaJIT launching with the extensionless argument, DNS resolution and outbound TCP/TLS communication. Memory inspection could reveal the decoded shellcode, the loaded Remcos image, or received modules depending on the capture point.
Those are evidence to seek, not events reconstructed from this file alone. There is no basis here to assign a real compromised account, establish dwell time, count successful commands or identify stolen data.
15.3 Attribution boundaries
Remcos identification is strong. Operator attribution is not. The group label GP, licence string, dynamic-DNS hostname, certificate dates and selected filenames are useful pivots, but none identifies a human operator by itself. The hostname's provider should not be confused with the operator or assumed to endorse the activity.
No infrastructure enrichment was performed. Public MITRE documentation was consulted for technique names only. This report does not claim a malware-distribution URL, associated campaign, historical IP address or relationship to another investigation merely because similar tooling is involved.
15.4 File rules and measured coverage
Four YARA rules accompany this report in detections/remcos_layers.yar. The rules deliberately separate a candidate Remcos string cluster from exact instance and wrapper indicators. A rule name is a statement of scope, not a claim of family-wide sensitivity.
| Rule | Basis | Intended scope | Observed positive |
|---|---|---|---|
Remcos_Core_String_Cluster |
Valid PE structure, size limit and at least three of four meaningful core strings | Family-oriented candidate; related variants untested | Recovered Remcos PE |
Remcos_Settings_Instance_79179155 |
SETTINGS key prefix, identity strings and PE header | This configuration instance | Recovered Remcos PE |
Lua_Loader_ZGNICV_Combination |
Decoder identifiers, exception/protection API names and embedded-data variable | This Lua wrapper | Original recovered Lua |
JScript_Dropper_ZGNICVGK_Combination |
Drop-name prefix and ActiveX, WScript and file-save strings | This outer script | Original JScript |
All four rules compile under yara-python 4.5.4. The core PE matches two rules, the Lua matches its wrapper rule and the JScript matches its delivery rule. The LuaJIT host matches none, which is a necessary negative control: a legitimate interpreter is not malicious merely because this delivery chain ships it. The decoded shellcode also matches none. These are file-layer rules, with an explicit gap at that intermediate stage.
The negative-control corpus contains 526 distinct local installed files, deduplicated by SHA-256: 16 Windows PE files, 150 ELF files, 180 Python files and 180 JavaScript files. There were zero observed matches. The full path, size, category and hash manifest is retained in detections/yara_test_results.json. These files are assumed benign from their installed runtime/system provenance; there is no independent clean-file attestation. The corpus is small and strongly biased toward this analysis environment, particularly the Windows PE subset. Zero observed matches is not an estimated production false-positive rate.
No related malware variants were available or tested. The candidate core rule therefore needs independent related-variant tests before it can be described as a reliable family detector. Wrapper names, the resource key and the embedded host can change. Next-stage validation should use a separate clean Windows corpus, unrelated malware, multiple independently acquired Remcos versions and differently wrapped samples, recording hashes and expected results before changing the signatures. Keep evaluation samples separate from samples used to tune the rules.
Figure D28. Detection testing and coverage boundaries
The accompanying test results separate actual sample/benign tests from missing related-variant and Windows telemetry coverage. Synthetic fixtures validate predicate logic, not a deployed SIEM integration.
15.5 Behavioral logic and correlation
Two experimental Sigma-format process-creation templates accompany the file signatures. rapid_vbs_task.yml looks for schtasks.exe with /create, /sc minute, /mo 10 and wscript.exe in the command line. This translates the source's ten-minute VBS task into a triage predicate. It does not establish that a task was created successfully. A legitimate administrator can produce the same command shape.
sample_interpreter_launch.yml requires the recovered host hash, an image path under Documents and the Lua companion identifier in the command line. Requiring the conjunction avoids treating the host hash as malicious on its own. It is intentionally narrow: missing image hashes, renamed companions and execution outside Documents create missed detections. A legitimate analyst reproducing the sample-shaped launch can also match it.
The YAML parses, and a small reference evaluator passes nine synthetic predicate fixtures. One deliberately benign administration fixture matches the task predicate, documenting a collision instead of hiding it. Other fixtures demonstrate the missing-hash and alternate-directory gaps. Neither template has been compiled through a Sigma backend or tested against actual Windows event logs. The reference evaluator supports only the modifiers used here; it is not a replacement for a Sigma implementation. Results are retained in detections/behavior_test_results.json.
For operational correlation, join the candidate interpreter launch and task command to file-creation records for the companion and VBS launcher on the same host and user. Use process ancestry or a process GUID when available; apply a bounded time window and corroborate the registered task action with task telemetry. A ten-minute grouping window is a starting hypothesis to tune against real data, not a measured property of this execution. Increase confidence when several independent artifacts agree. Compare the task against authorized automation, software inventories and the analyst's own activity before escalation. The logic requires process command lines and, for the narrow interpreter rule, SHA-256 image hashes.
The configured hostname and port are useful retrospective pivots, but no connection was observed. TLS encrypts the application framing during the configured transport path. The 24 04 ff application marker should therefore not be promoted into a plaintext network signature for ordinary encrypted traffic. A sensor would need decrypted traffic or endpoint instrumentation to inspect that application header.
Figure D27. Behavioral correlation for a defender
This is a proposed correlation model, not telemetry observed in this case. A single interpreter, VBS script or scheduled task is not enough to identify this infection.
15.6 ATT&CK mapping with evidence boundaries
The following mappings use the official technique pages linked in the table, consulted on 23 October 2026. They identify implementations or attempted behavior in the recovered code. They do not turn static capabilities into observed victim activity, and tactic assignments are intentionally omitted.
| Technique | Sample-specific evidence | Evidential status |
|---|---|---|
| T1053.005, Scheduled Task | Lua builds task ZGNICVGKWHUBRJMP with a ten-minute VBS action, section 4.2 |
Source-level persistence attempt; task registration unobserved |
| T1140, Deobfuscate/Decode Files or Information | Lua decoding, Donut decryptor at shellcode offset 0x8aaa9, SETTINGS consumer 0x41fa84 |
Decoded artifacts and selected decryption independently reproduced |
| T1620, Reflective Code Loading | Manual PE loader 0x416d79, export resolver 0x416fed |
Recovered core implementation; optional DLL execution unobserved |
| T1055.012, Process Hollowing | 0x41c6f4 creates a suspended process, maps image sections and changes thread state |
Recovered core implementation; successful hollowing unobserved |
| T1056.001, Keylogging | Hook setup 0x40c0b9, callback 0x40c0a5, WH_KEYBOARD_LL and ToUnicodeEx |
Recovered capability; ordinary offline startup mode disabled unless overridden |
| T1113, Screen Capture | Acquisition 0x41d480, JPEG path 0x41d0bd |
Recovered capability; automatic screenshot defaults disabled |
| T1123, Audio Capture | Microphone commands 29/30 and waveIn path |
Recovered capability; background recording default disabled |
| T1115, Clipboard Data | Primary command 40 opens the clipboard and returns its text, section 9 and Appendix A | Recovered command path; collection unobserved |
15.7 Windows validation still required
The figures explain code-derived behavior and isolated tests. They cannot resolve whether all stages cooperate under Windows. A full execution test was not performed, and the environment used here did not provide a Windows guest. In particular, the possible incompatibilities in the broad native patches remain material.
| Integration question | Evidence needed in an isolated Windows test | Present status |
|---|---|---|
| Do the native patches permit continued execution? | Original and patched bytes, target export resolution, page-protection results and the first failing instruction if a fault occurs | Unvalidated |
| Does persistence register and launch correctly? | Requested command, exit status, registered task action/principal, launcher contents and a subsequent task-triggered process event | Source recovered; runtime unvalidated |
| Does page protection and the VEH cooperate? | Handler registration, fault address, exception code, protection transition and resumed instruction pointer | Source recovered; runtime unvalidated |
| Does the implant initialize after the wrapper? | Entry transfer, module initialization, mutex creation and connection-attempt telemetry | PE recovered; full-chain runtime unvalidated |
| Are the detection templates useful in practice? | Captured process/task/file events, backend-converted queries and approved-administration controls | Synthetic predicate fixtures only |
A future test should preserve the original hash and use a disposable Windows guest with controlled connectivity and no access to the real controller. Separate evidence from the unmodified chain from any later run that changes a patch or configuration. Capture the failure first; a modified run cannot establish that the submitted sample works unchanged. Record the Windows build, architecture, launch context and observation settings with the resulting traces. Until those observations exist, the report supports payload recovery and substantial implementation analysis, with the execution outcome unresolved.
16. Verification and remaining limits
The central extraction result is supported by several independent checks rather than one successful-looking decode:
| Check | Result | What it establishes |
|---|---|---|
| Embedded-object capture | Reproduced Lua and host hashes | Outer decoding yields stable objects |
| Lua analytical copy | Syntax compilation succeeds after removing 62 repeated blocks | The selected source remains syntactically coherent |
| Donut machine-code decryptor | Isolated emulation completes | The identified routine decrypts the instance |
| Independent Python cipher | All 543,680 bytes match emulator output | Cipher reconstruction independently reproduces decryption |
| Exact PE boundary | Module length equals raw-section end | Final executable is not padded with instance residue |
| SETTINGS | RC4 decryption yields coherent positional data | Configuration recovery is structurally consistent |
| Certificates | Both self-signatures valid; local key matches field 54 | Embedded TLS objects are internally consistent |
| Primary dispatcher | 82 active / 134 default entries | Command-table counts follow the binary |
| Header routine | Plain/compressed accepted; invalid marker rejected | Header fields and lengths match the code |
| Secondary routes | Comparison/table routes extracted without running handlers | Additional namespaces are documented |
The full Windows chain was not launched. No controller or sample-referenced geolocation service was contacted. Public MITRE documentation was consulted only to name the techniques in section 15.6. No feature command was issued. No successful persistence, elevation, capture, hollowing or exfiltration event is claimed.
Several areas remain bounded: the full certificate-validation policy, every registration field, every secondary command's detailed argument schema, some configuration semantics and the internals of controller-supplied modules absent from the evidence. Compiler/runtime and TLS-library functions have not all been assigned semantic names. Candidate function boundaries in the generated disassembly can include internal targets and should be checked before using them as a formal call graph.
The report nevertheless reaches the core result decisively: the submitted script carries a recoverable Remcos executable, complete controller configuration and extensive remote-control code. The remaining uncertainty concerns runtime success and specific optional implementations, not the identity of the payload or the recovery of its bytes.
Figure D26. What each verification layer actually establishes
The evidence establishes extraction and selected code behavior. It does not establish that native patches, exception handling, task creation and implant startup work together on Windows.
Appendix A. Complete primary command map
These are active entries in this recovered primary dispatcher, not a universal Remcos protocol specification. Handler addresses are preferred VAs. Some labels describe an adapter or workflow because the implementing module is supplied separately. The 134 remaining IDs in 1-216 choose the default path at 0x41b689; command_map.csv preserves every slot.
| ID | Handler VA | Observed behavior |
|---|---|---|
| 1 | 0x419e4d | Status/keepalive reply and connection timing controls |
| 2 | 0x41a024 | Connection timeout/control reset |
| 3 | 0x41b12d | Enumerate installed software |
| 6 | 0x41a269 | Enumerate processes |
| 7 | 0x41a2c7 | Terminate process |
| 8 | 0x41a3bc | Enumerate windows |
| 9 | 0x41a336 | Minimize window (CloseWindow) |
| 10 | 0x41a35b | Maximize window |
| 11 | 0x41a382 | Restore window |
| 12 | 0x41a38e | Terminate process owning window, then refresh windows |
| 13 | 0x41a3fb | Launch cmd.exe through ShellExecute |
| 14 | 0x41a455 | Remote command shell with pipes |
| 15 | 0x41a499 | Open/execute supplied path through ShellExecuteW |
| 16 | 0x41a4c2 | Screen capture / remote desktop session |
| 17 | 0x419fd2 | Fetch IP-geolocation service output |
| 18 | 0x41a5dd | Keylogger configuration/status query |
| 19 | 0x41a604 | Start online keylogger |
| 20 | 0x41a613 | Stop online keylogger |
| 21 | 0x41a734 | Read keylogger logs |
| 22 | 0x41a759 | Retrieve/decode selected keylogger logs |
| 23 | 0x41a7a4 | Delete keylogger logs and refresh log view |
| 24 | 0x41b238 | Clear browser logins and cookies |
| 27 | 0x41a7d6 | Camera plugin channel |
| 28 | 0x41a88a | Stop camera channel and close plugin camera |
| 29 | 0x41a8a5 | Start microphone capture |
| 30 | 0x41a927 | Stop microphone object / recording |
| 31 | 0x41a931 | Password-recovery plugin channel and system-password helper |
| 32 | 0x41a996 | Delete supplied file |
| 33 | 0x41a9b4 | Exit Remcos |
| 34 | 0x41a9c6 | Uninstall and remove persistence |
| 35 | 0x41b63f | Restart Remcos through a VBS helper |
| 36 | 0x41a9e0 | Download replacement executable and update |
| 37 | 0x41aa66 | Secondary update channel; replacement-data callback reaches update helper |
| 38 | 0x41aac7 | Display message box |
| 39 | 0x41ad75 | Log off / shutdown / restart / suspend / hibernate controls |
| 40 | 0x41af1c | Read clipboard text |
| 41 | 0x41ae82 | Set clipboard content |
| 42 | 0x41af01 | Clear clipboard |
| 43 | 0x41af74 | Load PE module from received bytes in memory |
| 44 | 0x41afa8 | Decode supplied binary then load PE module in memory |
| 45 | 0x41aff9 | FunFunc plugin channel |
| 46 | 0x41b003 | Receive staged file and execute through ShellExecuteExA |
| 47 | 0x41b025 | Registry editor channel |
| 48 | 0x41b047 | Chat plugin channel |
| 49 | 0x41b069 | Change stored client display name |
| 50 | 0x41b10b | Forward/reverse tunnel plugin channel |
| 52 | 0x41b157 | Windows services plugin channel |
| 56 | 0x41b2a7 | Registry value write helper |
| 104 | 0x41ab53 | Startup enumeration plugin |
| 143 | 0x41a04f | File search/enumeration with command arguments |
| 144 | 0x41a15a | Cancel file search |
| 146 | 0x41a17f | Set desktop wallpaper |
| 148 | 0x41a3c6 | Set window title |
| 149 | 0x41b179 | System information channel |
| 151 | 0x41b1c0 | Generate dxdiag system report and return it |
| 152 | 0x41a02d | File manager / file transfer channel |
| 153 | 0x41a7b4 | Screenshot archive retrieval channel |
| 154 | 0x41a974 | Additional recovery-plugin channel |
| 158 | 0x41b5ce | Start/play audio-file path |
| 159 | 0x41b5d8 | Request audio-file workflow |
| 160 | 0x41b5e2 | Disable deferred audio-file flag |
| 162 | 0x41b59c | Configure audio-file input and optional immediate playback |
| 163 | 0x41b1e2 | Audio-control secondary channel |
| 171 | 0x41b5ec | Set elevation request flag for relaunch |
| 172 | 0x41b1ec | Show/create Remcos console |
| 173 | 0x41a386 | Show window normally |
| 174 | 0x41a38a | Hide window |
| 178 | 0x41a1f8 | Download/write/execute workflow on a secondary channel |
| 197 | 0x41b0c7 | Set stored highlight value |
| 198 | 0x41b646 | Cookie-recovery plugin workflow (RecoverCookies / FreeData) |
| 200 | 0x41a2ec | Suspend process via NtSuspendProcess |
| 201 | 0x41a311 | Resume process via NtResumeProcess |
| 202 | 0x41b42d | Read file and return binary data |
| 203 | 0x41b4d9 | Write supplied file and return file data |
| 204 | 0x41a622 | Enable/configure offline keylogger and persist okmode |
| 205 | 0x41a700 | Disable offline keylogger and persist okmode |
| 206 | 0x41b252 | Enumerate TCP/UDP connections over secondary channel |
| 210 | 0x41b555 | Set stored client group |
| 211 | 0x41b669 | Elevation/relaunch helper workflow |
| 214 | 0x41abbc | Receive/load startup enumeration plugin |
| 215 | 0x41abdf | Delete startup/registry entry and refresh enumeration |
| 216 | 0x41aca7 | Set startup/registry entry and refresh enumeration |
Appendix B. Configuration consumers and complete raw field inventory
B.1 Reviewed consumers
The table below names consumers established in code. Raw NUL, ASCII zero and a decimal selector are different encodings and are retained as such.
| Field | Value | Code-backed meaning and relevant address |
|---|---|---|
| 0 | hostname:20901:1 | Endpoint list, split into records and host/port/TLS parts at 0x4190c4 |
| 1 | GP | Label used in initial host registration, 0x4194cb onward |
| 2 | 1 | Reconnect interval in seconds; atoi × 1000 then Sleep at 0x419d95-0x419db5 |
| 3 | raw NUL | Gate for Remcos installation behavior; read at 0x414337 and 0x4146f0. Off here |
| 4 | raw 01 | First Run-key persistence selector, copied at 0x4146b5; consumed by 0x412f69 |
| 5 | raw 01 | Second Run-key persistence selector, copied at 0x4146cf; consumed by 0x412f69 |
| 7 | 100000 | Keylogger log-size limit, converted to 100,000,000 bytes, not a delay. Setup 0x40bc7d and GetFileSize comparison at 0x40c4b1-0x40c4c7 |
| 8 | raw NUL | Additional Explorer Run-key selector, copied at 0x4146e9 and consumed by 0x412f69 |
| 9 | 8 | Installation root selector. ASCII 8 selects ProgramData, falling back to ProgramFiles |
| 10 | Chrome.exe | Installation executable name, passed into installer 0x413093 |
| 14 | chrome-B1MV8Z | Confirmed single-instance mutex name |
| 15 | 0 | Default offline-keylogger mode. Mode 1 starts ordinary offline logging; mode 2 selects another logging path. Registry okmode may override it in 0x40bf07 |
| 16 | 8 | Log root selector: ProgramData / fallback ProgramFiles |
| 17 | logs.dat | Keylogger log filename |
| 20 | raw NUL | Gate for a screenshot thread path, 0x414af9 |
| 22 | raw NUL | Gate for a second screenshot thread path, 0x414b4c |
| 35 | raw NUL | Gate for background microphone recording, 0x414b9b |
| 37 | 1 | Recording path selector used only if the microphone branch is enabled |
| 38 | MicRecords | Recording path/name component |
| 40 | 0 | Console/debug-output mode; zero skips automatic console display |
| 41 | 0 | Initial connection delay in seconds, multiplied by 1000 at 0x419111 |
| 42 | two NUL bytes | Additional offline-keylogger argument, passed at 0x414ac6; exact filter semantics not fully traced |
| 43 | raw NUL | Gate for automatic browser-login/cookie clearing, 0x414c0e |
| 44 | raw 01 | Subordinate option for browser clearing, consumed only if field 43 enables that path |
| 45 | 0 | Browser-clearing repeat interval, converted to minutes × 60,000 in 0x41230a |
| 46 | raw NUL | Gate for an elevation-related startup attempt, 0x41465c |
| 48 | chrome | Installation subdirectory/name component |
| 49 | remcos | Keylogger directory/name component |
| 50 | raw NUL | Watchdog gate, copied to 0x47ce66 and checked before starting 0x4171a8 |
| 51 | raw NUL | Repair/reinstallation-monitor gate, copied to 0x47ce67 and checked before starting 0x417804 |
| 52 | 594F6AE6F44FEFCA561F7CA0A8B7CC0C | Stored as licence at 0x414949-0x414972 |
| 53 | raw NUL | Screenshot-thread option read alongside fields 20 and 22 |
| 54 | DER certificate | Local TLS certificate, passed into 0x404197 |
| 55 | DER EC private key | Local TLS key, passed into 0x404197 |
| 56 | DER certificate | Peer certificate, passed into 0x404197 |
B.2 All recovered positional elements
The table retains unknown fields rather than assigning meanings from an unverified external schema. Binary certificate/private-key material is represented by its type and size; the exact bytes remain in the encrypted evidence bundle. The final empty element is a split artifact, not a meaningful enabled setting.
| Index | Length | Recovered value |
|---|---|---|
| 0 | 50 | b'zxaswedryyhfrdswaaaaaacvbnmjgffds.ydns.eu:20901:1\x1e' |
| 1 | 2 | GP |
| 2 | 1 | 1 |
| 3 | 1 | b'\x00' |
| 4 | 1 | b'\x01' |
| 5 | 1 | b'\x01' |
| 6 | 1 | 1 |
| 7 | 6 | 100000 |
| 8 | 1 | b'\x00' |
| 9 | 1 | 8 |
| 10 | 22 | Chrome.exe (UTF-16LE) |
| 11 | 1 | b'\x00' |
| 12 | 1 | b'\x00' |
| 13 | 1 | 0 |
| 14 | 13 | chrome-B1MV8Z |
| 15 | 1 | 0 |
| 16 | 1 | 8 |
| 17 | 18 | logs.dat (UTF-16LE) |
| 18 | 1 | b'\x00' |
| 19 | 1 | b'\x00' |
| 20 | 1 | b'\x00' |
| 21 | 2 | 10 |
| 22 | 1 | b'\x00' |
| 23 | 2 | b'\x00\x00' |
| 24 | 1 | 5 |
| 25 | 1 | 6 |
| 26 | 11 | Screenshots |
| 27 | 1 | b'\x00' |
| 28 | 1 | b'\x00' |
| 29 | 1 | b'\x00' |
| 30 | 1 | b'\x00' |
| 31 | 1 | b'\x00' |
| 32 | 1 | b'\x00' |
| 33 | 1 | b'\x00' |
| 34 | 1 | b'\x00' |
| 35 | 1 | b'\x00' |
| 36 | 1 | 5 |
| 37 | 1 | 1 |
| 38 | 10 | MicRecords |
| 39 | 1 | b'\x00' |
| 40 | 1 | 0 |
| 41 | 1 | 0 |
| 42 | 2 | b'\x00\x00' |
| 43 | 1 | b'\x00' |
| 44 | 1 | b'\x01' |
| 45 | 1 | 0 |
| 46 | 1 | b'\x00' |
| 47 | 1 | 1 |
| 48 | 14 | chrome (UTF-16LE) |
| 49 | 14 | remcos (UTF-16LE) |
| 50 | 1 | b'\x00' |
| 51 | 1 | b'\x00' |
| 52 | 32 | 594F6AE6F44FEFCA561F7CA0A8B7CC0C |
| 53 | 1 | b'\x00' |
| 54 | 258 | DER local certificate |
| 55 | 121 | DER EC private key, matches field 54 |
| 56 | 260 | DER peer certificate |
| 57 | 0 | Empty trailing element |
B.3 Path selectors
| ASCII selector | Root |
|---|---|
| 0 | Temp environment directory |
| 1 | Current executable directory |
| 2 | SystemDrive |
| 3 | WinDir |
| 4 | Windows system directory, using system32/SysWOW64 logic |
| 5 | ProgramFiles |
| 6 | AppData |
| 7 | UserProfile |
| 8 | ProgramData, falling back to ProgramFiles |
Appendix C. Additional secondary-channel routes
The supplied secondary_routes.json records the raw route extraction, addresses and supporting references. The comparison tracer follows arithmetic and branches and stops before feature-handler execution. It is not a test of actual file, camera or registry operations.
C.1 Service channel
The dispatcher at 0x41e9d4 reads a leading byte and uses the table at 0x41ee38.
| Byte | Handler VA | Role |
|---|---|---|
| 0 | 0x41ea35 | Load service module, then continue to enumeration |
| 1 | 0x41ea6c | GetSvc |
| 2 | 0x41eaa4 | StartSvc |
| 3 | 0x41eb31 | StopSvc |
| 4 | 0x41eba6 | PauseSvc |
| 5 | 0x41ec1b | ResumeSvc |
| 6 | 0x41ec90 | RestartSvc |
| 7 | 0x41ed23 | SetSvcStartup |
C.2 Camera channel
| ID | Handler VA | Role |
|---|---|---|
| 60 | 0x403f89 | Receive/load DLL and resolve camera exports |
| 61 | 0x403f54 | Request frame acquisition while camera-open state is set |
| 62 | 0x403f42 | CloseCamera and clear open state |
| 63 | 0x403f2b | OpenCamera and continue to acquisition |
| 64 | 0x403ec5 | OpenCamera, delay and request capture |
C.3 Recovery channel
| ID | Handler VA | Role |
|---|---|---|
| 31 | 0x417be9 | Receive/load recovery module through recovery_run_str / recovery_free_str adapter |
| 68 | 0x417b48 | Execute recovery or helper coordination according to access-level path |
C.4 Remote-input channel
The leading control byte is an ASCII character, not a primary command DWORD.
| Character | Branch/check | Observed role |
|---|---|---|
| 0 | 0x41dc5e | Screen-session/frame request using capture object 0x47f9e8 |
| 1 | 0x41dc97 | Keyboard-input helper |
| 2 | 0x41dcf7 | Coordinate-bearing mouse input helper |
| 3 | 0x41dd57 | Additional coordinate-bearing mouse input helper |
| 4 | 0x41ddbb | Mouse movement/position input helper |
| 5 | 0x41de0c | Wheel event with -120 delta |
| 6 | 0x41de16 | Wheel event with +120 delta |
| 7 | 0x41de2d | Screen-session/frame request using capture object 0x47fab0 |
The detailed button/coordinate interpretation should be taken from the cited helper implementations rather than inferred from operation order alone.
Appendix D. Evidence package and reproduction
The evidence archive is AES-encrypted with password infected and contains malicious objects. Recovered executable material uses .bin filenames. The original script is retained for reproducibility and should be treated as executable malware content.
| File or group | Purpose |
|---|---|
| Original b036…js | Exact outer script |
| embedded1 / embedded3 | Recovered Lua and LuaJIT host |
| loader_clean.lua | Analytical source with repeated VM blocks removed |
| decoded_stage.bin | Decoded Donut shellcode |
| donut_instance.bin | Decrypted instance, including advanced counter |
| final_payload.exe.bin | Exact 537,600-byte Remcos PE |
| resource_10_SETTINGS.bin | Original encrypted configuration resource |
| remcos_config.bin / config_fields.json | Decrypted bytes and complete positional field representation |
| config_field_54.der / 55.der / 56.der | Certificate/key objects |
| deep/disassembly.asm | Indexed annotated disassembly |
| deep/command_map.csv | All 216 primary slots |
| deep/command_table.json | Active primary routes and supporting references |
| deep/secondary_routes.json | Additional recovered channel routes |
| deep/*validation.json | Independent verification results |
| manifest.json | Computed per-file sizes and SHA-256 values |
Dependencies used for the analytical scripts include Python, pyzipper, lupa, pefile, pycryptodomex, unicorn, capstone, cryptography and Node.js. None of the following steps requires contacting the sample's infrastructure.
From the package root, with the analysis directory present:
node analysis/extract.js
python analysis/preprocess.py
python analysis/decode.py
python analysis/decrypt_instance.py
python analysis/final_extract.py
python analysis/deep/verify_donut_cipher.py
python analysis/deep/verify_header.py
The first command evaluates the original script with inert Windows-interface substitutes and neutralizes its recursive debugger trap. The decryption script starts only at the standalone Donut decryptor. The header test starts only at the isolated header-reader routine. None should be confused with a full sandbox detonation.
The cipher and header verification outputs are retained in JSON so another analyst can check both the conclusion and its scope. The certificate validation records preserve signature and key-match results without including the private-key value in the narrative report.
Appendix E. Findings that required correction or qualification
Several attractive claims in the supplied notes did not survive closer examination:
- The Lua's repeated VM implementations do not make a custom bytecode deserializer necessary for this sample's payload recovery.
- High entropy and the absence of an MZ header did not establish an inaccessible final encryption layer. The first instruction points to an executable Donut wrapper.
- The Donut identification rests on structure and reproduced decryption, not the Lua's deliberately manufactured DonutStub string.
- The Lua's buffer of injection API names is decoy data. Genuine process hollowing was established separately in the Remcos PE.
- An AMSI/ETW patch attempt is not proof of a complete or stable bypass.
- Remcos's supported surveillance features are not all enabled at startup in the recovered defaults.
- Fifty-eight split elements include a trailing empty element; they should not be described as 58 independently meaningful options.
- The mutex and licence fields are now confirmed by their consumers rather than left as generic identifiers.
- The local certificate/private-key relationship and both self-signatures are now independently verified.
- The outer ZIP and the JScript have different hashes. The long basename matches the JScript.
- A primary command range of 1-216 contains 82 active entries, not 216 implemented features.
- Optional feature interfaces do not recover the contents of controller-supplied DLLs that are absent from the evidence.
These qualifications do not weaken the payload identification. They make the account more useful: each conclusion can be tied to recovered bytes, a specific code path or a narrowly defined verification result.
Appendix F. Visual evidence index
The 28 figures are embedded in their relevant sections. The separate visual atlas provides vector SVG, PNG, Mermaid and Graphviz source for every figure. Its illustrated report uses relative SVG image links for readers without Mermaid support. Colors aid navigation; captions state the evidential limits.
| Figure | Subject | Report section |
|---|---|---|
| D01 | Delivery, payload and persistence boundaries | 1. Executive assessment |
| D02 | Independent evidence supporting payload recovery | 2. Evidence and analytical scope |
| D03 | JScript delivery branch | 3. Delivery through JScript |
| D04 | Lua scheduled-task persistence conditions | 4. The Lua loader |
| D05 | Separating meaningful Lua from camouflage | 4. The Lua loader |
| D06 | Payload decoding decisions | 4. The Lua loader |
| D07 | VEH page-protection cycle | 4. The Lua loader |
| D08 | Donut shellcode regions and architecture selection | 5. Recovering the Donut instance |
| D09 | Independent reconstruction of the Donut cipher | 5. Recovering the Donut instance |
| D10 | Exact executable boundary inside the instance | 5. Recovering the Donut instance |
| D11 | SETTINGS resource and positional fields | 7. Configuration and startup decisions |
| D12 | Startup defaults versus later activation | 7. Configuration and startup decisions |
| D13 | Connection selection and retry control | 8. Controller communication |
| D14 | TLS object relationships and verification limits | 8. Controller communication |
| D15 | Plain and compressed application-frame layouts | 8. Controller communication |
| D16 | TCP buffering and message dispatch | 8. Controller communication |
| D17 | Primary command translation table | 9. Command dispatch and feature channels |
| D18 | Feature channels and shared module loader | 9. Command dispatch and feature channels |
| D19 | Keyboard capture, context and output paths | 10. Surveillance and remote interaction |
| D20 | Screen observation and remote input | 10. Surveillance and remote interaction |
| D21 | File-management channel and transfer feedback | 11. Files, registry and host control |
| D22 | Browser clearing versus module-based recovery | 12. Browser data and recovery modules |
| D23 | Manual PE loader control flow | 13. In-memory modules, hollowing and elevation |
| D24 | Section-backed process hollowing path | 13. In-memory modules, hollowing and elevation |
| D25 | Core lifecycle operations and wrapper boundary | 14. Restart, update, watchdog and removal |
| D26 | What each verification layer actually establishes | 16. Verification and remaining limits |
| D27 | Behavioral correlation for a defender | 15. Forensic interpretation and detection |
| D28 | Detection testing and coverage boundaries | 15. Forensic interpretation and detection |
The atlas also contains the 14 annotated assembly excerpts, four byte comparisons and detection test results as structured JSON. These displays make the central recovery and control-flow findings inspectable without rerunning the reproduction scripts.
Detection downloads
The YARA rules and experimental Sigma-format templates below accompany the original report. The behavioural templates were tested against synthetic fixtures, not Windows telemetry or a production SIEM backend. Validate and tune them before deployment.