吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 234|回复: 2
上一主题 下一主题
收起左侧

[Android 原创] 针对天天跳绳逆向抓包,刷作业的研究

[复制链接]
跳转到指定楼层
楼主
Liyunpeng 发表于 2026-8-12 10:13 回帖奖励

天天跳绳 (com.gkid.crazyrope) 逆向分析全链路

版本: 4.1.14
目标: 登录后自动完成所有体育作业, 可重复刷, 不被风控检测
环境: dotOS 5.1.3 (Android 11) + Magisk v30.7 root, PC=Kali Linux
设备: Redmi Note 5, 序列号 48c0f066
日期: 2026-08-11(最终更新 18:40)
作者: Leonzane

目录

  1. APK 基础信息
  2. 加固与反调试分析
  3. API 逆向提取
  4. 作业完成判定机制
  5. 提交接口数据结构
  6. 自动化工具详解
  7. 全自动登录模块(手机号+验证码+滑块)
  8. 风控防护设计
  9. 工具开发迭代与踩坑
  10. 附录: 完整接口清单

    1. APK 基础信息

    包名 com.gkid.crazyrope
    版本 4.1.14
    主进程 com.gkid.crazyrope
    作业游玩 Activity com.gkid.crazyrope/.ui.drillcommon.LandDrillActivity
    用户 UID bc4eb4e3-bd80-4043-8fd2-509412c0ff07
    用户 NO (formatUserNo) 0094199075
    核心 API 域名 api.tiantiantiaosheng.com

    旁路域名: api.tizhiyun.comapi-manage.tiantiantiaosheng.comcdn-data.tiantiantiaosheng.com

    2. 加固与反调试分析

    2.1 检测到的保护

    • vDexVMP / InnoSecure: 深度加固, 反调试反注入
    • DEX 加固: 实际业务代码在释放后的 DEX 中

      2.2 frida 注入测试结果(全部失败,已放弃

      方式 结果
      frida -U -f com.gkid.crazyrope (spawn) 早期注入可绕过 Java 未初始化错误, 但最终触发完整 Java 崩溃栈 (libart.so)
      frida -U <pid> (attach) PID 从 2336 自杀重启为 22516, 反调试检测到注入直接崩溃
      REPL 常驻 hook 崩溃输出完整 Java 崩溃栈 (fatal exception / libart.so)

      结论: vDexVMP/InnoSecure 检测到 frida 注入标记后直接让 App 崩溃 (art 层 fatal exception)。frida 注入 = 必被杀, 该路线彻底走不通, 放弃动态 hook。

      2.3 绕开方案(成功路线)

      不依赖动态 hook, 改用纯静态 smali 逆向 + 独立 Python 脚本直调接口。
      反编译产物: /tmp/ttts_all/classes*/
      关键模型: classes8/.../model/record/Record.smali (9453 行)

      3. API 逆向提取

      3.1 token 提取

      adb shell "su -c 'cat /data/data/com.gkid.crazyrope/shared_prefs/Settings.xml'"
      → 正则 name="LogInRsp">(.*?)</string>
      → html.unescape → json.loads → d['data']['token']
    • token 是 JWT, 存 /tmp/gkid_token.txt
    • payload: {"user_id":"bc4eb4e3-...","exp":1789026877000,"code":951410}
    • 错误码 41001 = token 截断/错误(PC 端提取时截断导致误判"账号风控锁定",实为 token 不完整)

      3.2 请求头(全套)

      Authorization: Bearer <token>
      x_littlelights_source: gkid
      X-Gkid-appVersion: 4.1.14
      X-Gkid-deviceName: Redmi Note 5
      X-Gkid-systemVersion: 11
      X-Gkid-uuid: <uid>
      X-Gkid-flavor: normal
      X-Gkid-no: 0094199075
      User-Agent: okhttp/4.9.0
      Content-Type: application/json; charset=UTF-8   (POST 时)

      3.3 作业相关接口

      接口 用途
      GET /api/homework/details_v7?start_date=YYYY-MM-DD&days=N 作业列表(release+homeworks), 精简版
      GET /api/homework/details 老版详情, 返回 BaseDrill 完整关联字段(plan_day_id/train_item_id)
      POST /api/user/drill_record_upload 作业记录提交接口(核心)
      GET /api/user/drill_record_list_v5?page=&page_size= 历史记录
      GET /api/user/today_homework_share?everyday_release_id= 服务端视角游玩统计
      POST /api/course/drill_playing 单动作游玩(仅 drill_name)
      POST /api/course/course_plan_playing 课程计划游玩(返回 CoursePlayingRsp)

      4. 作业完成判定机制(核心破解)

      4.1 关键发现线索(逆向排查过程)

  11. 提交记录能入库但未关联作业: 用 drill_record_upload 提交带 homework_id 的记录, 记录出现但作业 status=uncompleted, current_user_play_repeats=0
  12. 误判排除: 排出 reset_homework(code:-1)、课程组 group_record_id(属课程计划非作业)。
  13. 认知修正: 老板真实做切水果后 homework 848795922 变 done replay=1, 确认 drill_record_upload 确实是作业提交接口
  14. 服务端不靠 homework_id 字段: 脚本带 homework_id 提交仍未关联 — 服务端靠 record_id 的内容/上下文匹配
  15. record_id 格式差异(决定性线索):
    • 老板真实: 2a3be089-1786434502-0094199075-V2 (短uuid + unix秒 + no + -V2)
    • 脚本首次: 189de848-...-1786434386- (无 no、结尾 -) — 服务端解析失败

      4.2 根因(决定性突破)

      record_id 必须用 App 真实格式, 服务端从其中解析用户编号和时间戳来定位作业会话。

      4.3 Record 模型 record_id 生成逻辑(smali 还原)

      Record.smali 第 340-411 行, StringBuilder 拼接:

      record_id = UUID前9位(substring 0,9) + currentTimeMillis/1000 + "-" + formatUserNo + "-V2"

      即:

      <uuid前9位>-<unix秒>-<formatUserNo>-V2
      formatUserNo 来自 NetworkApi.getInstance().getFormatUserNo() = 0094199075(带前导0)。
      改进前后对比:
      record_id 服务端解析 作业关联
      错误 完整 uuid4()-秒-no-V2 no 提取失败, 结尾 - ❌ 不入库关联
      正确 uuid[:9]-秒-0094199075-V2 no 正确 + V2 ✅ 标 done

      4.4 Record.init(BaseDrill) 字段拷贝链(smali 第4730行起)

      依次调用:

      setMD5 → setDrill_id → setPlaying_type → setOrientation → setPlan_id →
      setCourse_id → setTraining_id → setDrill_group_id → setGroup_record_id →
      setUser_course_id → setCourse_plan_id → setUser_course_plan_id →
      setPlay_amounts(I) → setPlay_score(I) → setCompetition_id →
      setHomework_id(J) → setPlan_day_id → setTrain_item_id
    • plan_day_id 只在 BaseDrill.smali 中出现(唯一模型), 由服务端反序列化填充
    • BaseDrill 含: homework_id, plan_day_id, plan_day_id_v2, train_item_id, competition_id, play_score, play_amounts, play_seconds, record_type, group_id, group_record_id, entrance:"homework"

      4.5 达标判定(实测验证)

      对每个 homework 提交一条对应 drill 的达标记录即可让 release complete_count=1:

    • 当天刷当天作业status=done, done=15/15(完美完成)
    • 隔天补刷昨天作业status=delayed_done, complete_count=1(延迟完成, 同样算完成)

      4.6 delayed_done 语义澄清(关键认知修正)

      重要: delayed_done 不是反作弊/待复核标记, 而是补做/补交作业的正常完成状态
      判定代码实证: App 里 u0.smali / g2.smalid(String):Z(疑似 isDone)对 donedelayed_done 都返回 true —— 两者在 App 用户界面都显示为已完成
      实证对比:
      release 场景 状态 complete 达标用户
      368645970 8/11 当天作业当天刷 done 1 张婷婷(在 completion_patten_users)
      368222937 8/10 作业隔天补刷 delayed_done 1 张婷婷(在 completion_patten_users)

      结论: 刷作业假记录确实被服务端接受并达标——cheating_flag=False, 用户在 completion_patten_users(达标名单)里。delayed_done 只是"隔天补做"的完成态, 与 done 同等有效。

      5. 提交接口数据结构

      5.1 达标记录模板 (POST /api/user/drill_record_upload)

      {
      "user_id": "<uid>",
      "record_id": "<uuid前9位>-<unix秒>-0094199075-V2",
      "drill_id": "fruit_ninja_2103",
      "record_time": "2026-08-11 15:49:22",
      "display_name": "切水果",
      "metric_unit": "分",
      "metric_type": "score",
      "display_metric_unit": "分",
      "display_metric_base": 1,
      "success_count": 38,
      "succ_count": 38,
      "metric_count": 48,
      "metric_amount": 48,
      "score": 48.0,
      "playing_type": "fruit_ninja",
      "record_type": "体育作业",
      "homework_id": 848795922,
      "take_time": 60000,
      "duration": 60000,
      "begin_time": 1757xxxxxxx,
      "end_time": 1757xxxxxxx,
      "is_complete": 1,
      "finish_type": "score",
      "interactions": [ {每秒一条}
      }

      5.2 达标关键字段

    • metric_type=score, metric_amount = 要求秒数 × 5
    • record_type="体育作业"
    • homework_id = 该动作对应 homework_id
    • duration/take_time = 秒数 × 1000
    • is_complete=1, finish_type="score"
    • interactions: 每秒 1 条, 每条 duration≈1000ms

      5.3 interactions 结构

      {
      "interaction_id": "i0",
      "action_name": "a",
      "duration": 1000,
      "begin_time": 1757xxx,
      "end_time": 1757xxx+1000,
      "take_time": 1000,
      "score": 5.0,
      "succ_count": 1,
      "fail_count": 0,
      "error_count": 0,
      "distance": 0.0,
      "body": [],
      "target_object": []
      }

      6. 自动化工具详解

      6.1 工具清单(整合后)

      2026-08-11 整合: 已将原来的 ttts_batch_sweep.py / ttts_homework_auto.py / ttts_login.py 三个脚本全部并入单文件 Web 工具 ttts_sweep_web.py(登录模块已内嵌, 不再依赖外部 import)。散脚本已删除。只需一个文件即可完成全部功能 文件 位置 用途
      ttts_sweep_web.py ~/桌面/ 唯一主程序(自包含): 全自动登录 + 账号管理 + 多账号批量刷作业
      docker-compose.yml ~/桌面/ Docker 部署入口(推荐, 见末尾附章)
      accounts.json ~/桌面/ 账号数据(勿删, 保存所有账号 token/uid/no/设备指纹)
      start_web.sh ~/桌面/ 已删除(改 Docker 部署)
      ttts_batch_sweep.py ttts_homework_auto.py ttts_login.py ~/桌面/ 已删除(功能并入 Web)
      早备份: 渗透记录手册/逆向/天天跳绳.md ~/桌面/ 完整逆向+工具文档(含登录模块源码解析)

      注意: ttts_sweep_web.py 已内置登录模块全部函数(send_sms_code / login_with_sms / _pass_slider 等), 不再需要单独的 ttts_login.py

      6.2 通用设计原则(从零写工具的思考)

      工具本质就干三件事:

  16. 拉作业: 从 details_v7 拿到当前所有未完成的 release/homework
  17. 搭记录: 对每个 homework 构造一条真人化+达标的提交 JSON
  18. 提交+复查: POST drill_record_upload, 再拉一次确认 complete
    所有附加功能(随机化/延时/指纹轮换/幂等)都围绕防风控展开(详见第 7 章)。
    代码组织: 每个工具拆成"HTTP 封装 / record_id 生成 / 记录构造 / 作业获取 / 账号处理 / 主函数"六大块, 职责清晰、可独立测试。

    6.3 旧版单账号 ttts_homework_auto.py(历史·逐段讲解)

    ① 全局配置
    UID="bc4eb4e3-bd80-4043-8fd2-509412c0ff07"
    NO="0094199075"   # formatUserNo, 必须带前导0
    HOST="https://api.tiantiantiaosheng.com"
    TOKEN_FILE="/tmp/gkid_token.txt"
    ② 动作映射表(两个字典)
    PLAYING_MAP={  # drill_id -> playing_type
    "warm_up_small_step_2204":"warm_up_small_step",
    "fruit_ninja_2103":"fruit_ninja", ...}
    NAME_MAP={  # drill_id -> 中文名
    "warm_up_small_step_2204":"小碎步", ...}

    作用: 提交记录时 playing_typedisplay_name 必须和 drill_id 对应, 否则记录异常。这类映射是从 app 动作列表 + 真实记录反推出来的, 共 14 种动作。

    ③ token 加载(模块级一次性加载)
    def load_token():
    tok=open(TOKEN_FILE).read().strip()
    if not tok: print("! token 为空, 请先从手机提取"); sys.exit(1)
    return tok
    TOKEN=load_token()   # 模块加载时执行一次, 全局复用
    ④ HTTP 封装 api()
    def api(url, data=None, method=None):
    r=urllib.request.Request(HOST+url,
    method=method or ("POST" if data is not None else "GET"))
    r.add_header("Authorization","Bearer "+TOKEN)
    r.add_header("x_littlelights_source","gkid")
    r.add_header("X-Gkid-appVersion","4.1.14")
    r.add_header("X-Gkid-deviceName","Redmi Note 5")
    # ... 其余 X-Gkid 头 + UA
    if data is not None:
    r.add_header("Content-Type","application/json; charset=UTF-8")
    r.data=json.dumps(data, ensure_ascii=False).encode()
    ctx=ssl.create_default_context()
    try:
    return json.loads(urllib.request.urlopen(r, timeout=15, context=ctx).read())
    except Exception as e:
    return {"code":-1, "_err":str(e)}   # 网络错误统一包装成 code:-1

    设计要点:

    • 所有头固定拼死, 一处封装全局复用
    • 异常捕获返回 {"code":-1} 而非抛异常, 调用处统一判 code
    • ensure_ascii=False 保留中文(display_name 是中文)
      ⑤ record_id 生成(整个项目最关键的一行
      def make_record_id():
      return f"{uuid.uuid4().hex[:9}-{int(time.time()*1000)//1000}-{NO}-V2"
    • uuid.uuid4().hex[:9] → 前 9 位(hex 已去连字符)
    • int(time.time()*1000)//1000 → unix 秒
    • ${no} + -V2 → 与 App 完全一致
      ⑥ 提交一条记录 submit_record()
      def submit_record(drill, hw, sec, name, playing):
      now_ms=int(time.time()*1000)
      dur=sec*1000; base=now_ms-dur; N=sec; score=sec*5
      inter=[{...每秒一条 interaction, duration=1000, score=5.0...} for i in range(N
      r={
      "user_id":UID, "record_id":make_record_id(),
      "drill_id":drill, "record_time":time.strftime(...),
      "display_name":name, "metric_type":"score",
      "metric_amount":score, "score":float(score),
      "success_count":N, "succ_count":N,
      "playing_type":playing, "record_type":"体育作业",
      "homework_id":hw, "take_time":dur, "duration":dur,
      "begin_time":base, "end_time":now_ms,
      "is_complete":1, "finish_type":"score", "interactions":inter,
      }
      return api("/api/user/drill_record_upload", data=r)

      达标核心: metric_amount=sec*5, duration=sec*1000, 每个动作就是达标时长。

      ⑦ 作业获取 + 全量处理
      def get_releases(days=7):
      d=api(f"/api/homework/details_v7?start_date={time.strftime('%Y-%m-%d')}&days={days}")
      if d.get("code")!=0: return []
      return d["data"]["releases"
      def done_all():
      for rel in get_releases():
      rid=rel.get("everyday_release_id")
      hws=[h for h in rel["homeworks" if h.get("status")!="done"
      if not hws: print(f"release {rid}: 已全部完成 ✓"); continue
      for h in hws:
      p=h["homework_proto"
      drill=p["drill_id"; sec=p["each_group_count"
      playing=PLAYING_MAP.get(drill) or p["playing_type"
      name=NAME_MAP.get(drill) or p.get("drill_name") or drill
      submit_record(drill, h["homework_id"], sec, name, playing)
      # 提交完间隔2s复查一次

      6.4 主力工具 ttts_batch_sweep.py(历史·详细设计)

      相比旧版, 增强了 3 块: 多账号 / 真人化数据 / 完全幂等

      ① 命令行参数(argparse)
      python3 ttts_batch_sweep.py                     # 刷全部账号
      --accounts a.json   # 指定账号文件 (默认 accounts.json)
      --dry-run           # 只看计划不提交
      --no-random-order   # 关闭账号顺序随机
      --min-gap / --max-gap   # 账号间随机休息范围 (默认 8~25s)
      --account NAME      # 只刷指定账号 (匹配 name 或 no)
      --only-unfinished   # 只刷未完成 (兼容保留)
      ② 设备指纹池(多账号模拟不同手机)
      
      DEVICE_POOL=[
      {"brand":"Redmi","deviceName":"Redmi Note 5","systemVersion":"11","flavor":"normal"},
      {"brand":"Xiaomi","deviceName":"Mi 11","systemVersion":"12","flavor":"normal"},
      {"brand":"HUAWEI","deviceName":"Nova 9","systemVersion":"12","flavor":"normal"},
      {"brand":"OPPO","deviceName":"Reno 7","systemVersion":"13","flavor":"normal"},
      {"brand":"vivo","deviceName":"iQOO Neo6","systemVersion":"13","flavor":"normal"},
      {"brand":"Samsung","deviceName":"Galaxy A53","systemVersion":"14","flavor":"normal"},
账号配置里没写设备指纹时, 自动按 `index % 6` 轮换分配, 每个账号不同"手机"。
#### ③ 多账号 HTTP `api_req(acct, url, data)`
与旧版 `api` 的唯一区别: **头从 `acct` 字典动态取**, 而非写死全局常量:
```python
r.add_header("X-Gkid-deviceName", acct.get("deviceName","Redmi Note 5"))
r.add_header("X-Gkid-no", acct.get("no"))
r.add_header("X-Gkid-uuid", acct.get("uuid") or acct.get("uid"))

这样每个账号用自己的 token/uid/no/设备指纹。

④ 真人化记录构造 build_record()(对比旧版的核心升级)
def build_record(acct, drill, sec, name, playing):
    now_ms=int(time.time()*1000)
    eff_sec=max(sec, int(sec*random.uniform(1.0,1.12)))   # 时长随机但不低于达标
    dur=eff_sec*1000; base=now_ms-dur; N=eff_sec
    metric_amount=int(sec*5*random.uniform(1.0,1.08))     # 分数≥达标, 微随机
    success_count=max(1, int(N*random.uniform(0.85,0.98)))# 真人成功率<100%
    inter=[]; t=base
    for i in range(N):
        d=random.randint(900,1100)                          # 每个交互0.9~1.1s
        sc=round(random.uniform(3.0,5.0),1)                 # 分数3~5随机
        inter.append({...begin_time/t/end_time 连续累计...})
        t+=d
    return {...}   # 同旧版字段, 但所有数值已随机化

每个数值都加随机抖动, 让每次提交数据都不完全一样, 消除机器指纹。

⑤ 完全幂等判定(核心防重刷逻辑)
def sweep_account(acct, only_unfinished):
    for rel in get_releases(acct):
        rid=rel.get("everyday_release_id")
        hws=rel["homeworks"
        rel_complete = rel.get("complete_count",0) or 0
        if rel_complete>=1:                     # ← release 级已完成
            print(f"release {rid}: 已完成 跳过 ✓"); continue
        todo=[h for h in hws if h.get("status")!="done"
        if not todo: print("无待提交"); continue
        # ... 逐个 build_record + 提交 + sleep随机 0.8~2.2s
        # 提交完 sleep 2s 再复查新状态

关键: 用 release 级 complete_count>=1 判断(而非 homework 级 status), 因为 delayed_done 的 release homework 级永远是 uncompleted, 若按后者判断会反复刷同一套 → 风控暴露。改成 release 级判定后彻底不再重刷

⑥ 主函数流程
def main():
    1. 解析参数
    2. 加载 accounts.json
    3. --account 过滤 → 只留匹配账号
    4. 若未 --no-random-order: random.shuffle(accts)   # 账号顺序随机
    5. 为缺设备指纹的账号补指纹 (轮换池)
    6. 遍历每个账号:
         --dry-run → 只统计待刷数打印
         否则 → sweep_account() + 账号间 random.uniform(min_gap,max_gap) 休息

7. 全自动登录模块(手机号+验证码+滑块)

用途: 新手机号 → 自动过滑块 → 发验证码 → 验证码换 token → 自动入库 → 可刷作业
注(2026-08-11 整合): 登录模块函数已内嵌进 ttts_sweep_web.pysend_sms_code / login_with_sms / _pass_slider 等), 不再依赖单独的 ttts_login.py。本节的接口分析仍是权威参考。

7.1 登录完整链路(全自动, 已实测通过)

1. POST /api2/user/second_verification/get_coordinate_param  {phone, region:"86"}
(必须手机 H5 浏览器 UA + Referer: https://www.tiantiantiaosheng.com/apph5/)
→ {code:0, data:{req_value, token}}
2. resp_value = (123*req_value + 111) % 80 + 10        ← 纯算术, 无需图像识别/模拟拖动
3. POST /api2/user/second_verification/pic_check  {token, resp_value, v:null}
→ {code:0, data:{flag:true}}                     ← 滑块通过
4. POST /api2/user/send_login_code  {phone, region}     → 验证码真实下发
5. GET /api/user/sms_code_check?phone=&sms_code=          → 仅校验(返回空data), 不发token
6. POST /api/user/login  {phone, region, sms_code}        → 真正登录, 返回 data.token(JWT)

关键: 真正登录换 token 的是 第 6 步 POST /api/user/login(只校验不发 token / 返回空 data)。

7.2 滑块破解要点(自研算法, 无需图像)

  • 技术栈: 自研(无腾讯天御/极验等第三方验证码 SDK 特征)
  • chunk 文件: https://www.tiantiantiaosheng.com/apph5/js/76873.12645b18.js(104061 字节)
  • 核心算法: resp_value = (123 * req_value + 111) % 80 + 10 —— 纯算术, 拿到 req_value 后直接算, 不需要图像识别或模拟拖动耗时
  • 接口: get_coordinate_param(手机号)/ get_coordinate_param_v2(账号, 实测 404 不存在)/ pic_check

    7.3 「白名单」风控机制(重要)

    现象: get_coordinate_param{"code":1,"msg":"该phone没有在白名单里"}
    排查结论(已排除的因素):
    因素 测试 结论
    App UA vs 手机浏览器 UA 用 okhttp UA 被拒, 手机 H5 UA+Referer 首次成功 UA 是必要条件, 但非充分
    多 UA 变体 换 Pixel6 Chrome119、Redmi Note5、最新 Chrome 全部被拒
    冷等待 停 90~120 秒后单次纯净请求 仍被拒
    请求头(Referer/Origin/Cookie) 加完整 Referer+Origin、访问页面拿 cookie 仍被拒
    手机号本身 虚构未注册号 13800000001 也被拒 非号码注册判定

    当前判断: 「白名单」是该手机号当日的滑块挑战额度/风控——首次冷状态放行(进白名单), 完成一次后当日移出(额度用完)。首次实测通过、之后被拒符合此规律。
    影响评估: 对刷作业无影响。token 拿到后长期有效, 每号只需过一次滑块拿到 token 即可一直刷。批量场景下每号一次性成本可接受。

    7.4 Web 工具登录流程(新号接入)

    1. 浏览器打开 http://192.168.1.43:8088
    2. 「手机号+验证码 登录新账号」输入手机号 → 点「获取验证码」(后端自动过滑块+发码)
    3. 输入收到验证码 → 点「登录」→ 后端验证码换 token + 自动入库(去重) + 自动补 uid/no
    4. 新账号出现在「账号管理」列表, 可立即纳入「运行控制」批量刷作业

      8. 风控防护设计

      8.1 数据真人化(数值随机, 杜绝机器特征)

      字段 随机策略
      时长 eff_sec 要求秒数 + 随机0~12%(不低于达标)
      分数 metric_amount 要求秒×5 × [1.0, 1.08]
      成功次数 success_count 85%~98% 交互数(真人总有失误)
      单交互 duration 900~1100ms 随机
      单交互 score 3.0~5.0 分随机
      distance 0.5~2.0 随机
      时间戳 begin/end 按随机时长动态计算

      8.2 请求节奏(模拟真人操作)

  • 每个动作之间随机停 0.8~2.2 秒
  • 账号之间随机休息 8~25 秒
  • 账号处理顺序随机打乱

    8.3 设备指纹轮换(多账号)

  • Redmi / Xiaomi / HUAWEI / OPPO / vivo / Samsung 自动轮换
  • 每个账号用不同 deviceName / systemVersion / brand

    8.4 幂等 + 安全

  • release 级 complete_count>=1 即跳过(含 delayed_done) — 完全幂等, 不重复提交
  • 失败自动重试 + 全局节流
  • --dry-run 安全演练
  • 账号异常 try/except 隔离, 单个失败不影响其他账号

    9. 工具开发迭代与踩坑

    9.1 开发演进时间线(工具如何一步步成型)

    1. v0 手动验证 (gkid_details_old.py / gkid_submit_all.py): 逐个调接口试参数, 确认哪条能 complete
    2. v1 首次打通: 发现 record_id 格式问题, 用正确格式提交后 release 368645970 → done 15/15(关键里程碑)
    3. v2 批量化: 写 ttts_homework_auto.py 自动拉取+批量提交, 第二 release 补刷 → delayed_done
    4. v3 多账号+风控: 写 ttts_batch_sweep.py, 加多账号/设备指纹/真人化随机
    5. v4 彻底幂等: 发现 delayed_done 的 release 被反复刷, 改用 release 级 complete 判定 → 完全跳过

      9.2 踩坑实录(含代码级 bug 修复)

    6. record_id 用错格式 → 记录入库但不关联作业。修复: 改成 App 真实格式。这是唯一一个"改了配方立刻成功"的修复。
    7. token 截断导致 41001 → 误判"账号被风控"。修复: 重新完整提取 token。教训:先验证 token 是否完整再怀疑风控。
    8. delayed_done 被反复刷: v3 用 homework 级 status 判断, delayed_done 的 release homework 级永为 uncompleted → 每次跑都重刷。修复: 改用 complete_count>=1 release 级判定。
    9. hws 变量未定义 bug: 编辑删掉了 hws=rel["homeworks"] 但下方仍引用 → NameError。修复: 恢复赋值并做完整验证。
    10. shell heredoc parse error: exec 内嵌套 heredoc(含 >/$/引号) 触发 parse error near ')'。规避:一律写独立 .py 文件用 python3 执行。
    11. Web 工具前端 lg_slider 残留引用: 改全自动时删了手动滑块 UI 元素, 但 JS 函数里仍引用 $('lg_slider')/$('lg_slider_url') → JS 运行时访问 null 抛异常, 登录流程中断。修复: 删除 $('lg_slider') 相关代码。教训: 删 UI 元素时同步清理 JS 引用
    12. 前端手机号/验证码正则 \\d 双反斜杠: Python 三引号字符串里写的 \\d 到 JS 正则变成字面反斜杠, /^1\\d{10}$/ 永远匹配不到数字 → 手机号校验恒失败, 无法登录。这是真 bug。修复: 改单反斜杠 \d。教训: JS 嵌 Python 三引号, 反斜杠转义层叠, 写完后 node 模拟执行全流程验证正则

      9.3 通用踩坑(环境层面)

    13. frida 对强加固 App 不可用: vDexVMP/InnoSecure 检测注入即 art 崩溃, 别浪费精力, 转纯静态。
    14. $PATH 陷阱: exec 需先 export PATH=/usr/bin:/bin:/usr/local/bin:/usr/sbin:/sbin; frida/objection 在 ~/.local/bin 需额外 export。
    15. adb 变量陷阱: adb -s 48c0f066 需完整路径, $ADB 赋值会在新 shell 丢失。
    16. Kali Python 是管理式环境(PEP 668): pip 装 frida/objection 需 --break-system-packages
    17. frida-server 版本必须与 PC frida python 完全一致(当前 17.17.0)。
    18. Python re.search 转义: 独立 .py 中 re.search('"'+key+r'":\s*"?', s) 建议用 raw string 避免 SyntaxWarning: invalid escape sequence '\s'

      10. 附录: 完整接口清单

      10.1 用户/记录

  • GET /api/user/drill_record_list_v5?page=&page_size=
  • POST /api/user/drill_record_upload
  • GET /api/user/today_homework_share?everyday_release_id=

    10.2 作业 (HomeworkService)

    接口 用途
    GET /api/homework/details_v7 作业列表(精简)
    GET /api/homework/details 老版详情(含 BaseDrill 完整字段)
    GET /api/homework/head 作业头部
    POST /api/homework/update_master 更新作业
    GET /api2/homework/ai_teacher_info AI 老师信息
    GET /api2/homework/get_remind_info 提醒信息
    POST /api2/homework/set_remind_time 设置提醒时间
    GET /api/school/message 学校消息

    10.3 课程/游玩

  • POST /api/course/drill_playing (仅 drill_name)
  • POST /api/course/course_plan_playing (返回 CoursePlayingRsp)

    10.4 返回 BaseDrill 的接口(含 plan_day_id/train_item_id 等关联字段)

    UserService、GroupChallengeRsp$DataBean、ChallengeDataRsp$DataBean、InstitutionService、SmallSortDrillRsp$DataBean、PlayDrillRsp、PlanPlayRsp$DataBean$DrillGroupBean、**HomeworkDetailRsp$DataBean**、DrillListRsp、HomeServiceV2

    附: 本次成果数据

  • release 368645970 (8/11 布置, 15 个作业): status=done, done=15/15, complete_count=1
  • release 368222937 (8/10 布置, 15 个作业): status=delayed_done, complete_count=1
  • 均由老师"朝日格图"布置, 学生"张婷婷"名下显示完成
  • 全自动登录链(滑块+验证码)已实现并首次实测通过, 当前因该手机号当日滑块额度用完被拒, 等次日/换新号即可全自动验证
  • Web 工具最新版已修复前端 lg_slider 残留 + 手机号/验证码正则 bug, 运行于 http://192.168.1.43:8088
  • 唯一主程序: /home/leonzane/桌面/ttts_sweep_web.py(自包含单文件: 登录+账号+刷作业)
  • 部署方式: Docker 极简版(桌面 3 文件 + 一个 yaml), 见文档末尾「附: Docker 部署」
  • 动作映射表覆盖 14 种动作

    附: Docker 部署(极简, 推荐)

    2026-08-11: 已 Docker 化并精简。桌面只保留 3 个文件, 部署只需一个 compose + 一条命令。

    所需文件(桌面根目录)

    ~/桌面/
    ├── docker-compose.yml     # 部署入口(唯一 yaml)
    ├── ttts_sweep_web.py      # 主程序(yaml 挂载, 勿删)
    └── accounts.json          # 账号数据(持久化, 勿删)

    启动

    cd /home/leonzane/桌面
    # ⚠️ 桌面是中文目录名, compose 无法自动推项目名, 必须显式 -p
    docker compose -p ttts up -d
    # 浏览器: http://127.0.0.1:8088 (本机) / http://192.168.1.43:8088 (局域网)

    常用命令

    docker compose -p ttts up -d          # 启动
    docker compose -p ttts down           # 停止(数据在宿主机, 不丢)
    docker compose -p ttts restart        # 重启
    docker compose -p ttts logs -f        # 看日志
    docker compose -p ttts ps             # 看状态(应 Up healthy)

    设计要点

  • 用官方 python:3.12-slim 镜像, bind mount 挂载主程序+数据, 启动时自动 pip install flask无需自定义镜像/Dockerfile/entrypoint
  • 数据持久化: accounts.json 在宿主机, 容器重建/升级/重启账号不丢; 备份只需拷贝这一个文件。
  • 健康检查/api/status, 配 restart: unless-stopped 异常自动拉起。
  • 时区已设 TZ=Asia/Shanghai
    `

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
梅球王 发表于 2026-8-12 12:13
这个好,孩子体育老师暑假安排的可以搞定了
3#
wasm2023 发表于 2026-8-12 13:36
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-8-12 16:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表