extract_from_electron_appdb.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. """Extract JWT token from 996box Electron app's localStorage (LevelDB).
  2. The app's web UI stores the login session in:
  3. C:\\Users\\<user>\\AppData\\Roaming\\996传奇盒子\\Local Storage\\leveldb\\
  4. Key is 'React-ant-Admin-user' (the redux user state from chunk.js code).
  5. Value is JSON: {"token": "eyJ...", "userinfo": {...}}
  6. We search all .ldb files for the JWT prefix (eyJ) and extract the full token.
  7. """
  8. import json
  9. import re
  10. import sys
  11. from pathlib import Path
  12. # 996box Electron app's userData path
  13. APPDATA = Path.home() / "AppData" / "Roaming"
  14. USER_DATA = APPDATA / "996传奇盒子"
  15. LEVELDB_DIR = USER_DATA / "Local Storage" / "leveldb"
  16. # JWT pattern: header.payload.signature (3 base64url segments)
  17. JWT_PATTERN = re.compile(r"eyJ[A-Za-z0-9_\-]{20,}\.eyJ[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}")
  18. # Also try base64-without-dots (in case storage truncates dots)
  19. LOOSE_PATTERN = re.compile(r'"token"\s*:\s*"(eyJ[^"]{50,})"')
  20. # Loose: just look for any long eyJ string
  21. LONG_EYJ = re.compile(rb"eyJ[A-Za-z0-9_\-\.]{80,}")
  22. def find_tokens_in_ldbs() -> list[dict]:
  23. """Scan all .ldb files, return candidate tokens."""
  24. candidates = []
  25. for ldb_file in sorted(LEVELDB_DIR.glob("*.ldb")):
  26. try:
  27. data = ldb_file.read_bytes()
  28. except Exception as e:
  29. print(f" [warn] can't read {ldb_file.name}: {e}")
  30. continue
  31. # Search for JWT-shaped strings
  32. for m in JWT_PATTERN.finditer(data.decode("utf-8", errors="ignore")):
  33. candidates.append({"file": ldb_file.name, "token": m.group(0), "kind": "jwt-3part"})
  34. # Also try loose match (in case of escaping)
  35. for m in LONG_EYJ.finditer(data):
  36. t = m.group(0).decode("utf-8", errors="ignore")
  37. # Filter to JWT-shaped (3 dots) or plausibly long single
  38. if t.count(".") == 2 and len(t) > 100:
  39. if not any(c["token"] == t for c in candidates):
  40. candidates.append({"file": ldb_file.name, "token": t, "kind": "loose-3dot"})
  41. elif len(t) > 200:
  42. # Very long base64 - could be truncated JWT
  43. if not any(c["token"].startswith(t[:50]) for c in candidates):
  44. candidates.append({"file": ldb_file.name, "token": t, "kind": "long-eyJ"})
  45. return candidates
  46. def main() -> int:
  47. print(f"=== Scanning {LEVELDB_DIR} for JWT tokens ===\n")
  48. if not LEVELDB_DIR.exists():
  49. print(f"[FAIL] {LEVELDB_DIR} does not exist")
  50. return 1
  51. candidates = find_tokens_in_ldbs()
  52. if not candidates:
  53. print("[!] No JWT-like strings found in LevelDB files")
  54. print(" Possible reasons:")
  55. print(" - 996box has never been logged in successfully on this machine")
  56. print(" - localStorage was cleared")
  57. print(" - Files are DPAPI-encrypted (Chrome v80+ with --enable-encryption)")
  58. return 0
  59. print(f"Found {len(candidates)} candidate(s):\n")
  60. for i, c in enumerate(candidates[:5], 1):
  61. t = c["token"]
  62. kind = c["kind"]
  63. print(f" [{i}] kind={kind} file={c['file']}")
  64. print(f" token[:40] = {t[:40]}...")
  65. print(f" length = {len(t)}")
  66. print(f" 3-part JWT = {'YES' if t.count('.') == 2 else 'NO'}")
  67. print()
  68. return 0
  69. if __name__ == "__main__":
  70. sys.exit(main())