#!/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 # 多设备时指定序列号 依赖:已安装 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()