From d7eda540e20c8d8c4f43e94d62e77f0aec57c432 Mon Sep 17 00:00:00 2001 From: yibo Date: Sat, 8 Aug 2026 11:23:10 +0800 Subject: [PATCH] fix(phoneuse): WeChatConnector 按当前登录 uin 定位账号目录+推导 key --- phoneuse/connectors.py | 53 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/phoneuse/connectors.py b/phoneuse/connectors.py index f48cb8c..22ded99 100644 --- a/phoneuse/connectors.py +++ b/phoneuse/connectors.py @@ -134,29 +134,58 @@ class WeChatConnector(BaseConnector): APP = "wechat" PACKAGE = "com.tencent.mm" - CIPHER_KEY = "e03ce81" + CIPHER_KEY = "e03ce81" # fallback only — real key is derived per-account, see _discover_db_path def __init__(self): super().__init__() - self._db_path = self._kb.get("db_path", "") - if not self._db_path: - self._db_path = self._discover_db_path() - if self._db_path: - kb = self._kb - kb["db_path"] = self._db_path - _save_kb(self.APP, kb) - - def _discover_db_path(self) -> str: + # 每次都重新按当前登录的 uin 定位账号目录+key(便宜:一次 su grep), + # 跟缓存值不一致(换过账号)就刷新缓存。旧版只在缓存为空时按字典序取第一个目录 + # 名字+写死 key,换账号后会静默套错账号(正是这次 jimmy 账号读不到的根因)。 + current_path, current_key = self._discover_db_path() + cached_path = self._kb.get("db_path", "") + if current_path and current_path != cached_path: + self._db_path = current_path + self.CIPHER_KEY = current_key + kb = self._kb + kb["db_path"] = current_path + kb["cipher_key"] = current_key + _save_kb(self.APP, kb) + else: + self._db_path = cached_path or current_path + self.CIPHER_KEY = self._kb.get("cipher_key", current_key or self.CIPHER_KEY) + + def _discover_db_path(self) -> tuple[str, str]: + """定位当前登录账号的库+推导 key。key 公式(真机验证): + MD5("1234567890ABCDEF"+uin)[:7];账号目录名 = MD5("mm"+uin)。见 lifelog-recorder 的 WeChatDb.kt。 + """ + import hashlib + try: + uin = self._run_adb( + "grep -hoE 'last_login_uin\">[0-9-]+' " + "/data/data/com.tencent.mm/shared_prefs/*.xml 2>/dev/null " + "| grep -oE '[0-9-]+' | head -1" + ).strip() + if uin: + dir_hash = hashlib.md5(f"mm{uin}".encode()).hexdigest() + db_path = f"/data/data/com.tencent.mm/MicroMsg/{dir_hash}/EnMicroMsg.db" + exists = self._run_adb(f"[ -f {db_path} ] && echo yes || echo no").strip() + if "yes" in exists: + key = hashlib.md5(("1234567890ABCDEF" + uin).encode()).hexdigest()[:7] + return db_path, key + except Exception: + pass + # 兜底:读不到 uin(root 不可用/微信没登录)时退回旧的字典序取第一个目录, + # 用默认 key(只有单账号且是最早那个账号时才可能凑巧对)。 try: result = self._run_adb( "ls /data/data/com.tencent.mm/MicroMsg/ 2>/dev/null | head -1" ) for line in result.splitlines(): if len(line.strip()) == 32: # MD5 hash directory - return f"/data/data/com.tencent.mm/MicroMsg/{line.strip()}/EnMicroMsg.db" + return f"/data/data/com.tencent.mm/MicroMsg/{line.strip()}/EnMicroMsg.db", self.CIPHER_KEY except Exception: pass - return "" + return "", self.CIPHER_KEY def _sqlcipher(self, sql: str) -> list[dict]: db_path = self._db_path -- libgit2 0.21.4