ksjsb/tools/phone_swipe_solve.py
2026-07-30 20:25:56 +08:00

141 lines
5.6 KiB
Python

"""真机 adb 滑块求解 + 广撒 logcat 抓 result code。
决策逻辑(无需 result code 也可判):
- 活动从 KwaiWebViewActivity 变成别的 => result=1, 指纹过(真机路径可行)
- md5 变 + 活动仍是 KwaiWebViewActivity => fail(350002 缺口错 或 350014 指纹)
* 此时若我确信落点居中(Δ 居中于缺口) => 350014(号码被永久标记)
落点: Δ = hole_center_x - piece_home_center_x
piece_home_center_x = photo_left + (disX + cutPicW/2)*scale (disX=24, cutPicW=122)
hole: photo 区 <95 暗簇的加权质心(缺口纯黑核心)。
handle: 轨道带内最密簇(滑块按钮), 仅作 swipe 起点 x1(Δ 与 x1 无关, 只要落在按钮上)。
"""
from __future__ import annotations
import subprocess
import sys
import time
from pathlib import Path
import numpy as np
from PIL import Image
ROOT = Path(__file__).resolve().parent.parent
OUT = ROOT / "out"
SCALE = 947 / 686 # photo native->screen
PHOTO_LEFT, PHOTO_RIGHT = 66, 1013
PHOTO_TOP, PHOTO_BOT = 267, 819
DIS_X, CUT_W = 24, 122
PIECE_HOME_CX = PHOTO_LEFT + (DIS_X + CUT_W / 2) * SCALE # =183 (常量, 布局不变)
def adb(*args, binary=False):
r = subprocess.run(["adb", *args], capture_output=True,
text=not binary, timeout=60)
return r.stdout if not binary else r.stdout
def grab(path: Path):
data = adb("exec-out", "screencap", "-p", binary=True)
path.write_bytes(data)
return np.asarray(Image.open(path).convert("L"))
def top_activity():
out = adb("shell", "dumpsys", "activity", "activities")
for ln in out.splitlines():
if "topResumedActivity" in ln:
return ln.strip()
return "(none)"
def detect_hole(img) -> tuple[int, int, int]:
"""返回 (hole_center_x, cluster_left, cluster_right)。photo 区 <95 暗簇加权质心。"""
reg = img[PHOTO_TOP:PHOTO_BOT, PHOTO_LEFT:PHOTO_RIGHT]
dark = (reg < 95)
col = dark.sum(axis=0) # 每列暗像素数
hot = np.where(col > 25)[0] # 缺口列(暗像素密集)
if len(hot) == 0:
raise RuntimeError("no dark cluster (hole) found in photo")
# 切分成连续段, 取最宽那段(缺口), 再取其加权质心
breaks = np.where(np.diff(hot) > 5)[0]
segs = np.split(hot, breaks + 1)
seg = max(segs, key=len)
w = col[seg]
cx_local = int(round(np.average(seg, weights=w)))
cx = PHOTO_LEFT + cx_local
return cx, PHOTO_LEFT + int(seg[0]), PHOTO_LEFT + int(seg[-1])
def detect_handle(img) -> tuple[int, int]:
"""轨道带 y[920,980] 内最密簇(滑块按钮) -> (handle_cx, track_y)。"""
band = img[920:980, :]
nonwhite = (band < 235)
col = nonwhite.sum(axis=0)
hot = np.where(col > 20)[0]
if len(hot) == 0:
return 140, 950
breaks = np.where(np.diff(hot) > 8)[0]
segs = np.split(hot, breaks + 1)
# 滑块在最左; 取最左且较密的那段(x<300 范围内最密)
cand = [s for s in segs if s[0] < 320]
seg = max(cand, key=lambda s: col[s].sum()) if cand else segs[0]
hx = int(round(np.average(seg, weights=col[seg])))
return hx, 950
def main():
shot = OUT / "swipe_in.png"
img = grab(shot)
hole_cx, hl, hr = detect_hole(img)
handle_x, ty = detect_handle(img)
delta = int(round(hole_cx - PIECE_HOME_CX))
x1, y1 = handle_x, ty
x2, y2 = handle_x + delta, ty
print(f"[det] hole_cx={hole_cx} cluster=[{hl},{hr}] handle_x={handle_x} "
f"track_y={ty} piece_home_cx={PIECE_HOME_CX:.0f} => Δ={delta}", flush=True)
print(f"[det] swipe ({x1},{y1}) -> ({x2},{y2}) piece lands center @ {PIECE_HOME_CX+delta:.0f} "
f"(hole={hole_cx})", flush=True)
before_md5 = subprocess.run(
["certutil", "-hashfile", str(shot), "MD5"], capture_output=True, text=True).stdout
before = top_activity()
print(f"[pre] activity={before[:80]}", flush=True)
adb("logcat", "-c")
print(f"[act] adb input swipe {x1} {y1} {x2} {y2} 700", flush=True)
subprocess.run(["adb", "shell", "input", "swipe",
str(x1), str(y1), str(x2), str(y2), "700"], timeout=30)
time.sleep(4.0)
after = top_activity()
aft = OUT / "swipe_out.png"
img2 = grab(aft)
changed = not np.array_equal(img, img2)
same_photo = bool(np.array_equal(img[PHOTO_TOP:PHOTO_BOT, PHOTO_LEFT:PHOTO_RIGHT],
img2[PHOTO_TOP:PHOTO_BOT, PHOTO_LEFT:PHOTO_RIGHT]))
print(f"[post] activity={after[:80]}", flush=True)
print(f"[post] screen_changed={changed} same_photo={same_photo}", flush=True)
# 判定
closed = "KwaiWebViewActivity" not in after
if closed:
print("\n>>> VERDICT: ACTIVITY CLOSED => result=1, 真机指纹过 350014! 真机 WebView 路径可行。", flush=True)
elif changed and not same_photo:
print("\n>>> VERDICT: 图片刷新(新验证码) + 活动仍在 => FAIL。落点居中(Δ 居中缺口) => 350014(号码被标记)。", flush=True)
elif changed and same_photo:
print("\n>>> VERDICT: 仅拼图块位移, 图未刷新 => 滑动未完成/未触发 verify。", flush=True)
else:
print("\n>>> VERDICT: 无变化 => 滑块未动(handle 没抓到)。", flush=True)
# 广撒 logcat
print("\n===== LOGCAT (captcha/verify/result/350/http) =====", flush=True)
lc = adb("logcat", "-d")
for ln in lc.splitlines():
low = ln.lower()
if any(k in low for k in ("captcha", "verify", "350014", "350002", "35000",
"ksecretapi", "result", "okhttp", "--> ", "<-- ")):
print(ln[:200], flush=True)
if __name__ == "__main__":
raise SystemExit(main())