/* * WeChat Android Full Hunter (Frida 17.x) * Targets WeChat 8.0.72 on Android * * Capabilities: * 1. Capture SQLCipher key from libWCDB.so (native hook) * 2. Monitor outgoing messages via NetSceneSendMsg constructor (Java hook) * 3. Bypass ExtContentProviderBase auth check (Java hook) * 4. Monitor message DB inserts via sqlite3_exec (native fallback) * 5. RPC: sendTextMsg(toUser, content) - actively send messages */ var TAG = "[WeChatFullHunter]"; var seenKeys = {}; var targetDbNames = ["EnMicroMsg.db", "EnMicroMsg"]; var javaHooksActive = false; function isTargetDb(path) { if (!path) return false; var s = path.toString(); for (var i = 0; i < targetDbNames.length; i++) { if (s.indexOf(targetDbNames[i]) !== -1) return true; } return false; } function bytesToHex(bytes, len) { len = len || bytes.length; var hex = ""; for (var i = 0; i < len; i++) { var b = bytes[i] & 0xFF; hex += (b < 16 ? "0" : "") + b.toString(16); } return hex; } function isPrintableKey(keyPtr, keyLen) { if (keyLen < 4 || keyLen > 64) return false; try { var raw = keyPtr.readByteArray(keyLen); if (!raw) return false; var bytes = new Uint8Array(raw); var map = {}, unique = 0; for (var i = 0; i < bytes.length; i++) { if (!map[bytes[i]]) { map[bytes[i]] = true; unique++; } } return unique >= 3; } catch (e) { return false; } } function reportKey(keyHex, keyLen, via, path, extra) { var cacheKey = keyHex + "@" + (path || ""); if (seenKeys[cacheKey]) return; seenKeys[cacheKey] = true; send({ type: "key", hex: keyHex, len: keyLen, via: via, path: path || "", extra: extra || {} }); } function reportInfo(msg) { send({ type: "info", msg: msg }); } function reportError(msg) { send({ type: "error", msg: msg }); } function reportMsgEvent(direction, toUser, content, type, extra) { send({ type: "msg", direction: direction, toUser: toUser, content: content, msgType: type, extra: extra || {} }); } // ═══════════════════════════════════════════════════════════════════════════════ // NATIVE LAYER: SQLCipher Key Capture + SQL Monitoring // ═══════════════════════════════════════════════════════════════════════════════ function hookNativeSqlite3Key() { var modules = Process.enumerateModules(); var hooked = []; for (var i = 0; i < modules.length; i++) { var mod = modules[i]; var name = mod.name.toLowerCase(); if (name.indexOf("wcdb") === -1 && name.indexOf("sqlcipher") === -1 && name.indexOf("wechat") === -1 && name.indexOf("sqlite") === -1) { continue; } try { var modObj = Process.findModuleByName(mod.name); if (!modObj) continue; var keyAddr = modObj.findExportByName("sqlite3_key"); if (keyAddr && !keyAddr.isNull()) { var label = mod.name + "!sqlite3_key"; (function (lbl, addr) { Interceptor.attach(addr, { onEnter: function (args) { var keyPtr = args[1]; var keyLen = args[2].toInt32(); if (!isPrintableKey(keyPtr, keyLen)) return; var hex = bytesToHex(new Uint8Array(keyPtr.readByteArray(keyLen))); reportKey(hex, keyLen, lbl, null, { native: true, v2: false }); } }); })(label, keyAddr); hooked.push(label); } var keyV2Addr = modObj.findExportByName("sqlite3_key_v2"); if (keyV2Addr && !keyV2Addr.isNull()) { var label2 = mod.name + "!sqlite3_key_v2"; (function (lbl, addr) { Interceptor.attach(addr, { onEnter: function (args) { var keyPtr = args[2]; var keyLen = args[3].toInt32(); if (!isPrintableKey(keyPtr, keyLen)) return; var hex = bytesToHex(new Uint8Array(keyPtr.readByteArray(keyLen))); reportKey(hex, keyLen, lbl, null, { native: true, v2: true }); } }); })(label2, keyV2Addr); hooked.push(label2); } } catch (e) {} } return hooked; } function hookNativeSqliteExec() { var modules = Process.enumerateModules(); var hooked = []; for (var i = 0; i < modules.length; i++) { var mod = modules[i]; var name = mod.name.toLowerCase(); if (name.indexOf("wcdb") === -1 && name.indexOf("sqlcipher") === -1 && name.indexOf("wechat") === -1 && name.indexOf("sqlite") === -1) { continue; } try { var modObj = Process.findModuleByName(mod.name); if (!modObj) continue; var execAddr = modObj.findExportByName("sqlite3_exec"); if (execAddr && !execAddr.isNull()) { (function (addr) { Interceptor.attach(addr, { onEnter: function (args) { var sqlPtr = args[1]; try { var sql = sqlPtr.readUtf8String(); if (!sql) return; var upper = sql.toUpperCase(); // Monitor PRAGMA key if (upper.indexOf("PRAGMA KEY") !== -1) { reportInfo("sqlite3_exec PRAGMA: " + sql); } // Monitor INSERT into message table (native fallback for msg monitoring) if (upper.indexOf("INSERT INTO MESSAGE") !== -1 || upper.indexOf("INSERT OR REPLACE INTO MESSAGE") !== -1 || upper.indexOf("INSERT INTO \"MESSAGE\"") !== -1) { send({ type: "sql_insert", table: "message", sql: sql }); } } catch (e) {} } }); })(execAddr); hooked.push(mod.name + "!sqlite3_exec"); } } catch (e) {} } return hooked; } function hookNativeSqlitePrepare() { var modules = Process.enumerateModules(); for (var i = 0; i < modules.length; i++) { var mod = modules[i]; var name = mod.name.toLowerCase(); if (name.indexOf("wcdb") === -1 && name.indexOf("sqlcipher") === -1 && name.indexOf("wechat") === -1 && name.indexOf("sqlite") === -1) { continue; } try { var modObj = Process.findModuleByName(mod.name); if (!modObj) continue; var prepAddr = modObj.findExportByName("sqlite3_prepare_v2"); if (prepAddr && !prepAddr.isNull()) { (function (addr) { Interceptor.attach(addr, { onEnter: function (args) { var sqlPtr = args[1]; try { var sql = sqlPtr.readUtf8String(); if (sql && sql.toUpperCase().indexOf("PRAGMA CIPHER") !== -1) { reportInfo("sqlite3_prepare_v2 cipher: " + sql); } } catch (e) {} } }); })(prepAddr); } } catch (e) {} } } // ═══════════════════════════════════════════════════════════════════════════════ // JAVA LAYER: Message Sending + ContentProvider Bypass // ═══════════════════════════════════════════════════════════════════════════════ function hookNetSceneSendMsg() { try { var NetSceneSendMsg = Java.use("u01.r0"); // Constructor: (String toUser, String content, int type, int flag, long time) NetSceneSendMsg.$init.overload('java.lang.String', 'java.lang.String', 'int', 'int', 'long').implementation = function(toUser, content, type, flag, time) { reportMsgEvent("outgoing", toUser, content, type, { flag: flag, time: time }); return this.$init(toUser, content, type, flag, time); }; // Constructor: (long msgId, int type, String toUser) - resend try { NetSceneSendMsg.$init.overload('long', 'int', 'java.lang.String').implementation = function(msgId, type, toUser) { reportMsgEvent("outgoing_resend", toUser, "", type, { msgId: msgId }); return this.$init(msgId, type, toUser); }; } catch (e) {} reportInfo("[JavaHook] NetSceneSendMsg constructors hooked"); return true; } catch (e) { reportInfo("[JavaHook] NetSceneSendMsg not found: " + e); return false; } } function hookContentProviderAuthBypass() { try { var ExtBase = Java.use("com.tencent.mm.plugin.ext.provider.ExtContentProviderBase"); // Method h() returns int (1 = success, others = error codes) ExtBase.h.implementation = function() { var original = this.h(); if (original !== 1) { reportInfo("[Bypass] ExtContentProviderBase.h() returned " + original + " -> forcing 1"); return 1; } return original; }; // Method b() returns boolean (isLogin) ExtBase.b.implementation = function() { return true; }; // Method i() returns boolean (app validation) try { ExtBase.i.implementation = function(context) { return true; }; } catch (e) {} reportInfo("[JavaHook] ExtContentProviderBase auth bypass installed"); return true; } catch (e) { reportInfo("[JavaHook] ExtContentProviderBase not found: " + e); return false; } } function hookMessageStorage() { try { // Hook MsgInfo (f9) setters to monitor message content being set var MsgInfo = Java.use("com.tencent.mm.storage.f9"); // u1 sets talker (toUser) try { MsgInfo.u1.implementation = function(talker) { this.u1(talker); // Store temporarily for correlation this._talker = talker; }; } catch (e) {} // c1 sets content try { MsgInfo.c1.implementation = function(content) { this.c1(content); if (this._talker) { reportMsgEvent("storage", this._talker, content, this.getType ? this.getType() : 0, {}); this._talker = null; } }; } catch (e) {} reportInfo("[JavaHook] Message storage (f9) hooked"); return true; } catch (e) { reportInfo("[JavaHook] storage.f9 not found: " + e); return false; } } function hookSendMsgEvent() { try { var SendMsgEvent = Java.use("com.tencent.mm.autogen.events.SendMsgEvent"); // Hook the event constructor to catch all send msg events // This is a broader catch than NetSceneSendMsg reportInfo("[JavaHook] SendMsgEvent class found (monitoring via constructor)"); return true; } catch (e) { reportInfo("[JavaHook] SendMsgEvent not found: " + e); return false; } } // ═══════════════════════════════════════════════════════════════════════════════ // RPC: Active Message Sending // ═══════════════════════════════════════════════════════════════════════════════ rpc.exports = { // Check if Java hooks are active getStatus: function() { return { pid: Process.id, arch: Process.arch, javaAvailable: (typeof Java !== 'undefined' && Java.available), javaHooksActive: javaHooksActive }; }, // Send text message via internal API // Requires Java.available = true and correct process sendTextMsg: function(toUser, content) { if (typeof Java === 'undefined' || !Java.available) { return { success: false, error: "Java not available in this process" }; } return Java.perform(function() { try { // Method 1: Use the builder pattern (u01.q1 -> u01.m1) var Builder = Java.use("u01.q1"); var R1 = Java.use("u01.r1"); // Create builder via factory var builder = R1.a(toUser); builder.e(content); // set content builder.h(0); // contactType (0 for normal contact) builder.f.value = 0; // internal flag builder.i.value = 4; // scene type // Build and submit var result = builder.a(); // returns u01.m1 if (result && result.a.value) { // Submit via m1.a() which calls modelbase.r1.h() var submitted = result.a(); return { success: submitted, msgId: result.b.value.toString() }; } return { success: false, error: "Failed to build NetScene" }; } catch (e) { // Method 2: Direct NetSceneSendMsg construction try { var NetSceneSendMsg = Java.use("u01.r0"); var now = Date.now(); var scene = NetSceneSendMsg.$new(toUser, content, 1, 0, now); // Get dispatcher and submit var Dispatcher = Java.use("com.tencent.mm.modelbase.r1"); var dispatcher = Dispatcher.e(); // singleton getter var ok = dispatcher.g(scene); return { success: ok, method: "direct_constructor" }; } catch (e2) { return { success: false, error: e2.toString(), fallbackError: e.toString() }; } } }); }, // Alternative: Send via ContentProvider (requires auth bypass) sendViaProvider: function(toUser, content) { if (typeof Java === 'undefined' || !Java.available) { return { success: false, error: "Java not available" }; } return Java.perform(function() { try { var Context = Java.use("android.app.ActivityThread").currentApplication(); var ContentResolver = Context.getContentResolver(); // Build URI for sendTextMsg // content://com.tencent.mm.plugin.ext.message/sendTextMsg?appid=wx123 var Uri = Java.use("android.net.Uri"); var uri = Uri.parse("content://com.tencent.mm.plugin.ext.message/sendTextMsg?appid=wx00000000000000&source=openapi"); // selectionArgs[0] = encrypted contact id, selectionArgs[1] = message // For bypass mode, we try raw wxid var selectionArgs = Java.array("java.lang.String", [toUser, content]); var cursor = ContentResolver.query(uri, null, null, selectionArgs, null); var result = { success: cursor !== null, hasCursor: !!cursor }; if (cursor) { try { if (cursor.moveToFirst()) { var msgIdIdx = cursor.getColumnIndex("msgId"); var retCodeIdx = cursor.getColumnIndex("retCode"); if (msgIdIdx >= 0) result.msgId = cursor.getString(msgIdIdx); if (retCodeIdx >= 0) result.retCode = cursor.getString(retCodeIdx); } } finally { cursor.close(); } } return result; } catch (e) { return { success: false, error: e.toString() }; } }); }, // Get available WeChat DB paths getDbPaths: function() { if (typeof Java === 'undefined' || !Java.available) { return { error: "Java not available" }; } return Java.perform(function() { try { var File = Java.use("java.io.File"); var basePath = "/data/data/com.tencent.mm/MicroMsg"; var base = File.$new(basePath); var paths = []; if (base.exists()) { var files = base.listFiles(); if (files) { for (var i = 0; i < files.length; i++) { var f = files[i]; if (f.isDirectory()) { var dbFile = File.$new(f.getAbsolutePath() + "/EnMicroMsg.db"); if (dbFile.exists()) { paths.push(dbFile.getAbsolutePath()); } } } } } return { paths: paths }; } catch (e) { return { error: e.toString() }; } }); } }; // ═══════════════════════════════════════════════════════════════════════════════ // LATE-HOOK SYSTEM: Monitor dynamically loaded modules // ═══════════════════════════════════════════════════════════════════════════════ function setupLateHook() { var alreadyHooked = {}; function tryHookModule(modName) { try { var modObj = Process.findModuleByName(modName); if (!modObj) return false; var hookedAny = false; var keyAddr = modObj.findExportByName("sqlite3_key"); if (keyAddr && !keyAddr.isNull() && !alreadyHooked[modName + "!sqlite3_key"]) { alreadyHooked[modName + "!sqlite3_key"] = true; (function (lbl, addr) { Interceptor.attach(addr, { onEnter: function (args) { var keyPtr = args[1]; var keyLen = args[2].toInt32(); if (!isPrintableKey(keyPtr, keyLen)) return; var hex = bytesToHex(new Uint8Array(keyPtr.readByteArray(keyLen))); reportKey(hex, keyLen, lbl, null, { native: true, v2: false, late: true }); } }); })(modName + "!sqlite3_key", keyAddr); hookedAny = true; reportInfo("Late-hook " + modName + "!sqlite3_key"); } return hookedAny; } catch (e) { return false; } } setInterval(function () { var modules = Process.enumerateModules(); for (var i = 0; i < modules.length; i++) { var name = modules[i].name; if (alreadyHooked[name]) continue; alreadyHooked[name] = true; var lname = name.toLowerCase(); if (lname.indexOf("wcdb") !== -1 || lname.indexOf("sqlcipher") !== -1 || lname.indexOf("sqlite") !== -1) { tryHookModule(name); } } }, 2000); } // ═══════════════════════════════════════════════════════════════════════════════ // MAIN // ═══════════════════════════════════════════════════════════════════════════════ function main() { reportInfo("=== WeChat Android Full Hunter started ==="); reportInfo("PID=" + Process.id + " | Arch=" + Process.arch); var javaAvail = (typeof Java !== 'undefined' && Java.available); reportInfo("Java.available: " + javaAvail); // 1. Native hooks (always work) var nativeKeyHooks = hookNativeSqlite3Key(); reportInfo("Native sqlite3_key hooks: " + nativeKeyHooks.length + (nativeKeyHooks.length ? " -> " + nativeKeyHooks.join(", ") : " (modules may load later)")); var nativeExecHooks = hookNativeSqliteExec(); reportInfo("Native sqlite3_exec hooks: " + nativeExecHooks.length); hookNativeSqlitePrepare(); // 2. Java hooks (if available) if (javaAvail) { Java.perform(function () { var results = []; results.push({ name: "NetSceneSendMsg", ok: hookNetSceneSendMsg() }); results.push({ name: "ContentProviderBypass", ok: hookContentProviderAuthBypass() }); results.push({ name: "MessageStorage", ok: hookMessageStorage() }); results.push({ name: "SendMsgEvent", ok: hookSendMsgEvent() }); var activeCount = 0; for (var i = 0; i < results.length; i++) { if (results[i].ok) activeCount++; reportInfo("[JavaHook] " + results[i].name + ": " + (results[i].ok ? "OK" : "FAILED")); } javaHooksActive = (activeCount > 0); reportInfo("Java hooks active: " + javaHooksActive + "/" + results.length); }); } else { reportInfo("Java VM not available. Using native-only mode (key capture + SQL monitoring)."); reportInfo("To enable send-msg features, attach to a process where Java.available=true."); } // 3. Late-hook for dynamically loaded modules setupLateHook(); reportInfo("=== All hooks installed. Waiting for events... ==="); } main();