Commit d7eda540e20c8d8c4f43e94d62e77f0aec57c432

Authored by yibo
1 parent 7ea91bae

fix(phoneuse): WeChatConnector 按当前登录 uin 定位账号目录+推导 key

旧逻辑只在缓存为空时按字典序取第一个账号目录、写死单一 SQLCipher key,
第二个账号登录后(目录名字典序更靠后)会静默套错账号+错误 key 导致读取失败。
改成每次都用 last_login_uin 算 MD5("mm"+uin) 定位目录、MD5(imei+uin)[:7] 推导 key,
账号切换时自动刷新缓存,公式与 lifelog-recorder/WeChatDb.kt 保持一致。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Showing 1 changed file with 41 additions and 12 deletions
phoneuse/connectors.py
@@ -134,29 +134,58 @@ class WeChatConnector(BaseConnector): @@ -134,29 +134,58 @@ class WeChatConnector(BaseConnector):
134 APP = "wechat" 134 APP = "wechat"
135 135
136 PACKAGE = "com.tencent.mm" 136 PACKAGE = "com.tencent.mm"
137 - CIPHER_KEY = "e03ce81" 137 + CIPHER_KEY = "e03ce81" # fallback only — real key is derived per-account, see _discover_db_path
138 138
139 def __init__(self): 139 def __init__(self):
140 super().__init__() 140 super().__init__()
141 - self._db_path = self._kb.get("db_path", "")  
142 - if not self._db_path:  
143 - self._db_path = self._discover_db_path()  
144 - if self._db_path:  
145 - kb = self._kb  
146 - kb["db_path"] = self._db_path  
147 - _save_kb(self.APP, kb)  
148 -  
149 - def _discover_db_path(self) -> str: 141 + # 每次都重新按当前登录的 uin 定位账号目录+key(便宜:一次 su grep),
  142 + # 跟缓存值不一致(换过账号)就刷新缓存。旧版只在缓存为空时按字典序取第一个目录
  143 + # 名字+写死 key,换账号后会静默套错账号(正是这次 jimmy 账号读不到的根因)。
  144 + current_path, current_key = self._discover_db_path()
  145 + cached_path = self._kb.get("db_path", "")
  146 + if current_path and current_path != cached_path:
  147 + self._db_path = current_path
  148 + self.CIPHER_KEY = current_key
  149 + kb = self._kb
  150 + kb["db_path"] = current_path
  151 + kb["cipher_key"] = current_key
  152 + _save_kb(self.APP, kb)
  153 + else:
  154 + self._db_path = cached_path or current_path
  155 + self.CIPHER_KEY = self._kb.get("cipher_key", current_key or self.CIPHER_KEY)
  156 +
  157 + def _discover_db_path(self) -> tuple[str, str]:
  158 + """定位当前登录账号的库+推导 key。key 公式(真机验证):
  159 + MD5("1234567890ABCDEF"+uin)[:7];账号目录名 = MD5("mm"+uin)。见 lifelog-recorder 的 WeChatDb.kt。
  160 + """
  161 + import hashlib
  162 + try:
  163 + uin = self._run_adb(
  164 + "grep -hoE 'last_login_uin\">[0-9-]+' "
  165 + "/data/data/com.tencent.mm/shared_prefs/*.xml 2>/dev/null "
  166 + "| grep -oE '[0-9-]+' | head -1"
  167 + ).strip()
  168 + if uin:
  169 + dir_hash = hashlib.md5(f"mm{uin}".encode()).hexdigest()
  170 + db_path = f"/data/data/com.tencent.mm/MicroMsg/{dir_hash}/EnMicroMsg.db"
  171 + exists = self._run_adb(f"[ -f {db_path} ] && echo yes || echo no").strip()
  172 + if "yes" in exists:
  173 + key = hashlib.md5(("1234567890ABCDEF" + uin).encode()).hexdigest()[:7]
  174 + return db_path, key
  175 + except Exception:
  176 + pass
  177 + # 兜底:读不到 uin(root 不可用/微信没登录)时退回旧的字典序取第一个目录,
  178 + # 用默认 key(只有单账号且是最早那个账号时才可能凑巧对)。
150 try: 179 try:
151 result = self._run_adb( 180 result = self._run_adb(
152 "ls /data/data/com.tencent.mm/MicroMsg/ 2>/dev/null | head -1" 181 "ls /data/data/com.tencent.mm/MicroMsg/ 2>/dev/null | head -1"
153 ) 182 )
154 for line in result.splitlines(): 183 for line in result.splitlines():
155 if len(line.strip()) == 32: # MD5 hash directory 184 if len(line.strip()) == 32: # MD5 hash directory
156 - return f"/data/data/com.tencent.mm/MicroMsg/{line.strip()}/EnMicroMsg.db" 185 + return f"/data/data/com.tencent.mm/MicroMsg/{line.strip()}/EnMicroMsg.db", self.CIPHER_KEY
157 except Exception: 186 except Exception:
158 pass 187 pass
159 - return "" 188 + return "", self.CIPHER_KEY
160 189
161 def _sqlcipher(self, sql: str) -> list[dict]: 190 def _sqlcipher(self, sql: str) -> list[dict]:
162 db_path = self._db_path 191 db_path = self._db_path