156 lines
4.6 KiB
Python
156 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
通过 USB Type-C 连接的 Android 设备:提取 IMEI(或 Android ID/序列号)并输出其 MD5。
|
||
|
||
用法(在电脑上运行,手机用 Type-C 连上并开启 USB 调试):
|
||
python get_imei_md5.py
|
||
python get_imei_md5.py -s <device_serial> # 多设备时指定序列号
|
||
|
||
依赖:已安装 adb 且设备已授权。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import hashlib
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
from typing import Optional
|
||
|
||
|
||
def run_adb(serial: Optional[str], *args: str, timeout: int = 10) -> subprocess.CompletedProcess:
|
||
cmd = ["adb"]
|
||
if serial:
|
||
cmd.extend(["-s", serial])
|
||
cmd.extend(args)
|
||
return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||
|
||
|
||
def get_first_device() -> Optional[str]:
|
||
r = run_adb(None, "devices")
|
||
if r.returncode != 0:
|
||
return None
|
||
for line in r.stdout.splitlines()[1:]:
|
||
line = line.strip()
|
||
if not line or line.startswith("*"):
|
||
continue
|
||
parts = line.split()
|
||
if len(parts) >= 2 and parts[1] == "device":
|
||
return parts[0]
|
||
return None
|
||
|
||
|
||
def get_imei_via_dumpsys(serial: Optional[str]) -> Optional[str]:
|
||
r = run_adb(serial, "shell", "dumpsys iphonesubinfo 2>/dev/null")
|
||
if r.returncode != 0 or not r.stdout:
|
||
return None
|
||
# 匹配 "Device ID = xxx" 或 "Imei = xxx"
|
||
for pattern in (r"Device ID\s*=\s*(\S+)", r"Imei\s*=\s*(\S+)", r"IMEI\s*=\s*(\S+)"):
|
||
m = re.search(pattern, r.stdout, re.IGNORECASE)
|
||
if m:
|
||
raw = m.group(1).strip()
|
||
if raw.isdigit() and 14 <= len(raw) <= 16:
|
||
return raw
|
||
return None
|
||
|
||
|
||
def get_imei_via_service_call(serial: Optional[str]) -> Optional[str]:
|
||
r = run_adb(serial, "shell", "service call iphonesubinfo 1 2>/dev/null")
|
||
if r.returncode != 0 or "Parcel" not in r.stdout:
|
||
return None
|
||
# 解析 Parcel 中的 UTF-16 字符串(常见格式)
|
||
hex_part = re.sub(r"\s+", "", r.stdout)
|
||
hex_match = re.search(r"([0-9a-fA-F]{8})([0-9a-fA-F]+)", hex_part)
|
||
if not hex_match:
|
||
return None
|
||
try:
|
||
hex_str = hex_match.group(2)
|
||
if len(hex_str) % 4 != 0:
|
||
return None
|
||
s = "".join(
|
||
chr(int(hex_str[i : i + 4], 16))
|
||
for i in range(0, len(hex_str), 4)
|
||
if int(hex_str[i : i + 4], 16) < 0x10000
|
||
)
|
||
s = s.strip("\x00 ")
|
||
if s.isdigit() and 14 <= len(s) <= 16:
|
||
return s
|
||
except Exception:
|
||
pass
|
||
return None
|
||
|
||
|
||
def get_android_id(serial: Optional[str]) -> Optional[str]:
|
||
r = run_adb(serial, "shell", "settings get secure android_id")
|
||
if r.returncode != 0:
|
||
return None
|
||
raw = (r.stdout or "").strip()
|
||
return raw if raw and raw != "null" else None
|
||
|
||
|
||
def get_serial_no(serial: Optional[str]) -> Optional[str]:
|
||
r = run_adb(serial, "shell", "getprop ro.serialno")
|
||
if r.returncode != 0:
|
||
return None
|
||
raw = (r.stdout or "").strip()
|
||
return raw if raw else None
|
||
|
||
|
||
def md5_hex(s: str) -> str:
|
||
return hashlib.md5(s.encode("utf-8")).hexdigest()
|
||
|
||
|
||
def main() -> None:
|
||
parser = argparse.ArgumentParser(description="从 USB 连接的 Android 设备提取 IMEI/ID 并输出 MD5")
|
||
parser.add_argument("-s", "--serial", help="设备序列号(多设备时指定)")
|
||
parser.add_argument("-q", "--quiet", action="store_true", help="仅输出 MD5 一行")
|
||
args = parser.parse_args()
|
||
serial = args.serial
|
||
|
||
if not serial:
|
||
serial = get_first_device()
|
||
if not serial:
|
||
print("错误:未检测到已连接的 Android 设备。请用 Type-C 连接并开启 USB 调试。", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
raw_id: Optional[str] = None
|
||
source = ""
|
||
|
||
raw_id = get_imei_via_dumpsys(serial)
|
||
if raw_id:
|
||
source = "IMEI (dumpsys)"
|
||
|
||
if not raw_id:
|
||
raw_id = get_imei_via_service_call(serial)
|
||
if raw_id:
|
||
source = "IMEI (service call)"
|
||
|
||
if not raw_id:
|
||
raw_id = get_android_id(serial)
|
||
if raw_id:
|
||
source = "Android ID"
|
||
|
||
if not raw_id:
|
||
raw_id = get_serial_no(serial)
|
||
if raw_id:
|
||
source = "ro.serialno"
|
||
|
||
if not raw_id:
|
||
print("错误:无法获取 IMEI/Android ID/序列号(需设备已授权且部分机型需 root 才能读 IMEI)。", file=sys.stderr)
|
||
sys.exit(2)
|
||
|
||
md5_val = md5_hex(raw_id)
|
||
if args.quiet:
|
||
print(md5_val)
|
||
else:
|
||
print(f"设备: {serial}")
|
||
print(f"原始 ID 来源: {source}")
|
||
print(f"原始 ID: {raw_id}")
|
||
print(f"MD5: {md5_val}")
|
||
sys.exit(0)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|