141 lines
5.6 KiB
Python
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())
|