PhantomSync: חבילות קריפטו של npm מסתירות את גנב הארנקים

PhantomSync: שמונה חבילות npm למפתחי קריפטו מסתירות טפטפת מושהית ומתמשכת

TL; DR

מפרסם יחיד של npm שלח שמונה חבילות קטנות ששמותיהן נראים כמו אבני בניין יומיומיות לפיתוח בלוקצ'יין וארנקים, base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers, ו crypto-validate-libכל אחד מהם מכיל כלי עזר פעיל. עם זאת, לאחר כלי זה מצורף בלוק קוד שפועל מעצמו ברגע שהמודול מיובא.

בערך 37 שניות לאחר הייבוא, הבלוק הזה מפענח מטען שנשלח בתוך החבילה במסווה של מתקן בדיקה, כותב אותו לקובץ מוסתר תחת ספריית הבית של המשתמש, רושם את עצמו להפעלה מחדש בכל login ב-Windows, macOS ו-Linux, ומפעיל את הסקריפט המפוענח כתהליך מנותק. שום דבר בנושא זה לא מופעל במהלך התקנת npm; הוא ממתין לייבוא ​​והפעלה של הקוד, וזהו רגע שקט יותר מההתקנה.

המטען אינו אטום. הוא נשלח כבסיס רגיל (base64), כך שפענוח "מתקן הבדיקה" משחזר את השלב השני במלואו: א. ארנק קריפטו וגנבת סודות שממתין עד שהמכונה תעמוד במצב סרק, אוסף מפתחות פרטיים וביטויי זרע, מצפין כל מציאה באמצעות מפתח RSA-4096 שמור, ומחלץ אותו על ידי הצמדתו לאחסון IPFS ציבורי, ומבצע העברת אות חזרה כל 12 שעות.

אנו עוקבים אחר האשכול כ פנטום סינכרוןכל שמונה החבילות היו פעילות ברישום npm בזמן הניתוח, ופורסמו תחת חשבון יחיד.

מערכת אקולוגיתNpm
מארזיםbase58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers, crypto-validate-lib
פלטפורמות ממוקדותחלונות, macOS, לינוקס
התנהגות ליבהמחק זמן ייבוא ​​מושהה, מוסתר כמתקן בדיקה; מתקין קביעה חוצת פלטפורמות
מטעןגנב סודות וארנקי קריפטו, הצפנת RSA-4096, חילוץ באמצעות הצמדת IPFS ציבורית

אנטומיה של התקפה

כל חבילה נראית לא יוצאת דופן בקריאה ראשונה. base58-utils, לדוגמה, הוא כמה קילובייטים של קוד עזר Base58 / Bitcoin-WIF ללא תלות - בדיוק מה שהשם מבטיח. הקוד הרלוונטי יושב לאחר של המודול מודול.יצוא, היכן שקורא המדלג על ראש הקובץ לא צפוי לחפש: פונקציה הקוראת את עצמה ומתזמנת את עצמה עם טיימר.

שרשרת הפעולות, שנבנתה ממקור החבילה, פועלת כך:

1. Package is required() by the host project
2. A self-invoking function schedules a callback ~37,000 ms later (setTimeout)
3. On fire, the callback reads test/fixtures/keypairs.dat (a base64 blob)
4. It base64-decodes that blob into a Node.js script
5. It writes the script to ~/.cache-db/.node-sync/syncd.js (mode 0o700)
6. It installs login-persistence for that script (see below)
7. It spawns "node syncd.js" as a detached process

שתי אפשרויות עיצוב בולטות.

המטען נע כמתקן ניסוי. השלב השני אינו כתוב כקוד ברור מאליו. הוא חי ב test/fixtures/keypairs.dat, קובץ base64 ששמו משתלב בחבילה שטוענת שהיא מטפלת בזוגות מפתחות. עבור אדם המעיף את הקובץ tarball, זה נראה כמו נתוני דוגמה; עבור הקוד המצורף, זהו סקריפט לפענוח והפעלה. קובץ ה-dropper עצמו אינו מכיל כתובת רשת - אלו שנמצאות בשלב השני - אך אין ערפול נוסף: ה-fixture הוא שכבה אחת של base64, כך שפענוחו (base64 -d) משחזר את מלוא syncd.js והתנהגות הרשת שלה. הסעיף הבא יסביר מה עושה שלב ההתאוששות הזה.

הפיצוץ מתעכב וקשור לייבוא, לא להתקנה. מכיוון שהטריגר הוא דורש () בנוסף לטיימר של כ-37 שניות במקום וו התקנה, ההתנהגות עוקפת בדיקות שצופות רק ב- התקנת npm שלב, והעיכוב נמשך זמן רב יותר מהרבה ריצות קצרות של ארגזי חול ו-CI. עד שמשהו מבוצע, ההתקנה שמשכה את החבילה כבר הסתיימה מזמן.

ברגע שהסקריפט המפוענח נמצא בדיסק ב ~/.cache-db/.node-sync/syncd.js — נתיב שנבחר לקרוא כמו ספריית מטמון שגרתית — הטפטפת מאפשרת לו לשרוד אתחול מחדש בכל שלוש הפלטפורמות העיקריות:

  • Linux you ערך cron שמפעיל מחדש את הסקריפט. הערך מותקן על ידי סינון ה-crontab הקיים דרך grep -v syncd, אשר יש לה תופעת לוואי של השמטת הערך החדש מרשימה רגילה שמשתמשת ב-grep עבור אותו שם.
  • Windows: משימה מתוזמנת בשם WinNodeSync, מוגדר להפעלה חוזרת במרווחים של 12 דקות.
  • MacOS: משימת השקה שכותרתה com.apple.syncd, הנמצא בכמה מהחבילות - תווית המחקה שירות מערכת לגיטימי של אפל.

הסקריפט מופעל מיד כתהליך מנותק, כך שהוא ממשיך לפעול לאחר סגירת תוכנית הייבוא.

מה עושה השלב השני

מכיוון שהמתקן הוא שכבת base64 אחת, השלב השני מפענח בצורה נקייה וניתן לקרוא אותו במלואו. כל שמונה החבילות נושאות אחת משלוש גרסאות של אותו סקריפט, המזהה את עצמו בהערת כותרת כ- phantom syncd v3 - topo durmiente ("חפרפרת ישנה"). תפקידה לגנוב חומרים מארנקי מטבעות קריפטוגרפיים וסודות מפתחים ולשלוח אותם מהמכונה. היא פועלת בשלבים הבאים:

  • 1. המתן עד שהמכונה תהיה במצב סרק. לפני שעושים משהו, syncd.js בודק כמה זמן המשתמש לא היה פעיל ועובר רק סף מסוים (כ-15 דקות) — אקספרינטיידל ב- Linux, ioreg HIDIdleTime ב-macOS, ושאילתת זמן סרק של PowerShell ב-Windows. לכן, קצירת נתונים נוטה להתרחש כאשר אף אחד לא נמצא ליד המקלדת.
  • 2. השיגו מתג הפעלה הנשלט מרחוקהסקריפט שולף קונפיגורציה קטנה מ-dead-drop לפני שהוא פועל. המקור העיקרי הוא כתובת URL גולמית של gist ב-GitHub (gist.githubusercontent.com/juang55/…/cfg.txt); הסקריפט מופעל רק אם תצורה זו קוראת פעיל=1אם התמצית אינה זמינה, היא חוזרת לשלושה מזהי תוכן IPFS מקודדים בקפידה, שנאספו דרך שערי הגישה הציבוריים. gateway.pinata.cloud, ipfs.io, ו cloudflare-ipfs.comזה נותן למפעיל בקרת דריכה/נטרול לאחר מעשה וגיבוי עמיד בפני פירוק. (הגרסה הקטנה ביותר, שנשלחה ב- lib אימות קריפטו, עוזרי ארנק-את', ו כלי עזר למפתחות סולאנה, הוסרה שכבת ה-gist ומסתמכת על מזהי ה-IPFS בלבד.)
  • 3. איסוף חומרי ארנק וסודות. הסקריפט עובר דרך ספריית הבית של המשתמש — ~/.config/solana, ~/.ethereum/מפתחות, ~/.יציקה, ~/.קסדה, ~ / .ssh, ו שולחן העבודה/דוחות/הורדות (כולל שמות תיקיות בשפה הספרדית), בנוסף ~/.env וקבצי shell rc, והמקבילה AppData מיקומים ב-Windows. הוא מכוון למפתחות פרטיים של את'ריום, מפתחות WIF של ביטקוין, ביטויי זרע של BIP-39, זוגות מפתחות של סולאנה, JSON של מאגר המפתחות של את'ריום, מפתחות SSH ומשתני סביבה נושאי סודות. הגרסה הגדולה יותר (ב- קידוד אבי, base58-utils, פיתוח את') נושא את מילון BIP-39 המלא בן 2048 המילים ומשתמש בביטויים רגולריים כדי תמצית מפתחות בודדים וביטויי זרע מאומתים מכל טקסט שהוא קורא; שני הגרסאות הקטנות יותר במקום זאת תואמות קבצים לפי מילת מפתח (זרע, שינון, ארנק, מטאסק, פנטום, פִּנקָס, Box, …) ולהעלות קבצים שלמים.
  • 4. הצפנה וחילוץ באמצעות שירות הצמדה ציבורי. כל ממצא מצורף לטביעת אצבע של המארח (שם משתמש@שם מארח, פלטפורמה, חותמת זמן) ומוצפנים באמצעות מפתח ציבורי RSA-4096 מקודד המוטמע בסקריפט. לאחר מכן, הרשומה המוצפנת מועלית על ידי הצמדתה ל-IPFS דרך api.pinata.cloud/pinning/pinJSONToIPFS, מאומת באמצעות אישורי API של Pinata מקודדים. אין שרת C2 ייעודי לתפוס: הנתונים הגנובים חונים באחסון ציבורי מבוזר, וניתן לאחזר אותם על ידי המפעיל באמצעות הגיבוב של התוכן שנוצר. ההעלאות מרווחות עם כמה שניות של ריצוד אקראי, ויומן מקומי של חותמת זמן | סוג מפתח | גיבוב מוחזר נשמר ב ~/.cache-db/.node-sync/.sl.
  • 5. התמידו והפעילו איתות במחזור של 12 שעות. השלב השני מקים מחדש את ההתמדה שלו - ערך cron בלינוקס, א com.apple.syncd משימת launchd ב-macOS, ומשימה מתוזמנת בשם WindowsNodeSync ב-Windows - מוגדר להפעלה חוזרת כל 12 שעות. שימו לב שזהו אחר שם משימת Windows מזו שה-dropper מתקין (WinNodeSync); שניהם שווים ציד.

ציר זמן

שמונה החבילות פורסמו ברצף צפוף ב-13 ביולי 2026 וב-14 ביולי 2026. לחלקן יש יותר מגרסה אחת; הטפטפת זהה בין הגרסאות, כאשר רק קיזוזי הקווים משתנים ככל שגודל קוד השירות השפיר שמעליו משתנה.

תַאֲרִיך אירוע
2026-07-13 החבילות הראשונות באשכול מופיעות תחת מפרסם אחד (solana-key-utils, eth-wallet-helpers, crypto-validate-lib וגרסאות מוקדמות של השאר)
2026-07-13 → 07-14 שמות נותרו וגרסאות המשך פורסמו; אותם טפטפות מצורפות נשלחות בכל אחת מהן
2026-07-14 כל השמונה סומנו ונותחו; כל החבילה עדיין ניתנת להתקנה מהרישום

לאורך ההתפרצות, המוניטין של המפרסם ברישום עבר ממצב ניטרלי פחות או יותר בתחילת האשכול לשלילי מאוד ככל שהצטברו גילויים - תופעת לוואי נצפית של סימון החבילות, לא תכונה מתוכננת.

אינדיקטורים לפשרה

כל האינדיקטורים שלהלן אושרו כקיימים בזמן הניתוח - אלו בטבלאות "השלב השני" על ידי פענוח. test/fixtures/keypairs.dat וקורא את מה ששוחזר syncd.js.

קבצים ונתיבים

אינדיקטור תפקיד
~/.cache-db/.node-sync/syncd.js שלב שני מפוענח, נכתב עם מצב 0o700
~/.cache-db/.node-sync/.sl יומן חילוץ מקומי (חותמת זמן | סוג מפתח | גיבוב IPFS)
test/fixtures/keypairs.dat מטען מקודד Base64 מוצג בתוך ה-tarball כ"מתקן ניסוי"

תשתית רשת שלב שני (שוחזרה מ-syncd.js)

אינדיקטור תפקיד
gist.githubusercontent.com/juang55/b298754cb72942b1cdcf02ccd45cde2f/raw/cfg.txt הפעלה ללא יציאה; הסקריפט פועל רק אם ההגדרות קוראות active=1
Qmcqz3w8j4qFQXDAXAxnrdc2oSX3nzBT4NqtpTqL8mr1ga גיבוי תצורת IPFS (CID)
QmdTXoqVmTHY1i4ZWLdLkoQ9YChp5TXPh5cWXwnAYZt5iF גיבוי תצורת IPFS (CID)
QmfJkLU5gdCpqbbqEjWYC2anXW9FmuEeSLLeLiHVJKYUjp גיבוי תצורת IPFS (CID)
gateway.pinata.cloud, ipfs.io, cloudflare-ipfs.com שערי IPFS המשמשים לאחזור גיבוי התצורה
api.pinata.cloud/pinning/pinJSONToIPFS נקודת קצה של חילוץ, נתונים גנובים מוצמדים ל-IPFS ציבורי
מפתח ה-API של פינאטה 13c766575b9270a9825d, אישור חילוץ קשיח

יעדי איסוף בשלב השני (שוחזרו מ-syncd.js)

אינדיקטור תפקיד
~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~/.ssh, ~/.envקבצי shell rc ספריות/קבצים שחיפשו אחר מפתחות וסודות
AppData\Roaming\Solana, AppData\Local\ethereum\keystore חיפוש מקבילות ל-Windows
סוגי חפצים שנאספו מפתחות פרטיים של ETH, WIF של ביטקוין, ביטויי זרע של BIP-39, זוגות מפתחות של סולנה, JSON של מאגר מפתחות של את'ריום, מפתחות SSH, משתני סביבה סודיים

ארטיפקטים של התמדה

פלטפורמה אינדיקטור
לינוקס השקת ערך cron syncd.js; מותקן דרך crontab מסונן grep -v syncd
Windows משימה מתוזמנת WinNodeSync (טפטפת) ו WindowsNodeSync (שלב שני)
MacOS תווית launchd com.apple.syncd
הכל השלב השני מתמשך שוב במחזור של 12 שעות

התנהגות

  • פונקציה הפעלה עצמית שנוספה לאחר מודול.יצוא, תזמון א setTimeout של ~37,000 אלפיות השנייה בעת ייבוא.
  • תהליך_ילד spawn("צומת", ) עם קבוצת האפשרויות המנותקת.
  • הפעלה במצב סרק בשלב השני (אקספרינטיידל / ioreg זמן סרק של HIDIdleTime / PowerShell; סף של ~15 דקות).
  • הצפנת RSA-4096 לפי מציאת הנתונים, ולאחר מכן HTTPS POST ל-API ציבורי של הצמדת IPFS.

חבילות וגירסאות (npm, publisher solbuilder_io)

חֲבִילָה גרסאות
base58-utils 1.0.0, 1.0.1, 1.0.3
abi-encode 1.0.0, 1.0.1, 1.0.2
eth-dev 1.0.0, 1.0.1, 1.0.2
arb-kit 1.0.0, 1.0.1
layer2-sdk 1.0.0, 1.0.1
solana-key-utils 1.0.0
eth-wallet-helpers 1.0.0
crypto-validate-lib 1.0.0

מוֹצִיא לָאוֹר

  • solbuilder_io - angel_lopez89[@]proton[.]me, דוא"ל לא מאומת, אין חשבון בקרת מקור מאומת, אין מאגר מקושר.

ייחוס והתנהגות נצפית

שמונה החבילות חולקות חשבון מפרסם אחד ומטען אחד. כל אחת מהן היא חבילה טריוויאלית - כמה קילובייטים של קוד שירות אמיתי עם אותו קובץ שחרור (dropper) מצורף - שפורסמה ללא מאגר מקושר ותחת כתובת דוא"ל חד פעמית לא מאומתת. האחידות הזו, נתיב השחרור המשותף, תוויות ההתמדה המשותפות, והמשותף keypairs.dat קבצי staging הם מה שקושרים את האשכול יחד.

החבילות גלויות באופן יוצא דופן לגבי התנהגותן. כלי עזר למפתחות סולאנה נושא הערות מוטבעות המתארות את הבלוק המצורף במונחים פשוטים - אחד מתיייג אותו פנטום: התמדה בלתי נראית, אחר (בספרדית) קורא הוצא תמונה ברקע, "להפעיל את החפרפרת ברקע." אלו הן ההערות של הקוד עצמו לגבי מה שהוא עושה; שם הקמפיין בפוסט הזה נלקח מהתווית הראשונה הזו יחד עם הצומת המזויף "sync daemon" (סינכרון) שמנגנון ההתמדה מחקה.

אנו מתארים רק את מה שהקוד עושה באופן נצפה. מתן שמות לחבילות - כל המונחים של קריפטו, ארנק וכלי בלוקצ'יין - מצביע על קהל המפתחים שסביר להניח שימשוך אותן בשמן; זה כשלעצמו לא קובע מי המפרסם. השלב השני היה ניתן לשחזור מלא על ידי פענוח מתקן base64, והתנהגותו מתוארת לעיל: הוא אוסף מפתחות ארנק, ביטויי זרע וסודות ומעביר אותם, מוצפנים ב-RSA, לאחסון IPFS ציבורי דרך Pinata. בחירת עיצוב בולטת היא היעדר שרת C2 פרטי - התצורה מגיעה מ-gist של GitHub ומ-IPFS, ונתונים גנובים חונים באחסון ציבורי מבוזר המבוסס על גיבוב תוכן, ששניהם קשים יותר לתפיסה מאשר מארח יחיד הנשלט על ידי תוקף. לא נמצא דיווח ציבורי קודם התואם לאשכול זה בזמן כתיבת שורות אלה.

השפעה, מגמות והנחיות למגינים

מי נחשף. כל מי שהוסיף אחת מהחבילות הללו לפרויקט Node ולאחר מכן הפעיל קוד שמייבא אותה. מכיוון שפיצוץ מתבצע בזמן ייבוא ​​ולא בזמן התקנה, נוכחות החבילה בלבד אינה מספיקה - אלא כל שימוש רגיל שטוען את המודול מגיע למטען. שמות הפיתוי מכוונים למפתחים הבונים על Ethereum, Solana, Arbitrum, Layer-2 וכלי ארנק/קידוד כלליים - האוכלוסייה שסביר להניח שיש לה בדיוק את הנכסים שהשלב השני מחפש. כל מי שהפעיל אחד מהמודולים הללו על מכונה המחזיקה מפתחות ארנק, ביטויי זרע, מאגרי מפתחות, מפתחות SSH או... .v סודות צריכים להתייחס לאישורים אלה כאל נחשפים ולסובב אותם.

שני דפוסים שכדאי להפנים. ראשית, מטען כמתקן: שליחת השלב השני כ-base64 בתוך קובץ נתונים בעל שם סביר (test/fixtures/keypairs.dat) שומר על המקור הגלוי של החבילה נקי ודוחף את התוכן הזדוני לקובץ שכלי סקירה וסקירות אנושיות מתייחסים אליו לעתים קרובות כנתונים לא פעילים. שנית, ביצוע מושהה בזמן ייבוא ​​עם התמדה חוצת פלטפורמות: העברת הטריגר מ-install hook, הוספת טיימר ולאחר מכן שמירה על פונקציות נוספות לאורך cron, משימות מתוזמנות ו-launchd היא צעד מכוון הרחק מטכניקות סקריפט ההתקנה הרועשות יותר שסריקת רישום אוטומטית עוקבת אחריהן מקרוב ביותר.

הנחיות למגינים ולמתחזקים:

  • טיפול בקוד המצורף לאחר מודול.יצוא כעדיפות לסקירה - לוגיקת טפטוף מסתתרת לעתים קרובות מתחת לפני השטח ה"אמיתיים" של המודול.
  • אל תניחו שקבצי נתונים אינם פעילים. בלון base64 תחת מבחן/משחקי לוח/ שנקרא בזמן ריצה ומפוענח הוא צמוד לקובץ הרצה; מסמן קריאות בזמן ריצה של קבצי fixture שמזינים א פונקציה/הערכה/לכתוב-ואז-להשריץ שַׁרשֶׁרֶת.
  • חפשו את נתיב הנפילה ~/.cache-db/.node-sync/ ולגבי יחידות ההתמדה WinNodeSync (משימה מתוזמנת) ו com.apple.syncd (launchd) על מכונות מפתחים שמשכו את השמות האלה.
  • התראה על תהליכי Node שיוצרים ערכי cron, משימות מתוזמנות או משימות launchd - ספריות לגיטימיות לעיתים רחוקות עושות זאת בעת ייבוא.
  • העדיפו קבצי נעילה וגירסאות מוצמדות, ובדקו את ההבדל של כל תלות קטנה חדשה של "כלי עזר", במיוחד חבילות ללא תלות שפורסמו על ידי חשבונות לא מאומתים ללא מאגר מקושר.

עבור מגיני רישום, המסקנה היא שניטור install-hook הוא הכרחי אך לא מספיק: מכשיר ייבוא ​​עם השהיית טיימר הממוקם בתוך קובץ נתונים יעבור בדיקה בזמן התקנה בלבד, ושלב ההתמדה הוא לרוב האות הנצפה ביותר שנותר לתפוס.

כלי SCA לניתוח קומפוזיציה - תוכנה
תעדוף, תיקון ואבטחת סיכוני התוכנה שלך
קבל את החשבון החינמי שלך.
אין צורך בכרטיס אשראי.

אבטחו את פיתוח ואספקת התוכנה שלכם

עם חבילת המוצרים Xygeni