Nested Trust: HollowFrame’s Layered Loader and Matryoshka Backdoors
Key Takeaways
- Blackpoint Cyber’s Adversary Pursuit Group (APG) identified a previously undocumented, multi-stage Go based loader framework tracked as HollowFrame and a Rust based malware family tracked as Matryoshka.
- The intrusion began with a spear phishing chain that delivered an encrypted archive containing a malicious Windows shortcut. Executing the shortcut launched an obfuscated PowerShell sequence that requested elevation, weakened Microsoft Defender protections, and downloaded additional payloads.
- HollowFrame used a staged Python environment to disguise execution. A malicious python311.dll loaded through python.exe decrypted and launched a modular Go based framework designed to support multiple payload execution and persistence techniques.
- The Matryoshka family included two distinct Rust based backdoor variants. One provided HTTP based communication and command execution, while the other used GitHub for beaconing, tasking, reconnaissance, file transfer, and secondary payload delivery.
- Together, HollowFrame and Matryoshka gave the actor a persistent foothold for remote command execution, Active Directory reconnaissance, file transfer, and deployment of follow-on tooling.
- These capabilities could support credential theft, lateral movement, and broader domain compromise through additional tools delivered after initial access.
- The campaign distributed malicious activity across scripts, signed binaries, proxy DLLs, and trusted cloud services. Individual artifacts could appear unrelated, but together they revealed a coordinated path from phishing to persistent remote access.
Executive Summary
Blackpoint Cyber’s Adversary Pursuit Group (APG) identified a previously undocumented, multi-stage intrusion affecting two endpoints at a law firm. The activity began with a spear phishing lure and progressed through several layers of obfuscated scripts, encrypted payloads, and trusted software components before establishing persistent remote access.
The intrusion relied on a modular Go based loader tracked as HollowFrame and a Rust based malware family tracked as Matryoshka. HollowFrame provided the actor with multiple execution and persistence options. The Matryoshka family was represented by two distinct backdoor variants, one that communicated directly over HTTP and another that used GitHub for tasking and payload delivery. Together, these variants supported command execution, reconnaissance, file transfer, and follow on malware deployment.
The campaign’s defining characteristic was its deliberate fragmentation. Each stage performed a limited role while concealing malicious activity behind legitimate runtimes, signed applications, familiar Windows components, and widely used cloud services. This layered design made the intrusion more difficult to identify and reconstruct through any single source of telemetry.
Technical Analysis
A Document Trail with a Hidden Turn
The initial access path stemmed from a spear phishing email sent to multiple users within a law firm. The embedded link directed recipients through an attacker-controlled redirector to a Mega hosted encrypted archive. The archive contained Case Documents.lnk, and execution of the shortcut initiated the local command chain.
Figure 1: Mega hosted download page used to deliver the initial encrypted archive.
The malicious .lnk launched a command sequence that wrote Base64 encoded content into %TEMP%\sgrfm.b64, generated a companion %TEMP%\sgrfm.cmd script, and invoked certutil.exe -decode to reconstruct the next command stage. The decoded script then launched PowerShell to retrieve additional content from 2.26.252[.]84, allowing the attacker to separate delivery, decoding, and network retrieval across several native Windows processes.
Figure 2 : High level overview of the shortcut execution chain
The Cradle Beneath the Cradle
The malicious PowerShell executed in memory by the initial download cradle stored a 16-character key in $k and a long hexadecimal payload in $d. It converted each byte from hexadecimal, XORed it against the repeating key, rebuilt the decoded script as text, and executed the result through Invoke-Expression. This was a straightforward obfuscation layer, but it concealed the network endpoint, Defender modifications, installation path, and execution flow from basic string inspection.
Figure 3: PowerShell routine that converts the hexadecimal payload, applies repeating-key XOR operation, and executed decoded content using Invoke-Expression
The decoded stage checked whether it was running with administrative privileges. When elevation was unavailable, it wrote a temporary script under %TEMP% and relaunched PowerShell with -Verb RunAs, -NoProfile, -ExecutionPolicy Bypass, and -WindowStyle Hidden. The stage then created %LOCALAPPDATA%\Programs\Python\Python311-Brief and added Defender exclusions for both that directory and the process name python.exe.
This sequence mattered because the exclusions were established before the executable payloads arrived. The directory exclusion reduced scanning of every file placed beneath the staged Python path, while the process name exclusion broadened that protection to subsequent activity executed under python.exe. The actor effectively prepared a trusted looking execution lane before introducing the loader.
The script then downloaded hxxp://2.26.252[.]84/Python-3.11.0-embed-amd96.zip over HTTP. The nonstandard amd96 suffix distinguished the archive from a legitimate Python embedded distribution. Before extraction, the script verified that the file exceeded 1,024 bytes and began with a valid ZIP signature. It then extracted python.exe, python311.dll, and vcruntime140.dll before launching python.exe without a Python script, module argument, or interactive console.
This launch moved the chain into the sideloading stage. Instead of using Python to execute a script, the actor used python.exe as the host process for the adjacent python311.dll, which redirected execution into malicious Go code. The bundled Python package therefore served as a delivery wrapper for HollowFrame rather than a functional Python environment.
Python in Name Only
The bundled python.exe imported only Py_Main from the adjacent python311.dll. The DLL was not a CPython runtime. It was a 64-bit Go DLL that exported only four Python compatible names: Py_Main, Py_Initialize, Py_Finalize, and Py_IsInitialized (Figure 4). That small surface was enough to satisfy the host’s immediate import requirement and transfer execution into the malicious Go runtime.
Figure 4: Execution flow from python.exe through the sideloaded python311.dll into HollowFrame
Go build metadata preserved the source paths C:/Users/Admin/Desktop/Project220Rebr/stub/cmd/dllstub/main.go and exports.go. The APG used those artifacts to help track the framework, but Project220Rebr appeared to be an internal build directory name rather than an embedded malware family. Therefore, the APG is tracking the Go loader as HollowFrame.
The DLL contained a 6,398,938-byte encrypted region stored in an interleaved format. HollowFrame processed only the bytes at even offsets and skipped every odd position byte, reducing the stored region to the ciphertext actually consumed by the decryptor. It then used a 32-byte key and 24-byte nonce with XChaCha20-Poly1305, followed by zlib decompression, to recover a valid AMD64 executable.
The skipped bytes added structural noise around the ciphertext rather than stronger cryptographic protection. This altered the appearance and byte relationships of the embedded data, preventing a straightforward contiguous carve from reproducing the payload without first following HollowFrame’s byte selection logic.
The XChaCha20-Poly1305 construction supplied both confidentiality and authenticity. A 32-byte key and 24-byte nonce were used to derive the operational ChaCha20 state, while the Poly1305 tag ensured the loader would reject modified ciphertext. Successful authentication yielded a zlib stream beginning with the expected compression header, and decompression produced an `MZ` executable. This sequence gave the actor a reliable way to distinguish a valid embedded stage from corrupted or analyst-modified data before transferring execution.
HollowFrame, Built for Options
The recovered Go executable was a modular loader and persistence framework. Its internal packages included payload, ntapi, ghosting, stomp, memload, p3inject, scload, and syscheck. The loader supported several execution modes, including p3_poison, phantom_dll, process_ghosting, module_stomping, and file_based.
The p3inject package implemented Process Parameter Poisoning by creating a child process with benign looking startup details, then using its process parameter structures to stage malicious content before execution. Additional execution paths included Process Ghosting, module stomping, manual PE mapping, shellcode execution, NtCreateThreadEx based execution, PEB image base patching, telemetry patching, and file-based launch. This range of options allowed the operator to select an execution method based on the payload and local environment, meaning the same framework could produce different telemetry across endpoints.
The execution modes were not interchangeable labels for the same behavior. Process Ghosting created an image section from a delete pending file and launched a process whose executable content no longer had a stable on-disk counterpart. The manual mapper instead parsed PE headers, allocated an image sized region, copied sections, applied relocations, resolved imports, set page protections, and transferred control to the entry point inside the current process. The shellcode path treated the recovered bytes as position independent code, while the file-based path wrote the payload and used conventional process creation.
The loader also implemented anti analysis checks for uptime, installed memory, file count in the user profile, and cursor movement. A failed gate produced the message looks like a sandbox, exiting. Persistence options included an on-logon scheduled task with highest privileges and a 30-second delay, a permanent WMI event subscription triggered by new Win32_LogonSession instances, and Startup-folder deployment.
The persistence mechanisms covered different operational needs. The scheduled task provided a simple and durable on-logon trigger, the WMI subscription allowed execution in response to new logon session objects without a visible startup file, and the Startup-folder path offered a lower complexity fallback (Figure 5). The WMI routine also removed filters, consumers, and bindings that reused the generated name before recreating them, reducing duplicate artifacts and making repeated deployment more deterministic.
Figure 5: HollowFrame persistence through an on-logon scheduled task and WMI logon-session trigger
Seventy Pieces of Panda
A second encrypted container was embedded in the Go loader’s data section. The 1,960,517-byte container began with the magic value FB F5 F6 9B and used a separate 80-byte parameter structure to reconstruct a 32-byte key from four reordered XOR shards. The loader authenticated the magic value as additional data, decrypted the manifest and each payload chunk with ChaCha20-Poly1305, decompressed each chunk with zlib, and concatenated the plaintext.
The container separated metadata from payload content. An 80-byte parameter structure supplied the four-byte magic value, key-unmasking material, shard order, and the offset at which the encoded key data began. HollowFrame reconstructed the 32-byte key from four eight-byte blocks, using the order `0, 2, 3, 1`, then authenticated the container magic as additional data during ChaCha20-Poly1305 decryption. Binding the magic value into authentication prevents an analyst or delivery intermediary from changing the container type without invalidating the tag.
The encoding concealed the configuration but did not protect the resulting traffic, which remained plaintext HTTP. By resolving WinHTTP functions at runtime, the DLL avoided exposing obvious networking APIs in its static import table. Because the backdoor ran inside updater.exe, its network and command activity appeared under the sideloaded Microsoft process.
This was a second trust transfer. The dropper did not need to perform Command and Control (C2) itself. Instead, it placed a familiar Microsoft executable beside a DLL that matched one of its expected dependencies. Windows loaded the local DLL into the updater process, moving the implant’s network and command activity into a process that appeared legitimate at a glance.
Matryoshka’s Quiet Command Channel
The sideloaded version.dll was a Rust HTTP command backdoor tracked as Matryoshka HTTP. It exported DllMain, GetFileVersionInfoSizeW, GetFileVersionInfoW, and VerQueryValueW. The Version API wrappers resolved matching functions from the legitimate Windows API, preserving host functionality while a worker thread initiated the implant logic.
The C2 address and port were stored as XOR encoded byte arrays and decoded with the repeating key 39 72 52 4C C1 76 EC 6F. The recovered endpoint was 45.158.196[.]184:8888 over plaintext HTTP. The DLL dynamically resolved WinHTTP functions and submitted JSON content.
Figure 5: HTTP component of Matryoshka loading in encoded configuration values
The XOR layer concealed configuration rather than protecting traffic. The repeating eight-byte key decoded the IP address and port at runtime, but the resulting session used plaintext HTTP. The implant dynamically resolved WinHTTP APIs instead of importing them directly, which kept obvious networking functions out of the static import table. Once loaded inside updater.exe, the HTTP activity, socket ownership, and subsequent command execution were attributed to the trusted-looking host process.
The protocol used JSON objects for registration, tasking, and results. Registration included a client identifier, username, hostname, and process ID. Task objects contained cmd, arg, ping, and seq, while commands were executed through cmd.exe. Results returned the matching sequence number with captured command output, allowing the server to correlate each response with its original task. The ping field provided a lightweight liveness check.
Command execution inherited the security context of the OneDrive updater. The DLL constructed C:\Windows\System32\cmd.exe /c <command> 2>&1, waited for the child process, collected the merged output, and returned it to the server. This meant the operator gained whatever filesystem, network, and domain access was available to the user or elevated process that launched the sideload host. The implant did not need dedicated credential theft or lateral movement modules because arbitrary shell access allowed those capabilities to be introduced with native utilities or follow-on tooling.
Analysis did not identify dedicated capabilities for screenshots, keylogging, browser credential theft, webcam access, or remote desktop control. Instead, the DLL functioned as a command backdoor, giving the operator shell access through the sideloaded process. From there, the operator could use built-in Windows tools or deploy additional payloads as needed.
Matryoshka’s GitHub Dead Drop
A separate Rust wtsapi32.dll recovered from the same intrusion is being tracked as Matryoshka GitHub. The DLL proxied 41 Windows Terminal Services exports and loaded C:\Windows\System32\wtsapi32.dll to preserve legitimate API behavior. Its background worker used the GitHub Contents API to communicate through the private adioziaete/memio repository, where it polled victim specific command files, submitted beacon and result data, and retrieved additional payloads.
The repository functioned as a collection of per-host mailboxes, with each victim assigned a dedicated <computer>_<username> directory. These directories contained beacon.json, cmd.json, result.json, and, in some cases, an upload/ tree for file delivery (Figure 6). This structure allowed the operator to manage tasking and results for individual endpoints through GitHub without maintaining a custom command server, while also leaving a versioned history of repository changes unless the associated commits or repository were removed.
Figure 6: Matryoshka’s per-victim Github tasking structure
Matryoshka GitHub used the GitHub Contents API to read and write files within the repository’s master branch. GET requests returned tasking and payload data through inline Base64 content or raw download URLs, while PUT requests created or updated beacon and result objects. Before updating an existing file, the implant retrieved its current Git blob sha, which GitHub required for replacement. The backdoor used the user agent OneDrive/24.170.0825.0001. The repository path, initiating process, user agent, and repeated access to task oriented JSON objects provided useful indicators for distinguishing the activity from legitimate GitHub usage.
The recovered tasking supported sequenced commands, arguments, and a built-in health check that returned a pongv2 response. Operator commands were passed to cmd.exe with UTF-8 enabled, while standard output and error were combined and captured through anonymous pipes before being returned to the GitHub tasking channel.
Analysis identified built-in reconnaissance capabilities beyond generic shell execution. The implant could collect network configuration, inspect local token and privilege details, identify domain controllers, enumerate domain computers and privileged group membership, and inventory installed software. Together, these capabilities could help an operator map an Active Directory environment, identify high value accounts and systems, and select appropriate follow-on tooling.
The upload path supported secondary payload delivery. The DLL could decode inline repository content or follow a GitHub download URL, expand environment variables in the destination path, write the file, and launch it with ShellExecuteW. It also supported module overloading into winspool.drv and a fallback VirtualAlloc, VirtualProtect, CreateThread sequence for raw payload execution.
The two in-memory execution paths offered different tradeoffs. The module overloading routine targeted winspool.drv, changed protections on module backed memory, copied the supplied bytes, and attempted to create a thread within that address range. This placed execution inside memory associated with a legitimate Windows module. The fallback path used VirtualAlloc, copied the payload into private memory, changed the region to executable with VirtualProtect, and called CreateThread. The fallback was simpler and more reliable, but it also generated the classic allocate, write, protect, and execute sequence that many endpoint products monitor.
Final Architecture
The full recovered architecture progressed from a phishing redirect and encrypted archive to a malicious shortcut, an XOR obfuscated PowerShell downloader, a staged Python sideload package, a fake Go python311.dll, the HollowFrame Go execution framework, and an encrypted loader_panda.exe dropper. That dropper delivered Matryoshka HTTP through a OneDrive sideload host. The same intrusion also produced Matryoshka GitHub, a Rust wtsapi32.dll implant that used GitHub for victim specific tasking, reconnaissance, file transfer, and in-memory payload execution.
Across the chain, each stage reduced the amount of malicious behavior visible in the stage before it. PowerShell handled staging and security control changes, the fake Python DLL handled authenticated decryption, HollowFrame handled persistence and execution selection, loader_panda.exe handled the final sideload setup, and the Matryoshka variants handled operator tasking. That separation complicated attribution and detection because no single component contained the full infection logic or complete C2 picture.
Indicators of Compromise
File Indicators
| Indicator | File Name | SHA-256 |
| Initial archive | avvoalert.zip | 55ed788ca7130089c4262cda8f0cb936eab9244bea68eeebe863842ba368e270 |
| Malicious shortcut | Case Documents.lnk | Hash not recovered |
| Python payload archive | Python-3.11.0-embed-amd96.zip | cb6b6289698f53111bb026ad5e95f841a03cf690bda670bb551e416e8cda77d3 |
| Python sideload host | python.exe | 14a89eda72e385f76bf15a7c4fd539c48837cf5df444a16f28c5b94f29799550 |
| Go sideload DLL | python311.dll | b2123d476646459234ab3083b79d13690d3864f9f5a9aa2b1272dfd502d0d3e2 |
| Bundled runtime | vcruntime140.dll | d66c3b47091ceb3f8d3cc165a43d285ae919211a0c0fcb74491ee574d8d464f8 |
| HollowFrame Go loader | N/A | d3bf01fce1f97f86aa58e9220e6dc1dae90005e5d7552f06192393d370355ed9 |
| Native dropper | loader_panda.exe | f882d0a0bf6f7fc687b9be6c7991d0bce4b60e81b742f5919933d07de37d625c |
| OneDrive sideload host | updater.exe | 015d7b212d20681d346e690159e7f4cd9e88b51de27e84b514fce865deef3a5c |
| Matryoshka HTTP backdoor | version.dll | f59f32c9af4fa8a5dbd4668df8893593bc0c4324816cbf9b956acedcbfb8cdb6 |
| Matryoshka GitHub backdoor | wtsapi32.dll | f96ff2f3abbff7f382ace509b90e54853b4b61c402ecde27d82f1c17b414867b |
Network Indicators
| Type | Indicator |
| URL | hxxps://avvoalert[.]info/ORYwAL |
| URL | hxxps://mega[.]nz/file/FqQVBZAC#Mux9rU6U2qT5S0dEq_1WdeyurWK4g57RwMzjXDmujjA |
| IP | 2.26.252[.]84 |
| URL | hxxp://2.26.252[.]84/Python-3.11.0-embed-amd96.zip |
| IP and port | 45.158.196[.]184:8888 |
| Domain | api.github[.]com |
| URI pattern | /repos/adioziaete/memio/contents/<victim-id>/ |
| User-Agent | OneDrive/24.170.0825.0001 |
Recommendations
- Hunt for Matryoshka GitHub tasking by correlating unexpected api.github[.]com connections from non-browser processes with proxy requests to /repos/adioziaete/memio/contents/, tasking JSON files, or the OneDrive/24.170.0825.0001 user agent.
- Detect signed binaries loading adjacent DLLs from user writable paths. Prioritize Python, updater, and other trusted executables launched from %TEMP%, %APPDATA%, or %LOCALAPPDATA% when they load python311.dll, version.dll, wtsapi32.dll, or other dependencies from the same directory.
- Monitor command shells spawned by unusual parent processes. Alert on cmd.exe or powershell.exe launched by updater processes, Python hosts, or applications that loaded non-system copies of version.dll or wtsapi32.dll. Include command lines containing chcp 65001 >nul &&, 2>&1, or other output capture patterns.
- Review scheduled tasks, WMI subscriptions, and Startup-folder persistence. Hunt for update themed task names, on-logon tasks with highest privileges, WMI CommandLineEventConsumer activity under root\subscription, and payload paths pointing into temporary or user-profile directories.
- Strengthen controls around password protected archives and shortcut files. Detonate encrypted archives and .lnk files in a controlled environment, retain PowerShell Script Block Logging and command line telemetry, and restrict unsigned or obfuscated PowerShell where operationally feasible.
- Constrain GitHub API access from non-development endpoints. Use proxy, application control, or identity aware policy to limit api.github[.]com access from unexpected processes and systems while avoiding broad blocking of legitimate GitHub traffic.
Conclusion
The central lesson from HollowFrame and Matryoshka was not simply that the actor used several loaders, but that each stage only became meaningful when viewed in context. A document themed lure, an obfuscated PowerShell command, a staged Python runtime, a fake python311.dll, a Microsoft updater, and GitHub API traffic could each be dismissed as isolated anomalies. Taken together, they showed a deliberate effort to move malicious execution through increasingly trusted components while obscuring the relationship between the initial user action and the final remote access capability.
HollowFrame established a durable execution layer by combining persistence with multiple payload launching techniques, while Matryoshka extended that access through HTTP command execution, GitHub based tasking, reconnaissance, file transfer, and secondary payload delivery. Together, HollowFrame and the Matryoshka backdoors formed a flexible intrusion chain that could maintain access and shift between execution methods and communication channels as the operation progressed.
HollowFrame and Matryoshka illustrate a broader defensive challenge: modern intrusion chains increasingly rely on fragmentation. By distributing malicious behavior across scripts, trusted binaries, proxy DLLs, encrypted containers, and cloud services, attackers reduce the chance that any single control will observe the full operation. Effective detection and response depend on reconstructing that context across endpoint, identity, process, and network telemetry, then treating the combined pattern as the intrusion rather than waiting for one artifact to provide definitive proof.
DATE PUBLISHEDJuly 30, 2026
AUTHORNevan Beal and Sam Decker
SHARE ON
The 2AM Test
Executive Guide
Most MDR evaluations focus on dashboards, not what a provider actually does when something is live. Learn the four questions that matter more than the demo: authority, speed, surface coverage, and proof.
*88–91% of ransomware attacks land outside business hours