Malware analysis / reverse engineering
PackClient
Reverse Engineering a Modular RAT Framework
Abstract
This report follows PackClient from its signed-host carrier into a suspended 32-bit surrogate, the injected Donut package, Launcher execution, and a 985,088-byte Core recovered directly from historical PLK1 traffic. Reassembled sessions preserve successful authentication, Core delivery, plaintext commands, and 15 PV10 JPEG frames. Analysis of the recovered Core reveals its plugin contracts, application encryption, update storage, clipboard replacement, and built-in screenshot producer.
Key findings
- A 985,088-byte
PackClientCore.dllwas recovered directly from historical PLK1 traffic; its.textmatches all 352 retained Core memory mappings. - A complete
PLH1 -> PLC1 -> PLA1 -> PLK1session continues into bidirectional Core traffic and 15PV10JPEG frames. - Normal full-EXE execution persists the Tax Notice host, while direct carrier-DLL execution produces a broken
rundll32.exetask without the original DLL argument.
From Launcher to recovered Core
One chain connects the signed host, injected Launcher, PLK1 delivery, recovered Core, persistence, and live C2 traffic.
This research follows one PackClient build from its signed host and sideloaded carrier into a suspended 32-bit surrogate, the injected Donut package, Launcher delivery, and the recovered Core.
| Area | Result |
|---|---|
| Launcher | Recovered framing, authentication, PLK1/cache, active-session, and 1RCP contracts |
| Core | Reconstructed a 985,088-byte PackClientCore.dll directly from historical PLK1 traffic and mapped its exports, configuration, plugin interfaces, commands, and screenshot producer |
| Historical traffic | Reassembled PLH1 -> PLC1 -> PLA1 -> PLK1, followed by bidirectional Core traffic and 15 PV10 JPEG frames |
| Persistence | Distinguished the normal full-EXE path from the broken task produced when a sandbox invoked the carrier DLL directly through rundll32.exe |
The Launcher's raw-BGRX 1RCP worker and the Core's GDI/WIC PV10 producer are separate recovered paths. No component linking them was found, and no plugin binary was present in the available traffic, memory, or cache material. The PackClient Core analysis develops those findings, while Samples and provenance lists the complete file identities.
The signed-host execution chain
The signed host loads a colocated carrier, which starts an embedded Donut package inside a suspended 32-bit surrogate.
The July 2026 Tax Notice lineage places a signed NV Access/NVDA executable beside a malicious nvdaHelperRemote.dll. Because the host imports the helper by bare filename, the colocated carrier satisfies the import. At host RVA 0x1004, the executable calls nvdaHelperRemote.dll!injection_initialize through IAT slot 0xF268. Samples and provenance identifies the studied host and carrier; the legitimate library layout is described in the NVDA helper documentation.

The installed directory preserves the signed host and its colocated carrier as a pair. The same chain creates a suspended SysWOW64\svchost.exe, writes the embedded package into that process, changes its primary-thread context, and resumes execution.

The injected call-over-data package contains a Donut instance and embedded executable A. Four bytes of terminal glue recover the instance pointer from the opening CALL; the next 11,647 bytes exactly match Donut LOADER_EXE_X86 from revision 47758d787209dd1744f58c140102ac91b649df16, followed by 23 zero bytes. Donut maps A, and A maps PackClientLauncher.exe B. The complete package layout appears in the Launcher architecture.
Across twelve July and August Triage tasks, the protected allocation is 417,792 bytes and has SHA-256 E49581067CC2AA5ABD09C8DF42D6FBD87CB064A9363FE8B52D8369FD1C51FFE5. It contains a 415,071-byte logical record; the additional 2,721 allocation bytes remain unexplained. These are distinct objects, so their hashes are not interchangeable. One task records 53 write-labelled events into PID 844 at 0x00440000, followed by SetThreadContext on primary thread 4600. This sequence supports thread execution hijacking (T1055.003), not classic process hollowing or remote-thread creation. The carrier's precise write call site and the instruction pointer installed by SetThreadContext remain unresolved. See the Launcher architecture for the carrier and A-to-B mapping details.
Launcher B provides transport, PLK1 delivery and caching, active-session handoff, and an early screenshot-worker mode. Once PLK1 completes, the recovered Core supplies the wider command set and the PV10 screenshot path seen in historical traffic.
| Object | Recorded size | Identity anchor |
|---|---|---|
| Tax Notice host | 120,984 bytes | SHA-256 begins 93DD8B7B393289F8 |
| Sideload carrier | 455,527 bytes | SHA-256 begins 7295090C2CB63EBC |
| Reconstructed Launcher B | 271,872-byte PE32 executable | SHA-256 begins 46B34789196733FA |
| Recovered Core | 985,088-byte PE32 DLL | SHA-256 begins 4DE6EF8647FB4B59 |
The sample and object table lists the complete hashes.

Launcher screenshot worker
An early command-line branch turns Launcher B into a local screenshot worker with a compact 20-byte protocol.
The worker is selected by an early argument-dispatch branch, before the later active-session/Core path. The interface is /scr_cap_worker <endpoint-utf8> [monitor-index]. Its body runs in the already launched process and inherits that process’s token, session and environment. The wrapper exits after the worker returns; the later token-based launch path cannot be used to infer the worker’s original security context. The screenshot worker analysis traces this activation path.
The endpoint argument is converted to UTF-16 and passed to CreateFileW with read/write access, no sharing and OPEN_EXISTING. There is one open attempt. The recovered path contains no endpoint creator, named-pipe server, wait-for-pipe loop or peer authentication. A named pipe is strongly supported by context, but the argument’s namespace is not recovered; the API itself also accepts other paths. Its endpoint handling is documented with the rest of the screenshot worker interface.

After the first successful capture, the worker emits READY. It then accumulates exactly 20 command bytes, checks the magic and dispatches the request type. Correct-magic types other than 3 and 5 are ignored. Bad magic, a failed read or a zero-byte read ends the command loop without a protocol reply.
The header is five little-endian DWORDs. The bytes 31 52 43 50 spell 1RCP; as a DWORD the magic is 0x50435231.
A normal exchange follows 1 → 3 → 2 → 5: the worker announces READY, the peer requests a capture, the worker returns the frame, and the peer eventually exits.
| Type | Direction | Meaning | Dimensions and payload |
|---|---|---|---|
| 1 | Worker → peer | READY | Captured width and height; payload length zero |
| 3 | Peer → worker | Recapture | Request DWORDs 2–4 are ignored |
| 2 | Worker → peer | Frame response | Width, height and following pixel-byte length |
| 5 | Peer → worker | Exit | Request DWORDs 2–4 are ignored; no reply |
Zero-filled request fields are a synthetic test convention, not a worker requirement. The command loop always reads the next 20-byte header and does not consume extra bytes according to the final DWORD. Worker process exit codes are separate from protocol message types.
The selected monitor supplies positive width and height. The DIB uses 32-bit BI_RGB with a negative height, producing top-down rows. Each pixel is blue, green, red and an unused fourth byte; meaningful alpha is not established. Stride is width × 4, with no additional per-row padding. The expected payload is width × height × 4; the IA-32 reconstruction found no separate arithmetic overflow guard. The actual response length is taken from the framebuffer vector’s end-minus-begin distance. The complete field and pixel layout appears in the screenshot worker analysis.

Within Launcher B, the pixel path ends at WriteFile on the supplied endpoint. No call or data edge carries those pixels into Winsock, PLK1, the encrypted transport, or a JPEG/PV10 conversion path. An external peer could transform or forward the pixels, but that component was not recovered. See the screenshot worker analysis for the complete path.
When this mode is not selected, Launcher B continues into the network framing and authentication path described next.
Launcher transport and authentication
Launcher B frames, authenticates, and can decrypt traffic before the Core is loaded; historical captures preserve the plaintext handshake and delivery path.
The outer frame starts with a little-endian word formed by 0x5A400000 | body_length. The receiver checks (frame_word >> 22) == 0x169, bounds the nonzero body at 0x3FFFFF, reads it exactly, and requires at least four bytes for the type. Thus 24 00 40 5A means a 36-byte body and a 40-byte total frame. The complete byte layout is documented in the Launcher protocol.
| Offset | Bytes | Outer-frame meaning |
|---|---|---|
0x00 |
4 | Prefix combined with body length |
0x04 |
4 | Little-endian type DWORD, part of the body |
0x08 |
Variable | Payload; body length minus four |
B sends PLH1 and PLA1 in plaintext type 0x15. Its first framed receive accepts PLC1 either directly in type 0x15 or through an authenticated outer 0x16 envelope with inner type 0x15, provided the envelope keys are initialized. This receive precedes Core loading. PLH1 is a 32-byte client hello; PLC1 is the 24-byte server challenge; PLA1 is the 40-byte client authentication object. PLH1 includes version 1, an unresolved word set to 0x20, fixed fields, a tick count and the process ID. PLC1 contributes a 16-byte challenge at offsets 0x08..0x17; challenge generation lives in the server.
PLA1 uses HMAC-SHA-256 over exactly 42 bytes, rather than the complete hello and challenge:
PLH1[0x10:0x20] // 16 bytes
|| PLC1[0x08:0x18] // 16 bytes
|| PLH1[0x0C:0x10] // 4 bytes
|| PLH1[0x06:0x08] // 2 bytes
|| "PLK1" // 4 bytes
The PSK comes from PACK_LAUNCH_PSK; missing or empty values select the 19-byte fallback pack-launch-dev-psk. After sending PLA1, the recovered function does not wait for a separate authentication acknowledgement. The July captures confirm the plaintext PLH1 → PLC1 → PLA1 sequence. The historical traffic record preserves the successful exchange.
Optional encrypted receive
Type 0x16 is an inbound encrypted envelope. In this B image, the send path constructs only type 0x15. The envelope carries a version byte, 16-byte IV, four-byte big-endian ciphertext length, ciphertext, and a 32-byte HMAC tag. The length field’s byte order differs from the surrounding little-endian framing.

The MAC covers version, IV, encoded length and ciphertext, excluding the outer frame/type and the trailing tag. A tag match gates AES-256-CBC decryption with BCrypt block padding; the resulting plaintext must begin with inner type 0x15. The tag comparison exits on the first mismatch and is not constant-time.
A ready byte at B RVA 0x41C44 gates a 32-byte AES key at 0x41C48 and a separate 32-byte HMAC key at 0x41C68. Analysis found reads of these globals but no initializer, setter or derivation. The handshake PSK was not shown to feed them. Unless another in-process component initializes this state, encrypted receive remains disabled. The Launcher protocol documents the key-state reads and the unresolved writer.
PLK1 delivery and Core loading
PLK1 reconstruction is only the first gate: the Core must survive a protected cache save and reload before execution.
PLK1 validates the header, original and transferred size bounds, chunk sequence, optional raw-LZ4 decompression, and the final plaintext SHA-256. Both active callers request slot 0, corresponding to PackClientCore.primary.dll; no caller in this Launcher requests the secondary slot supported by shared code. The PackClient Core analysis follows this delivery through to the recovered DLL.

A fresh transfer is not loaded directly. Save must first persist the plaintext, then TryLoad must decrypt and re-hash the cached copy. Only the reloaded vector reaches MemoryLoadLibraryEx; successful network delivery alone is insufficient for execution.
Each of the eight complete historical transfers uses eleven ordered type-0x15 chunks: ten carry 65,536 bytes and the final chunk carries 14,357 bytes. The resulting 669,717-byte raw-LZ4 object decompresses to the same 985,088-byte Core and matches the SHA-256 declared by the sender.
The cache resolves CryptProtectData and CryptUnprotectData dynamically and stores the Core by its plaintext SHA-256. This supports current-user DPAPI protection for the relative cache, although the absolute runtime base directory remains unresolved. The Launcher protocol traces the cache-hit and fresh-delivery paths.

Inside PackClient Core
Historical PLK1 traffic reconstructs the Core and reveals its exports, transport, plugin system, updates, commands, and screenshot producer.
All eight complete PLK1 transfers—four in 260715-wd77daas7l and four in 260716-dhnz7aft6z—produce the same 985,088-byte x86 PackClientCore.dll, SHA-256 4DE6EF8647FB4B599966A233740CB0514D1E71B8019A1A1792ED7E1E514EDF1C. Across eight Core processes, all 352 retained mappings contain the same 753,664-byte .text section, SHA-256 F06FF7AB6D62B761344CAECBCC6857912F7543F43C0E5FF462D2174BADB0CA3F. The full-file and mapped-section hashes independently identify the recovered build.

Ghidra independently identifies the recovered payload as a little-endian 32-bit x86 Portable Executable and records the same full-file SHA-256 produced by PLK1 reconstruction. The complete identity and recovery steps appear in the PackClient Core analysis.

Runtime entry points
The DLL exposes 11 exports in four groups:
- Main entry modes:
Main,MainMinimal,MainWithConfig,RunWithConfig, andRunDualWithConfig - Launcher integration:
PackClientDll_AbiVersion,PackClientDll_Run, andPackClientDll_TrySystemSessionHandoff - Stored-plugin memory:
PackClient_AllocStoredPluginImageWandPackClient_FreeStoredPluginImage - Probe entry:
ProbeEntry
PackClientDll_Run reads five fields from the Launcher's configuration block:
| Offset | Field |
|---|---|
+0x00 |
Host |
+0x04 |
16-bit port |
+0x08 |
Group |
+0x0C |
Optional tag |
+0x10 |
Optional configuration string |
The PackClient Core analysis traces the export preconditions and Launcher-to-Core ABI.
RunWithConfig starts one S1 link, while RunDualWithConfig maintains separate S1 and S2 endpoint states. Core implements reconnect, heartbeat, and periodic DNS re-resolution and reads six local settings for fragmentation, DNS polling, TLS, application authentication, and preview control.
Core transport and encryption
Core's authenticated type-0x16 record uses a little-endian ciphertext length. A nonempty local auth_psk derives independent keys as SHA256("PACKAPP|AES256|v1|" || raw_psk) and SHA256("PACKAPP|HMAC|v1|" || raw_psk); an empty value clears readiness and both keys. Core encrypts the complete original type and payload, then redispatches the decrypted inner record. The Launcher's earlier type-0x16 envelope instead uses a big-endian length, unresolved key state, and accepts only inner type 0x15. The PackClient Core analysis provides the complete application-envelope layout.
Plugin loading and updates
The Core accepts a modern plugin ABI—PackPlugin_GetAbiVersion, PackPlugin_GetFeatureId and PackPlugin_OnLoad—and retains a legacy Main loader. Before dispatch, a candidate plugin must expose at least RunWithConfig or Main; the modern ABI adds further validation. The recovered implementation includes eleven feature-to-DLL mappings, screen-codec sidecars, staged raw-LZ4 delivery, legacy LZ4/zlib delivery, activation policy, and a paired current-user-DPAPI .pblob/JSON cache. The cache writes through a temporary file, flushes, atomically replaces the destination, and records feature, version, architecture, hash, size, and timestamps. It is separate from the Launcher's SHA-256-addressed Core cache.
Core updates use a distinct CLIENTCOREUPD path that stores DPAPI-protected chunks under HKCU\Software\PackMonitorClient\LauncherDllStore\<bits>\Primary. No plugin binary, paired cache, completed plugin delivery, activated mapping, or Core-update transaction appears in the available artifacts. The plugin and update paths are traced in the PackClient Core analysis.

Commands and capabilities
Beyond the observed startup and preview exchange, analysis identified handlers for:
- File, process, startup-item, scheduled-task, and registry operations
- Shell and payload execution
- Remote input, screen control, keylogging, and clipboard telemetry
- TCP and UDP proxying
- Virtual desktops and webcam handling
- Plugin delivery and Core updates
Captured traffic contains startup, inventory, offline-keylogger status, preview control, and screenshot delivery.
| Surface | Observed traffic | Recovered Core behavior |
|---|---|---|
| Session/startup | INP HELLO, STARTUP PROBE and STARTUP OK |
Session/control router and startup-probe branch |
| Preview | Enable/request/ack exchanges and 15 PV10 JPEG frames |
GDI/WIC capture and PV10 producer |
| Keylogger | KTL OFFLINE telemetry |
Hook, archive, sync and clipboard-telemetry paths |
| Plugins | No transaction, cache pair or plugin bytes | Staged protocol, ABI, loader and DPAPI cache |
| Core update | No transaction | CLIENTCOREUPD validation and registry store |
| Auth envelope | No type-0x16 frames |
Optional Core KDF and AES-CBC/HMAC envelope |
| ETCHOOK | No activation traffic | Regex-controlled clipboard replacement |
The built-in preview producer uses GDI capture and scaling, GetDIBits, WIC JPEG encoding, and a bytewise PV10 serializer. All 15 historical type-18 frames match that format. This path is distinct from the Launcher's raw-BGRX 1RCP worker, and no recovered component bridges the two.

Clipboard replacement
Core also implements regex-controlled ANSI and Unicode clipboard replacement through ETCHOOK. Proofpoint previously reported PackClient's clipboard-replacement capability; analysis of this build adds the built-in patterns and the concrete PERCLIENT and SYNC control formats. ETCHOOK does not appear in the captured traffic.
Active-session handoff
Launcher B can relaunch into the active console session and replace itself when that session changes.
The active-session path resolves a bootstrap executable, preserves a constrained set of special arguments, duplicates the current process token as a primary launch token and assigns it to the target console session before calling CreateProcessAsUserW. A separately selected user token can supply the environment; it is not the launch token. This is separate from the early screenshot mode, which exits through its own wrapper. Token selection and process creation are traced in the active-session handoff analysis.
| Handoff detail | Behavior |
|---|---|
| Application and command line | Null application-name argument; mutable quoted command line |
| Environment | Optional Unicode block; handles are not inherited |
| Desktop | WinSta0\Default |
| Window state | Hidden with CREATE_NO_WINDOW |
| Creation flags | CREATE_BREAKAWAY_FROM_JOB, CREATE_NO_WINDOW, and CREATE_UNICODE_ENVIRONMENT (0x09000400) |
| Retry behavior | Up to 30 attempts; each failure is followed by a one-second sleep |
| Drift check | Every 200 ms; a valid different console ID triggers replacement |
On successful drift replacement, the launcher closes child handles and exits the old process. It uses canonical -acsi state for this handoff. The continuity path is explicit in the Launcher, but the available runtime evidence does not identify which elevation API produced the original high-integrity host. The handoff analysis documents the drift state machine.

Launcher B in process memory
Private executable mappings, a thread start, and Launcher-specific state connect the reconstructed image to the live surrogate.
The first runtime capture preserves a bare 32-bit SysWOW64\svchost.exe, PID 5812, an NvSvc task, VMMap state, a user-mode dump and a late Process Monitor capture. The runtime correspondence below comes from passive examination of those artifacts.
VMMap’s 408 KiB private executable allocation at 0x002E0000 was an investigative lead. Its displayed “Thread Environment Block” label did not identify its contents. The dump’s package structure, embedded offsets, PE metadata and section hashes supplied that identification. Samples and provenance records the corresponding offsets and hashes.

| Runtime object | Location | Identification |
|---|---|---|
| Protected package allocation | 0x002E0000; 417,792 bytes |
Contains A and B at the expected logical-record offsets 0x128D and 0x1CC1 |
| Mapped A | 0x02C70000; image size 0x64000 |
Entry RVA 0x1308, timestamp 0x6A27AE4B, and PE layout match the reconstruction |
| Mapped B | 0x05410000; image size 0x48000 |
Entry RVA 0x12FAC, timestamp 0x6A3CB0B5, and immutable sections match the reconstruction |
| Thread 1884 | Start 0x02C71308 |
Start address resolves exactly to mapped A plus its entry RVA |
The complete protected allocation has SHA-256 E49581067CC2AA5ABD09C8DF42D6FBD87CB064A9363FE8B52D8369FD1C51FFE5; the embedded 415,071-byte logical record has SHA-256 0419AE7381CAA97172C40F5AEA601B8A22F1F58D27F3930509AF5808E043F65E. The mapped images are outside the registered module list. B-specific state includes the named mutant PackClientLauncher.Session.9b2126fc5ed31443, effective configuration, and formatted launcher diagnostics. Together, the package layout, mapped images, thread start, mutex, configuration, and diagnostics identify the live mapping as Launcher B.
No identifiable Core image appears in this local dump, although headerless content, missing pages, or a different capture moment cannot be excluded. The dump also does not reveal how the surrogate was populated. The runtime analysis combines it with cross-task Triage records that show the remote writes and SetThreadContext sequence.
Persistence and execution modes
Process records connect the signed host to the surrogate and explain why full-EXE and direct-DLL execution produce different scheduled tasks.
During controlled execution in Windows 11, Process Monitor records successful loading of the adjacent helper after two earlier 0xC0000906 failures. A medium-integrity Tax Notice process, PID 5952, relaunches as high-integrity PID 2116. The elevated host creates the bare surrogate and the task-creation branch below. The session includes separate launch attempts; the process relationships are tied to their recorded creation events. See the runtime analysis for the native process evidence.

The relationship is specific: svchost 3696 and schtasks 4600 are siblings under 2116. Conhost 8248 belongs below schtasks. The parent exits shortly after creating its children; its continued appearance in a process tree preserves ancestry, not liveness. Later reuse of a numeric PID must be joined to a new process start and lifetime.
Native file and registry records establish installation beneath C:\ProgramData\NVIDIA Corporation\NvSvc, hidden and backdated copies, and HKCU Run/RunOnce writes in this chain. Task XML and the creation record establish NvSvc persistence. Task Scheduler shows the at-logon trigger, ProgramData action, and configured user context. The later capture also records the backdating operation rather than merely showing a timestamp mismatch. The underlying file, registry, and task identities are listed in Samples and provenance.

Controlled Triage tasks close the apparent persistence discrepancy. Full-EXE execution copies and schedules Tax_Notice_23665.exe. Direct invocation as rundll32.exe …nvdahelperremote.dll,#1 bypasses the expected signed host, so the carrier treats the sandbox host as its executable and persists a copied rundll32.exe without the DLL argument. That action cannot replay the original entry point.


“Highest privileges” here belongs to the selected current user; it does not mean SYSTEM. A later persisted-path launch is observed, but the saved local evidence does not resolve every trigger as logon versus on-demand. Across the Triage runs, remote writes followed by SetThreadContext on the suspended primary thread identify thread execution hijacking (T1055.003). The precise write primitive and installed instruction pointer remain unresolved.
A separate dump contains the 415,071-byte transformed package and a private mapping of Launcher B. After relocations and imported addresses are normalized, three package sites differ by eight bytes. This confirms that the package and Launcher were resident, but does not prove that the eight bytes form a port patch. Three 6666-to-443 substitutions would explain six bytes; resolving the remaining two requires the original before-and-after bytes from each site. See Samples and provenance for the recorded comparison.

From failed connections to a live session
Local captures preserve failed connection attempts; July Triage traffic preserves the complete authenticated session and delivered Core.
PID 5812’s preserved environment and formatted buffers identify 154[.]36[.]188[.]201:443, a slot-0 pull attempt and WSA=10060. The PID 3696 set adds 373 ICMP host-unreachable messages quoting TCP SYNs to the same destination, plus two direct SYN_SENT socket views. These local artifacts establish attempted connections, not a completed session.
By contrast, the July Triage streams contain server PLC1, client PLA1, complete PLK1 delivery, and bidirectional Core traffic on the same TCP/443 endpoint. The September reruns 260908-zwr5naybjb and 260909-abma8sab28 reached only the greeting/retry stage because the server returned no application data. The complete decoded sequence appears in the PackClient Core analysis.

The packet capture has no PID metadata, so it cannot independently assign traffic to PID 3696; Process Explorer supplies that process-to-socket link. The successful July traffic uses TCP/443 but is raw PackClient rather than TLS or HTTPS. The preserved evidence establishes the different connection outcomes, but not why the server stopped responding. Capture identities and network observations are listed in Samples and provenance.
Analysis tools and detections
Offline decoders, a Wireshark dissector, and detection rules turn the recovered formats and behavior into practical research tools.
Two offline Python tools and a phase-aware Wireshark Lua dissector accompany the analysis. They decode Launcher and Core framing, distinguish the two type-0x16 formats, label observed Core messages, validate PV10, and export verified PLK1/Core and JPEG files when requested. Each export records its source flow, frame positions, size, and hash. All three tools operate passively on supplied files or byte streams. Setup and usage are described under Analysis tools.
The test suite covers framing, authentication, both encryption formats, PLK1 reassembly and extraction, Core commands, PV10, capture reassembly, Wireshark integration, screenshot IPC, and detection rules. Running the tools against the historical captures reproduced the recovered Core and all 15 JPEG frames.
Detection and hunting
The detection set covers three layers:
- Sigma rules target the observed
NvSvctask, 32-bit surrogate creation, screenshot-worker mode, and active-session mode. - Suricata rules include standalone Launcher markers that overlap existing Emerging Threats coverage, plus flow-aware challenge-to-PLK1 and Core-startup-to-
PV10sequences. - Separate YARA rules identify the recovered Launcher and Core.
On the retained case material, the Launcher rule matched all 86 Launcher mappings and none of 383 other objects. The Core rule matched the recovered DLL and all 352 Core mappings, with no matches among 116 Launcher or control allocations. A preliminary scan of more than 30,000 local Windows and installed-application PE files produced no matches for either rule.
The YARA rules remain experimental because they have not been measured against a broad enterprise-software or unrelated-malware corpus. See the Detection guide for rule logic, validation, and false-positive considerations.
What this research adds
The recovered Launcher and Core extend the public PackClient account from campaign reporting into an execution and protocol reconstruction.
Proofpoint’s 27 August 2026 report describes the Tax Notice chain, PackClient’s modular Launcher/Core architecture, representative protocol markers, and existing Emerging Threats coverage. Deception.Pro later documented PackClient hands-on-keyboard activity and ManageEngine use. This research adds the Launcher’s transport and loading contracts, direct Core reconstruction from PLK1 traffic, the signed-host/Donut/mapper chain, and working analysis and detection tools. Proofpoint campaign report, Deception.Pro later reporting
Several pieces remain outside the recovered chain:
| Missing component or event | What would resolve it | What is known now |
|---|---|---|
| Plugin binaries | A preserved plugin transfer, cache pair, or post-activation memory image | Core contains the protocol, cache, ABI, and loaders, but no plugin PE was captured |
| Carrier write primitive | The precise write call site and the instruction pointer installed by SetThreadContext |
Remote placement and primary-thread hijacking are established at runtime |
External 1RCP peer |
The endpoint creator and type-2 consumer | Launcher B supplies the worker; no bridge to Core PV10 was found |
Real 1RCP exchange |
A complete READY/request/frame sequence from the malware worker | The code path and synthetic exchange agree, but the real exchange did not complete |
| Launcher envelope keys | The initializer or live values for the gated key globals | The receive format is recovered; its key source is not |
| Server implementation | Server-side code or configuration | Historical captures reveal behavior and commands, not the implementation |
| September non-response | Server-side telemetry or controlled infrastructure | The client sent PLH1 but received no application data |
| Production detector accuracy | Broader benign and unrelated-malware collections | The rules pass case-specific and local benign tests |
The PackClient Core note contains the full Core reconstruction. Scope and limitations collects the remaining technical gaps.
Continue reading
Browse the technical notes for the complete protocol, runtime, and tooling write-ups, or open samples and source figures for hashes and figure provenance.