摘要

這份報告檢視一個在營運者圈內被識別為 JWR 的釣魚套件家族的客戶端工程,聚焦於三個技術層次:使用 Vue 的單頁式前端(Single-page front end)、擁有二進位即時傳輸(Binary real-time transport)的 Web Worker,以及用來偽裝外洩認證資料的 AES-256-CTR 加密信封。分析僅限於主要來源 [1] 所揭露的 Source artefact 與解密程式碼,並將此設計定位在既有研究中所記錄的釣魚套件工程演進脈絡之中。
號稱AES-256-CTR加密?JWR這款PhaaS其實漏洞百出 | 資訊安全新聞

1. 簡介

簡訊釣魚套件以往是靜態的 HTML 複製品,會等待表單送出後才傳送竊得的資料。此處檢視的套件捨棄了那種模型,改為在受害者輸入時,把表單輸入逐欄連續串流到後端營運者 [1] 。這讓釣魚套件從被動收集器轉變為即時互動式詐騙主控台:真人營運者觀察受害者的 session,並在流程中途下達導覽指令,依據已擷取到的內容選擇接下來要呈現哪個驗證挑戰。報告其餘部分重建套件的元件架構、認證資料模型、傳輸層密碼學,以及直接源自開發者設計選擇的偵測產物。

2. 元件架構

套件將關注點乾淨地分離到三個邏輯元件。Vue 2 單頁式應用程式負責繪製 漏斗頁面(Funnel page) 、執行客戶端欄位驗證,並維護記憶體中的受害者紀錄。專用的 Web Worker 是密碼學例行程序與傳輸層的唯一擁有者;它不直接存取 DOM,僅透過 postMessage 與主執行緒通訊。傳輸本身是雙路徑:二進位 WebSocket 通道承載即時雙向流量,而兩秒一次的 HTTP long-poll 則在 socket 無法使用時,作為相同指令串流的備援 [1] 。將加密金鑰隔離在 worker 範圍內,提高了以主控台進行事件應變的門檻;而將傳輸與 UI 解耦,則意味著營運者的通道可以替換,而不必更動漏斗頁面。

下圖重建主要來源所描述的訊息流,從初始頁面載入到 session 中途由營運者發出的指令:

sequenceDiagram participant V as Victim Browser (Vue SPA) participant W as Web Worker (WorkerCrypto) participant C as C2 Endpoint (/api/open/*, /webSocket/QT) participant O as Operator Console Note over V,W: Page bootstrap - main.js created()/mounted() hooks V->>W: postMessage(init, victim id JWRCVV-timestamp-rand-rand) W->>C: POST /api/open/getSyncSettings (AES-256-CTR envelope) C-->>W: encrypted config (key||iv||ciphertext, 32B+16B header) W->>V: postMessage(syncSettings: BIN lists, luhn_check, unattended_switch) Note over V: Victim reaches payment stage (c_pay.html) V->>W: postMessage(cvvform field update on keystroke) W->>C: WSS addCvv frame (encrypted per-keystroke snapshot) C->>O: forward live session state O->>C: dispatch instruction (e.g. to_sms_bank) C-->>W: encrypted instruction (WSS or 2s long-poll fallback) W->>V: postMessage(instructionConfig handler) V->>V: navigate to next funnel stage / render challenge

圖 1 — 訊息傳遞時序,涵蓋面向受害者的 SPA、擁有加密功能的 worker、C2 端點與營運者主控台。

3. 認證資料模型

單一物件(在還原的 bundle 中稱為 cvvform )驅動整個竊取漏斗(Harvesting funnel)。它的宣告在同一處揭示了整個工具包的蒐集範圍:身分欄位、政府證件影像(包含一種『手持』變體,旨在規避照片身分證的二次驗證)、支付卡欄位(搭配伺服端的 BIN 擴充)、三個獨立的網路帳號憑證欄位、一個專用的 PayPal 子路徑、裝置遙測,以及一個用於面板端分類的 operator-metadata block [1] 。特別是三個認證槽,顯示此漏斗的設計是在單一 session 內串接主要銀行、次要銀行與證券帳戶,而非只竊取單一組認證。

  1. // Representative excerpt of the cvvform declaration, reconstructed from the
  2. // primary source. Field names are verbatim; grouping comments are added here
  3. // for analysis and are not present in the original minified bundle.
  4. const _0x1570c0 = {};
  5. // -- Identity / KYC fields --
  6. _0x1570c0.firstname = '';
  7. _0x1570c0.lastname = '';
  8. _0x1570c0.ssn = '';
  9. _0x1570c0.passport_number = '';
  10. // _handheld_img fields capture a "selfie holding ID" photo, specifically
  11. // aimed at defeating liveness/KYC re-verification checks downstream.
  12. _0x1570c0.passport_handheld_img = '';
  13. // -- Payment card fields (13-field group) --
  14. _0x1570c0.cvv_cardnumber = '';
  15. _0x1570c0.cvv_expiry = '';
  16. _0x1570c0.cvv_cvv = '';
  17. // Populated server-side after a /api/open/binLookup round trip, not
  18. // entered by the victim -- this is enrichment, not raw capture.
  19. _0x1570c0.cvv_issuer = '';
  20. _0x1570c0.cvv_country = '';
  21. // -- Three independent web-account credential slots --
  22. // Design intent: chain primary bank -> secondary bank -> brokerage
  23. // inside a single victim session.
  24. _0x1570c0.web_login_account1 = ''; _0x1570c0.web_login_pwd1 = '';
  25. _0x1570c0.web_login_account2 = ''; _0x1570c0.web_login_pwd2 = '';
  26. _0x1570c0.web_login_account3 = ''; _0x1570c0.web_login_pwd3 = '';
  27. // -- Operator / panel metadata, not victim-entered data --
  28. _0x1570c0.operator_by = ''; // handling operator id
  29. _0x1570c0.online_status = ''; // victim currently on page?
  30. _0x1570c0.belong_to_template = ''; // campaign/brand self-tag, set from cfg.projectName

程式 1 — cvvform 竊取物件的註解節錄,顯示身分、付款、 multi-slot 認證與 operator-metadata 分組 [1]

belong_to_template 欄位值得單獨提出:它由建置時的專案名稱常數填入,並附加到每個外洩封包,功能如同自我宣告的活動標籤。由於該工具包不依附特定品牌,並且被重複用於彼此無關的冒充外觀,此單一欄位──依據主要來源──成為最強的可用歸因訊號,用來將看似無關的活動聚合回同一套操作者基礎設施 [1]

4. 傳輸層密碼學:帶內金鑰的 AES-256-CTR

在 WebSocket 通道與 HTTP application/octet-stream 端點上的每一則訊息都使用固定的封裝格式:先是以明文形式傳送的 32 位元組 AES-256 金鑰,接著是 16 位元組的 CTR 初始計數器,最後是以 AES-256-CTR 加密的 UTF-8 JSON payload。 [1] 。此設計的核心特徵在於其保密性只是幻象:因為金鑰與它所保護的密文一同以明文傳輸,該機制對任何能讀取傳輸線路的對象都不具防護效果。它的實際作用更狹窄且平凡 — 僅僅是避免被蒐集的 JSON 在隨意的封包擷取或代理日誌中顯示為可辨識的明文,這就足以規避天真型的自動釣魚偵測器,而無需任何真正的金鑰管理基礎設施。

這項結構性弱點也正是為何一旦讀過客戶端加密例行程序,此格式就可輕易還原。給定固定的 48 位元組 header,解密化簡為從 blob 前緣切出 key 與 IV,並對其餘部分執行標準 AES-CTR:

  1. #!/usr/bin/env python3
  2. # Minimal decryptor for the JWR envelope, reconstructed from the primary
  3. # source's WorkerCrypto.encrypt/decrypt logic. Comments below explain each
  4. # step and are added for this analysis.
  5. from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  6. import json, sys
  7. from pathlib import Path
  8. ENVELOPE_HEADER = 48 # 32-byte AES-256 key (clear) + 16-byte CTR IV (clear)
  9. def decrypt_envelope(blob: bytes):
  10. if len(blob) < ENVELOPE_HEADER + 1:
  11. raise ValueError(f"input too small ({len(blob)} B)")
  12. # The key and IV are NOT secret material here -- they are shipped
  13. # in the same message as the ciphertext they decrypt. This is the
  14. # root cause of the scheme's lack of real confidentiality.
  15. key, iv, ct = blob[:32], blob[32:48], blob[48:]
  16. # AES-CTR is a stream cipher mode: no padding, decrypt == encrypt.
  17. pt = Cipher(algorithms.AES(key), modes.CTR(iv)) \
  18. .decryptor().update(ct)
  19. return key, iv, pt # pt is expected to be UTF-8 JSON
  20. if __name__ == "__main__":
  21. for path in sys.argv[1:]:
  22. blob = Path(path).read_bytes()
  23. key, iv, pt = decrypt_envelope(blob)
  24. # Recovered plaintext is the operator's synced configuration,
  25. # or an exfiltrated cvvform snapshot, depending on the endpoint.
  26. print(json.dumps(json.loads(pt), indent=2, ensure_ascii=False))

程式 2 — 套件 WebSocket 與 HTTP 通道所用 48 位元組 key||IV||ciphertext 信封的重建最小解密器 [1]

將此例行程序套用到將此流程套用到一個已擷取到的 getSyncSettings 回應,可還原出一份由伺服器驅動的功能旗標文件:包含 BIN 允許/拒絕清單、一個 Luhn 檢查開關,以及一個 unattended_switch ,在啟用時允許工具包自行做出接受/拒絕決策,而不需人工操作者介入 [1] 。這證實了操作者的詐欺邏輯是外部化到設定檔中,而非 hardcoded 在程式裡,讓同一個客戶端建置能支援多個具有不同風險容忍度的活動。

5. 源自設計的偵測工程

由於該工具包的傳輸機制、儲存金鑰命名方式,以及驗證程式庫在彼此無關的活動中皆被共用,因此不論冒充的是哪個品牌,都會反覆出現相同的程式碼層級指紋 [1] 。「兩條偵測規則直接源自主要來源所揭露的構件:一條以 hardcoded WebSocket 路徑後綴的網路特徵簽章;以及一條以 JavaScript bundle 中共同出現的端點與識別字串的靜態特徵簽章。

# Suricata rule matching the hardcoded WebSocket access-token suffix.
# The literal path segment below is fixed across every recovered build
# and functions as an unintentional, high-confidence family marker --
# comment added to explain why each content match was selected.
alert tls $HOME_NET any -> $EXTERNAL_NET any
   ( msg:"JWR phishing kit - WebSocket access token in URL";
           content:"/webSocket/QT/"; http_uri;              # channel path prefix
           content:"/khkjsahfjkwhakjlsdwdddddd88"; http_uri; # hardcoded token suffix
           classtype:trojan-activity; sid:99000001; rev:1; )
// YARA rule for the main JS bundle. Each string targets a distinct
// architectural element identified in this analysis: REST endpoints,
// the per-victim id prefix, and the campaign self-tag field.
rule jwr_phishing_kit_main_js {
           meta:
           description = "JWR phishing kit - main bundle"
           strings:
           $a = "/api/open/addCvv"              // primary exfiltration sink
           $b = "/api/open/getPendingInstruction" // long-poll command target
           $c = "/webSocket/QT"                   // real-time C2 channel
           $d = "JWRCVV-"                          // per-victim id prefix
           $e = "belong_to_template"               // campaign self-tag field
           condition:
           4 of them
}

圖 4 與 5 — 依據主要來源 [1] 所揭露端點與識別字串建構的網路與靜態內容偵測規則。

6. 在釣魚即服務版圖中的定位

此工具包所展現的設計模式 — 一個持續維護的前端框架搭配專用的即時傳輸 — 與先前工具包家族研究中已可觀察到的更廣泛趨勢一致。同一研究脈絡下的一份較早 PhaaS 工具包分析,記錄了類似的操作者端功能旗標與品牌模板慣例 [2] ;而另一份針對銀行的工具包研究則報告了透過不同後端設計達成相似的即時面板同步。這顯示即時受害者工作階段控制正逐漸成為不同獨立開發工具包的收斂特徵,而非僅屬於某一工具包家族的獨有特性 [3] 。這種趨同反映了一個較早記錄的案例:一個以行動裝置為重點的套件結合簡訊、電子郵件與語音假託,大規模竊取單一登入認證與照片身分證件 [4] ;以及另一個單獨報告的即用型工具包,它將多品牌模板打包,並置於訂閱制支援模式之下 [5] 。合併閱讀,這些案例支持將「Live operator cockpit」架構視為商品化釣魚套件的新興基準期待,而非訂製創新。

7. 結論

此處分析的套件顯示,釣魚套件工程已趨向借用一般網頁應用程式開發的模式:元件分離的 SPA、worker 隔離的傳輸層,以及伺服器驅動的設定。讓套件在營運與跨活動重複使用上更具效率的同一套一致性,也正是讓它能持久被指紋化的原因 — 認證資料模型、傳輸信封形狀,以及 hardcoded access-token 後綴,在不同不相關的品牌冒充之間保持不變,為防禦者提供一組能在基礎設施輪替後存活的穩定程式碼層級與網路層級特徵。