Skip to content
Skillv1.0.0

network-protocol-reverse

Merged skill combining: protocol-reverse-engineering, har-to-proxy-script, s3-presigned-upload-reverse, web-font-anti-scraping.

by 7452323(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from 7452323/Nexus (Hermes/skills/reverse-engineering/network-protocol-reverse/SKILL.md). Install upstream with npx skills add 7452323/Nexus --skill network-protocol-reverse. Copyright stays with the author.

network-protocol-reverse

Merged from: protocol-reverse-engineering, har-to-proxy-script, s3-presigned-upload-reverse, web-font-anti-scraping Source count: 4


Table of Contents

  1. protocol-reverse-engineering
  2. har-to-proxy-script
  3. s3-presigned-upload-reverse
  4. web-font-anti-scraping

protocol-reverse-engineering

协议逆向工程

触发条件

用户需要:

  • 分析未知网络协议结构
  • 还原API请求/响应格式
  • 解析自定义二进制协议
  • 分析IoT/游戏/设备通信协议
  • 分析WebSocket游戏协议
  • 绕过客户端版本检查

工具链

protobuf-inspector(Protobuf专用)

# 安装
pip install protobuf-inspector

# 使用(无需.proto文件)
cat unknown_protobuf.bin | python -m protobuf_inspector
# 输出:自动推断的消息结构

netzob(通用协议逆向)

# 安装
pip install netzob

# Python API
from netzob.all import *

# 导入捕获的数据
messages = PCAPImporter.readFile("capture.pcap")

# 协议推断
from netzob.Inference.all import *
format = Format(messages)

Wireshark(网络抓包)

# 命令行抓包
tshark -i eth0 -w capture.pcap

# 过滤特定流量
tshark -r capture.pcap -Y "tcp.port == 8080"

分析流程

Step 1: 数据捕获

# 抓包
tcpdump -i any -w capture.pcap port 8080

# 或用Wireshark GUI

Step 2: 协议识别

# 检查是否为已知协议
file capture.pcap
strings capture.pcap | grep -i "http\|json\|xml\|protobuf"

# 检查是否为TLS/SSL
tshark -r capture.pcap -Y "tls"

Step 3: 消息格式分析

# Protobuf分析
cat message.bin | python -m protobuf_inspector

# 自定义二进制分析
# 1. 识别固定头部(长度/类型/序列号)
# 2. 识别分隔符
# 3. 识别字段边界
# 4. 推断数据类型

Step 4: 状态机推断

# 使用netzob
# 1. 导入多条消息
# 2. 自动推断状态转换
# 3. 生成协议模型

常见协议模式

TLV格式

[Type 1-4B][Length 1-4B][Value NB]

固定头部+载荷

[Magic 2B][Version 1B][Length 2B][Payload NB][CRC 4B]

长度前缀

[Length 4B][Data NB]

分隔符分隔

[field1]\r\n[field2]\r\n[field3]\r\n

知识库引用

  • ~/.hermes/knowledge/re-engineering/protocol-re/ — protobuf-inspector + netzob工具
  • references/websocket-version-check-bypass.md — WebSocket 游戏协议版本检查绕过实战(QQ农场案例)

Protobuf 逆向工具箱(从全网搜索吸收)

工具 用途 安装
protobuf-inspector 无.proto文件盲解析protobuf流 pip install protobuf-inspector
protorev protobuf逆向工作台,保留偏移/wire type/递归length-delimited候选/语料级字段存在性/草稿schema pip install protorev
protoc --decode_raw 官方原始解码(无类型信息) protobuf编译器自带
Protobuf Decoder (在线) 纯JS本地解码,无需上传 protobuf-decoder.serenetia.com
arkadiyt/protobuf-from-binary 从编译后二进制提取原始.proto定义 参考arkadiyt.com博客

Protobuf逆向流程:

1. 识别protobuf(wire type 0-5的字节模式)
2. protoc --decode_raw 或 protobuf-inspector 盲解析
3. protorev 保持证据链(偏移/wire type/字段存在性)
4. 多消息对比 → 推断字段类型和名称
5. 逐步定义字段 → 直到parser无需猜测
6. 从编译后二进制提取.proto(arkadiyt方法)

从编译后二进制提取.proto定义(arkadiyt方法):

  • Protobuf编译后在二进制中保留FileDescriptorProto
  • 搜索\n\x0C(Tag=1,Length=12)开头的protobuf编码
  • 或搜索字符串特征:.proto文件路径、message名、field名
  • 工具:protoc --decode=google.protobuf.FileDescriptorSet解码

CTF协议逆向方法论(从reverse-skill/CTF-Sandbox-Orchestrator吸收)

自定义协议重放5步法:

  1. 角色+流ID+端口+会话重置+握手边界+成功transcript
  2. 分离传输分段与应用消息
  3. 帧边界 → 方向序列 → 完整性字段(checksum/MAC/counter/nonce/signature)
  4. 压缩/编码/加密边界识别
  5. Accepted vs Rejected transcript delta → 最小重放证明

PCAP协议解码5步法:

  1. 流重组(不要从单包推理)
  2. 帧恢复+方向
  3. 压缩/编码/加密/传输边界
  4. 提取解码消息/传输对象
  5. 关联主机行为

常见pitfall: 混合不相关流(共享host/port但不同session state)/ 命名协议但不解码内容 / 未理解帧和状态依赖就尝试重放

其他工具

工具 用途
URH (jopohl/urh) 无线协议逆向(SDR支持)
Scapy 数据包构造/解析
CyberChef 数据编解码

har-to-proxy-script

HAR to Proxy Script — 抓包转代理脚本

转换流程

  1. 获取 HAR 文件(Charles/Surfboard/浏览器导出)
  2. 检查技能 references/ 目录是否有该 App/HAR 的现有分析 — 同名 HAR 文件名或同域名可能已有 pre-analysis,直接引用可跳过步骤 3-4
  3. 解析 HAR 提取请求/响应数据
  4. 识别目标端点和参数
  5. 选择脚本类型(Unlock/Checkin/Cookie/AdBlock)
  6. 生成对应平台脚本
  7. 测试脚本可用性

常用转换模式

场景 提取内容 生成脚本
解锁会员 响应 JSON 结构 响应体修改 Unlock 脚本
签到 Token/Cookie + API 地址 Cron 签到脚本
Cookie 采集 请求头 Cookie/Authorization Header 捕获脚本
去广告 广告 API 地址 Reject/空响应脚本

⚡ HAR 程序化分析脚本

直接在 Hermes 里跑这段 Python,自动提取所有域名和路径。

先用 execute_code(首选)。如果被拒绝(BLOCKED: cron mode 等安全限制),fallback 到 terminalpython3 -c

python3 -c "
import json, urllib.parse
with open('/path/to/file.har') as f:
    har = json.load(f)
entries = har['log']['entries']
# ... 粘贴下面的代码内容 ...
"

注意:terminal 里的 Python 字符串要用双引号套单引号避免 escape 问题(见下面的代码模板里的 \" 转义)。

execute_code 版本(推荐):

import json

with open('your.har') as f:  # 或从 /tmp/xxx.har 读
    har = json.load(f)

entries = har['log']['entries']

# 1) 域名分组统计
domains = {}
for e in entries:
    from urllib.parse import urlparse
    domain = urlparse(e['request']['url']).netloc
    domains[domain] = domains.get(domain, 0) + 1

print("=== 域名统计 ===")
for d, c in sorted(domains.items(), key=lambda x: -x[1]):
    print(f"  {c:>4}x  {d}")

# 2) 路径清单
print("\n=== 请求路径 ===")
seen = set()
for e in entries:
    url = e['request']['url']
    path = url.split('.com')[1].split('?')[0] if '.com' in url else url
    method = e['request']['method']
    status = e['response']['status']
    key = f"{method} {status} {path}"
    if key not in seen and 'budingscan.com' in url:
        seen.add(key)
        print(f"[{method:>4}] {status} {path}")

# 3) 关键词搜端点
print("\n=== 含 used/remain/count/consume 的路径 ===")
for e in entries:
    url = e['request']['url']
    path = url.split('.com')[1] if '.com' in url else url
    if any(kw in path.lower() for kw in ['used','remain','count','consume','deduct','cost','spend']):
        print(f"  [{e['request']['method']}] {path}")

# 4) 查看某个端点的完整响应
for e in entries:
    url = e['request']['url']
    if 'paid_modules' in url:
        text = e['response'].get('content', {}).get('text', '')
        if text.startswith('{'):
            obj = json.loads(text)
            print(json.dumps(obj, indent=2, ensure_ascii=False))
            break

🚨 签名检测 — HAR 分析第一步(此用户实战重要教训)

拿到 HAR 后,第一件事不是搜 is_vip,而是检查请求是否有签名验证

常见签名信号

签名类型 特征 示例
Header HMAC X-CC-Signature + X-CC-Access-Key-ID 句读/有诗 — 阿里云/火山引擎风格 HMAC-SHA256
URL 参数签名 URL 带 signature=sign=_sign= 有诗 — MD5 参数签名
JWT Token Authorization: Bearer <jwt>,body 加密 各种带 sk-
请求体加密 body 是 base64 或加密 hex 串 需要先解密才能看到字段

⚠️ iOS Swift App 签名验证的陷阱

核心事实:iOS 原生 App(Swift/ObjC)的签名 key 编译在 arm64 Mach-O 二进制中,且通常被混淆/加密。strings 搜不到签名 key 是正常情况,不要花精力在二进制里找 key。

实战判断流程:

  1. HAR 中看到签名 headers(如 X-CC-SignatureX-CC-Access-Key-ID
  2. 提取一次完整的请求和响应,尝试用常见 key 候选验签(空字符串、access key 本身、SHA256 of access key 等)
  3. 所有候选都匹配不上 → 签名 key 被混淆在二进制中,找不到
  4. 结论:直接放弃,iOS Swift App 改 response 方案不可行

为什么改 response 不生效(iOS Swift App):

  • 不是因为 response 有签名(response 没有签名),而是 App 本地存储了会员状态
  • 恢复购买完全走苹果收据本地验证,不走网络请求。HAR 里没有恢复购买请求 = App 根本没联网验证你的会员身份
  • 即使用户点了"恢复购买"显示成功,那也是客户端自己骗自己
  • 下一次请求 users/wechatusers/me 时,服务端仍然返回 is_member: false

北京才程科技(有诗/句读)的 App:

  • 签名位置:句读用 X-CC-Signature header,有诗用 URL signature= 参数
  • 同公司所有 App 都走同一套签名系统
  • 两次试错已浪费。响应体改得再完美也没用。
  • 不要再尝试同公司的任何 App(有诗、句读等)

仅限弱签名的 App 可以继续尝试:

  • 签名只用于请求完整性,响应体不签名 → 尝试改 response
  • 签名用的是固定 key,hack 后能重新计算 → 可以继续
  • 注意:即使签名是前端的(用于服务端验签),改 response 后客户端可能缓存或校验原始数据

⚠️ 服务端限制 vs 客户端限制 — 可行性预判

改 JSON 响应不是万能的。 有些限制在服务端,MITM 根本改不了。在分析 HAR 时,必须先判断限制类型:

限制类型判定矩阵

限制类型 特征信号 MITM 能解?
客户端开关 响应中有 is_vip: falseunlocked: falsefeature_enabled: 0 ✅ 改字段值
服务端过滤数据 响应中 steps: []content: null、关键字段被截断 ❌ 数据根本不发,改标志位也没内容
服务端次数验证 POST 请求到类似 paid_module_usedconsume_times 的扣减端点 ✅ Mock 返回 {code:0}
服务端签名校验 请求带 sk= JWT、api_signencrypt_text 加密参数 ⚠️ 只有无签名/弱校验的 key 才能生效
CDN 视频分段 视频 URL 带 v.0_10000.(低清试看)、或 Range 分段限制 ✅ URL-rewrite 替换版本号
客户端 UI 假锁 按钮灰色但 API 实际返回了所需数据 ✅ 改 unlocked/is_enabled 等标志
CDN Token 限时 视频 URL 带 t=expires=sign= 等时效参数 ❌ CDN 服务端控制,动态生成

实操判断方法

在 HAR 中找到锁定/未锁的对比样本:

  1. 找到两个同源 entry(一个免费、一个会员锁住的)
  2. 对比响应体结构:免费有 steps: [实际步骤]、会员 steps: []服务端限制,不了
  3. 对比 watch_typeunlockedis_prime 差异 → 可解的标志位
  4. 检查付费操作后的下一个请求:如果紧跟 POST paid_module_used/consume可拦截的扣减

什么时候该放弃(纯服务端限制)

  • 数据从服务端就不返回(steps 为空、content 被截断)
  • 签名校验强(JWT + nonce + timestamp,每次都变)
  • 视频 CDN 使用动态时效 Token(t=, us=, sign=
  • 菜谱步骤/文章正文不是分段 API 而是同个接口一次性返回

仍然能做的事(不要空手放弃)

  • 所有标志位全开(is_prime=true, unlocked=true, watch_type=1, expires_time=2099
  • 次数扣减全部 mock 成功
  • CDN 视频 URL-rewrite 替换试看版本号
  • 引导购买的文案/popup/banner 清空
  • 去广告

不可解限制的事后应对(本会话实战教训)

用户发现脚本能跑但部分功能仍然受限时:

  1. 不要跟用户争辩什么是服务端限制什么是客户端限制。用户只看结果。
  2. 回退到已知可用的版本git checkout <last-good-commit> -- script/xxx.js
  3. 只改一行就推一次,让用户逐条验证
  4. 用户说脚本废物时:承认问题,不解释限制原因,问具体哪个路径/功能还不行
  5. 别自己发挥:不加任何用户没要求的字段或替换

懒饭类混合限制模式(菜谱 App 参考)

懒饭、下厨房等菜谱 App 的会员限制是典型混合模式

  1. 服务端限制(不可解)recipe/page_detail 中会员菜谱的 steps: []tips: "购买会员..."
  2. 客户端标志(可解)unlocked: falsetruewatch_type: 2→1is_prime: false→true
  3. CDN 视频限制(可解):视频 URL v.0_10000.v. 替换为高清版
  4. 次数扣减(可解):培训计划/会员课程等操作后的扣减请求

HAR 关键端点清单(懒饭 v2.4.3):

端点 策略
user/prime.json is_prime:trueprime.expires_time:2099-12-31
recipe/page_detail.json unlocked:truewatch_type:1、清 tips
homepage/feed.json 改所有嵌套 unlocked:truewatch_type:1
story/get_v2.json unlocked:truewatch_type:1
plan/paged.json watch_type:1
prime/promotion_banner.json 设 null
chuimg.com 视频 URL URL-rewrite v.0_10000.v.

脚本结构(递归遍历 JSON,不用正则替换):

let body = $response.body;
try {
  const obj = JSON.parse(body);
  function unlock(obj) {
    if (!obj || typeof obj !== 'object') return;
    for (const k in obj) {
      const v = obj[k];
      if (k === 'is_prime') obj[k] = true;
      else if (k === 'unlocked') obj[k] = true;
      else if (k === 'watch_type') obj[k] = 1;
      else if (k === 'watermark') obj[k] = false;
      else if (k === 'tips' && typeof v === 'string' && v.includes('会员')) obj[k] = '';
      if (typeof v === 'object') unlock(v);
    }
  }
  unlock(obj);
  body = JSON.stringify(obj);
} catch (_) {}
$done({ body });

⚠️ 正则替换 vs JSON 解析 — 路线选择(关键教训)

如果用户给了已知可用的参考(如 Surge Body Rewrite 用纯正则),严格复制其替换方式,不要自做主张改成 JSON 解析版

纯正则可用的脚本,改为 JSON 解析 + JSON.stringify() 可能改变原始响应格式,触发客户端额外的兜底逻辑(如触发"1500+精选 限时特惠"弹窗)。已经工作的东西不要换底层。

决策规则

  1. 用户给了参考 → 照抄,同种替换方式。不要升格(纯正则→JSON解析)。
  2. 用户没给参考,自己分析 → JSON 解析更稳妥。
  3. 不要混搭 — 一个已生效的脚本中途不要改替换方式。
  4. 对用户说"这做不到"前,先确认:是否已经按用户给的参考试过了? 如果试了还不行,用户给的数据里是否存在你没覆盖的端点。
  • JSON 递归解析在性能和准确率上优于正则,但如果用户给的参考已经能工作(哪怕看起来简陋),保持不动
  • 三平台模块文件(.sgmodule / .plugin)同理:用户仓库里已有的格式就是标准,不要重新发明格式

扫描/翻译/OCR 类 App HAR 分析要点

这类 App(布丁扫描、扫描全能王、白描等)的付费校验分散在多个端点,分析 HAR 时需系统性识别:

分析步骤

  1. 按域名分组 — 先用脚本统计 HAR 中所有域名及请求数,找到业务主域名
  2. 识别会员信息端点 — 搜索响应体中含 user_typevipsubscribeend_time 的条目
  3. 识别付费模块端点 — 搜索含 paid_modulesproductspackages 的路径
  4. 识别次数扣减端点 — 🔑 关键:搜索 POST 请求,路径含 usedconsumedeductcostspend 的端点
  5. 识别剩余次数端点 — 搜索含 remainremainingcountusage 的路径
  6. 识别广告端点 — 搜索含 adbannersplash 的路径

⚠️ 容易漏掉的端点

路径特征 为什么容易漏 应该怎么处理
paid_module_usedconsume_timesdeduct_count 响应体小(200B+),通常是加密数据,容易被当作"不重要" 必须拦截,否则实际使用时次数会扣光
paid_module_usageremain_times 响应体也是加密的,看起来"动不了" 原样透传即可,次数扣减停了它就不重要
plansproductspackages 看起来只是展示,不影响功能 过滤掉续费计划可以减少用户看到付费弹窗
get_dynamic_configget_config 以为是客户端配置 检查是否有功能开关、白名单,可能有惊喜

加密体处理

有些端点的请求/响应体是加密的(如 encrypt_text 字段,base64 AES 加密)。对这些端点:

  • 向后端扣减的加密 POST(如 paid_module_used:直接拦截返回 {code:0, msg:"ok"},不需要解密
  • 向前端返回的加密 GET(如 paid_module_usage:原样透传不改,因为次数扣减已经停了
  • 不需要为了破解加密投入精力——服务端校验的端点直接 mock 成功即可

脚本模板快速生成

// Unlock 脚本模板
const url = $request.url;
if (url.includes('api/subscribe')) {
    let body = JSON.parse($response.body);
    body.data.vip = true;
    body.data.expire = '2099-12-31';
    $done({body: JSON.stringify(body)});
} else {
    $done({});
}

// 签到脚本模板
const cookie = $prefs.valueForKey('cookie_name');
$httpClient.get({url: 'https://api.example.com/checkin', headers: {Cookie: cookie}}, function(err, resp, data) {
    if (err) $done();
    $notification.post('签到结果', '', data);
    $done();
});

🎯 签到脚本实现模式(酷我音乐案例)

当目标 App 有签到/积分功能,但 HAR 中没有签到执行请求(因为已签到过了),可以反向利用已有的开源签到脚本来推导接口:

方法

  1. 从 HAR 分析业务域 — 找到 API 主域名(integralapi.kuwo.cn
  2. 从 HAR 提取认证参数 — 提取 loginUidloginSid 等认证参数
  3. 从已有开源脚本找 API 定义 — 已经有别人写好的签到脚本(如 kuwo.js)包含了所有任务接口
  4. 结合两个信息源
    • HAR 确认了当前 App 版本的域名/参数格式
    • 开源脚本提供了接口路径和参数格式
  5. 验证:用 doListen?from=mobile 等接口模拟任务,用 taskList 验证状态变化

实际案例(酷我音乐升级脚本)

来源 提供的信息
HAR 包 (integralapi.kuwo.cn) 域名、认证参数(loginUidloginSid)、请求头格式
kuwo.js (General74110) 任务 API 路径 (doListen)、参数 (from 不同任务类型)
升级系统 taskList 响应 任务类型列表(sign/music/novel/comment/advert)

执行任务接口(酷我音乐积分系统):

GET /api/v1/online/sign/v1/earningSignIn/everydaymusic/doListen
  params: loginUid, loginSid, from= {mobile|novel|collect|videoadver|sign|comment|clock}, goldNum= {18|58|10|...}

升级系统状态查询:

GET /openapi/v1/usersystem/taskList
  params: appUid, loginUid, loginSid, version, src

升级等级查询:

GET /openapi/v1/usersystem/userRank
  params: appUid, loginUid, loginSid, type=1

何时使用此模式

  • HAR 中只有状态查询(taskListtodayStatus),没有任务执行请求
  • 目标 App 有成熟的第三方签到脚本
  • 签到任务 + 升级任务是两套独立 API 系统,但共享同一套认证

注意事项

  • HAR文件可能很大(50MB+),优先用 curl 直接下载到 /tmp/
  • 关键数据在 log.entries[].response.content.text(响应体)和 request.postData.text(请求体)
  • log.entries[].request.url 是最重要的识别字段
  • Telegram 传输 HAR 被拒 — 先在 /root/.hermes/config.yaml 里查 gateway.max_file_size_mb(默认只有 10MB)。修复命令:hermes config set gateway.max_file_size_mb 200。注意重启网关后方生效。禁止上来就说"TG限20MB"——先查自己的配置再下结论。
  • 大部分 .har 里的响应体是脱敏/空的(Charles 导出选项),需用真实设备+代理抓包
  • Charles 导出的 .har 可能包含多个域名,先提取目标域名条目再分析
  • ⚠️ QX 导出的 HAR 可能只含系统加密连接 — Quantumult X 导出 HAR 可能只有 MMTLS(微信)、Apple ls(gspe1-ssl.ls.apple.com)等加密连接,无 App HTTP API 流量。第一步域名统计如果只有这些系统域名,直接告诉用户 HAR 无效需重抓。
  • HAR 无效时的 Bundle ID 查询 — 用 iTunes API 直接拿:curl -s "https://itunes.apple.com/lookup?id=APP_ID" | python3 -c "import json,sys; [print(r.get('bundleId')) for r in json.load(sys.stdin).get('results',[])]"

🔍 Rewrite 匹配精度规则(HAR 在手的铁律)

当你有 HAR 数据(抓包已知确切 URL)时,rewrite 正则必须精确匹配,不要加没必要的通配符。

错误写法(本会话被骂过的)

/* ❌ 明明 HAR 里抓到了 api.revenuecat.com/v1/subscribers/xxx
   却写了通配子域名 + 两个域名 + 两个版本 + 额外端点匹配 */
^https?:\/\/([a-z0-9-]+\.)*revenuecat\.com\/(v[12]\/)?(receipts$|subscribers\/[^?#]+)
^https?:\/\/([a-z0-9-]+\.)*rc-backup\.com\/(v[12]\/)?(receipts$|subscribers\/[^?#]+)

正确写法

/* ✅ HAR 里只有这一个精确 URL,就只写这一个 */
^https:\/\/api\.revenuecat\.com\/v1\/subscribers\/.+ url script-response-body ...
^https:\/\/api\.revenuecat\.com\/v1\/subscribers\/.+ url script-request-header ...

[mitm]
hostname = api.revenuecat.com

规则

  1. HAR 里有精确 URL → 写精确正则。不要自加泛化(备份域名、子域名通配、版本范围)。
  2. 每多加一个 |\.*(v[12])(receipts|subscribers) 都是多余的,除非 HAR 确认该 App 确实用了多个域名/版本。
  3. MITM hostname 同理 — HAR 里只出现 api.revenuecat.com,就只写 api.revenuecat.com。不要写 *.revenuecat.com, *.rc-backup.com
  4. 只有用户没有 HAR 数据(盲写通杀脚本)时才用宽泛匹配。
  5. 如果 App 后续更新了 API 路径,用户会再抓包更新。不需要提前覆盖。

为什么

  • 多配符增加误触风险(其他 App 的请求被匹配执行了不该执行的脚本)
  • 备份域名不存在于真实流量中,加了纯属浪费字符
  • 用户看到你对着精确 HAR 写通配符会觉得你在偷懒/模板化

脚本长度控制

HAR 在手时,脚本应该极短。 参考结构(60 行以内):

/*
App名 解锁

[rewrite_local]
^精确URL url script-response-body ...
^精确URL url script-request-header ...

[mitm]

---
*[Content truncated: 589 lines total]*
---

**HAR 在手时,脚本应该极短。** 参考结构(60 行以内):

```javascript
/*
App名 解锁

[rewrite_local]
^精确URL url script-response-body ...
^精确URL url script-request-header ...

[mitm]
hostname = 精确域名
*/
var body = JSON.parse($response.body);
if ($response && body && body.xxx) {
  body.xxx = 值;
  body.yyy = 值;
  $done({ body: JSON.stringify(body) });
} else {
  // 删 ETag
  delete $request.headers["if-none-match"];
  $done({ headers: $request.headers });
}

禁止:模式A/模式B 分支、hasRealData 检测、递归遍历函数、大段注释说明。HAR 在手就知道确切结构,直接注入即可。

代码质量标准(此用户特严)

违禁模式 判断标准 示例 正确做法
废话去重 两行操作互相抵消 delete obj.desc; obj.desc = "success" 直接赋值一次或不要这行
注释污染 注释比代码多且无信息量 // 下面从1开始循环 + for(i=1;...) 只写 Why,不写 What
多余变量 定义后只用一次 const x = obj.field; if(x>0) 直接 if(obj.field>0)
空 catch catch 块只打 log 无逻辑 catch(e) { console.log(e) } 可接受,但更好是 catch(_){} 如果 debug 已够
正则误伤 替换会破坏 JSON 结构 body.replace(/\"unlocked\":\w+/g, ...) 可能替换到嵌套数据中的同名字段 用 JSON 递归遍历
漏端点 没处理次数扣减接口 只改 is_prime 和 paid_modules 必须覆盖 paid_module_used 类端点
本地交付 文件只存本地没推 GitHub 写到 /root/xxx.js 让用户下载 推送到用户 QX 仓库对应目录

核心原则:每行代码必须有实际作用,无废话、无冗余、无两行抵消。

🚨 必守前置:检查仓库现有结构(7452323 铁律)

在写任何代码之前,必须先看仓库里已经有什么! 不然写出来的全是垃圾。

第一步:了解仓库结构

# 克隆仓库并查看结构
gh repo clone 7452323/QuantumultX /tmp/qx-repo
ls /tmp/qx-repo/script/
cat /tmp/qx-repo/script/RevenueCat.js  # 看看到底有什么通用脚本

仓库现有结构(2026-06)

script/              ← QX JS 脚本,内嵌 rewrite/mitm 注释
  RevenueCat.js      ← 通用 RC 解锁(revenuecat.com + rc-backup.com,含 ETag 剥离 + UAMapping)
  Foodie.js          ← 单个 App 脚本,自包含
  Hireader.js
  PicSeed.js
  读不舍手.js
  ...
surge/               ← Surge sgmodule(每个 App 一个 .sgmodule)
  RevenueCat.sgmodule
  PicSeed.sgmodule
  Foodie.sgmodule
  读不舍手.sgmodule
  ...
Rule/                ← QX 格式过滤列表(HOST-SUFFIX, ...)
  漫画.list

关键约束

  1. RevenueCat.js 已经通用 — 覆盖 revenuecat.com + rc-backup.comsubscribers / receipts 端点,双阶段(ETag剥离 + 响应修改),还有 UAMapping 后备。绝大多数 RC App 无需额外脚本。不要重复造轮子。

  2. 脚本风格 = 内嵌 rewrite 注释 — 单个 .js 文件顶部的 /* */ 注释块里写 [rewrite_local][mitm] 规则。不要单独写 .conf 文件。看 Foodie.js(35行)的写法:

    /*
    [rewrite_local]
    ^https?://... url script-response-body https://raw.githubusercontent.com/7452323/QuantumultX/main/script/Foodie.js
    [mitm]
    hostname = ...
    */
    // 然后只有业务逻辑,没有多余
  3. 不要混合 RC 解锁和去广告 — RC 解锁归 RevenueCat.js,去广告归独立的规则列表。一个脚本只干一件事。

  4. 去广告用 Rule/ 下的 .list 文件 — QX 过滤列表格式(HOST-SUFFIX,domain,reject)。不做成 JS。

  5. 不要删除仓库原有目录mitm/ 等目录虽然看着没用,用户不开口就别碰。用户说了才能删。

仓库交付规范(7452323)

生成的脚本文件不能只存本地,必须推送到用户的 GitHub 仓库 7452323/QuantumultX每次必须同时推 QX + Surge 两份文件,缺一不可。 用户发现只推了 QX 没推 Surge 会问"Surge没同步吗?"。

文件类型 目标路径 示例
QX JS 脚本 script/<appname>.js script/Foodie.js
Surge sgmodule surge/<app名>.sgmodule surge/Foodie.sgmodule
过滤列表 Rule/<app名>.list Rule/漫画.list

Surge sgmodule 格式(RC 解锁双阶段模式):

#!name=读不舍手
#!desc=读不舍手 - RC解锁
#!author=7452323
#!homepage=https://github.com/7452323/QuantumultX

[Script]
# 清 ETag 防 304
http-request ^https?:\/\/([a-z0-9-]+\.)*(revenuecat|rc-backup)\.com\/(v[12]\/)?(receipts$|subscribers\/[^?#]+) requires-body=false, script-path=https://raw.githubusercontent.com/7452323/QuantumultX/main/script/读不舍手.js, tag=读不舍手-清ETag
# 改订阅日期
http-response ^https?:\/\/([a-z0-9-]+\.)*(revenuecat|rc-backup)\.com\/(v[12]\/)?(receipts$|subscribers\/[^?#]+) requires-body=true, script-path=https://raw.githubusercontent.com/7452323/QuantumultX/main/script/读不舍手.js, tag=读不舍手-解锁

[MITM]
hostname = %APPEND% *.revenuecat.com, *.rc-backup.com

注意:Surge 的 http-request + http-response = QX 的 script-request-header + script-response-body。同一个 .js 脚本同时处理两个阶段,脚本内部用 typeof $response === "undefined" 区分。

Loon .plugin / .lpx 格式(与 .sgmodule 结构相同,但 script-path 后加 , requires-body=true 而非 =true):

#!name=Retouch
#!desc=TouchRetouch - RC解锁
#!author=7452323
#!icon=https://raw.githubusercontent.com/7452323/QuantumultX/main/icon/Loon.png
#!homepage=https://github.com/7452323/QuantumultX

[Script]
http-response ^https:\/\/([a-z0-9-]+\.)*(revenuecat|rc-backup)\.com\/(v[12]\/)?(receipts$|subscribers\/[^?#]+) script-path=https://raw.githubusercontent.com/7452323/QuantumultX/main/script/Retouch.js, requires-body=true, tag=Retouch-解锁
http-request ^https:\/\/([a-z0-9-]+\.)*(revenuecat|rc-backup)\.com\/(v[12]\/)?(receipts$|subscribers\/[^?#]+) script-path=https://raw.githubusercontent.com/7452323/QuantumultX/main/script/Retouch.js, requires-body=false, tag=Retouch-清ETag

[MITM]
hostname = *.revenuecat.com, *.rc-backup.com

推送流程:

cd /tmp/qx-repo
cp <生成的脚> script/<appname>.js
cp <生成的sgmodule> surge/<app>.sgmodule
git add -A && git commit -m "feat: <app名> - 解锁 [+ Surge]" && git push

推完立刻发 raw 链接给用户 — 不要等用户问"链接给我啊"。直接在回复里贴两个 URL:

https://raw.githubusercontent.com/7452323/QuantumultX/main/script/<app名>.js
https://raw.githubusercontent.com/7452323/QuantumultX/main/surge/<app名>.sgmodule

⚠️ 注意:仓库已有通用 script/RevenueCat.js(双阶段版本,已含 ETag 剥离), 不要为每个 App 重复造 RC 解锁轮子。专用脚本只在存在独有字段/逻辑时创建。
独占逻辑包括:去广告规则(按 App 特有的广告 SDK 域名)、非 RC 的独有 API 端点、特定 App 的参数格式等。

特别警惕:不要画蛇添足

  • 如果 App 用的是 RevenueCat + rc-backup.com,先确认 RevenueCat.js 的 UA Mapping 是否匹配。RevenueCat.js 的 UAMapping 字典里如果已经有一个名字相似的 entry(如 "Reader" 映射到 "vd_monthly_999"),它可能属于另一个 App。reader/46 的 UA 会误触这个 mapping,给错误的 product ID。
  • RevenueCat.js 的策略是:模式A(有真实数据→改过期时间)→ 模式B(无数据→UAMapping 伪造)。对于无购买历史的新用户,模式A会跳过,模式B用错 mapping 的话就翻车。
  • 如果 RevenueCat.js 的 UAMapping 不匹配当前 App 的 product ID,写一个独立的稳定脚本(只改 expires_date,不注入伪造数据)是正确做法,不是画蛇添足。
  • 如果 HAR 里的 RC 请求全是 304,这是正常情况——先开 request-header 阶段清 ETag 强制拿 200 之后再看响应结构。
  • 先看已有脚本再动手,别让用户说你踏马写的什么鬼东西

限制可行性预判速查

拿到 HAR 文件后,必须先判断限制类型再动手写脚本:

响应特征 限制类型 能解? 方案
unlocked: false, is_vip: false 客户端标志 改字段值
steps: [], content: null, 关键字段缺失 服务端返回过滤 数据不发出,放弃
"tips":"购买会员..." 客户端引导 清空文案
POST paid_module_used 服务端次数校验 mock {code:0}
watch_type: 2 限制等级 1(更宽松)
带签名时效的视频 URL(t= CDN Token 限时 动态生成,不可伪造
纯 CDN URL(无签名参数) 客户端播放器限制 客户端行为,rewrite 改不了
视频 URL 含 v.0_10000. 低清版 URL-rewrite v.0_10000.v.

Pitfalls

  1. 先看仓库再动手(7452323 铁律 #1) — 用户仓库已有 RevenueCat.js,写脚本前必须 ls script/ 看看有哪些通用脚本。没检查就写 = 被骂。

  2. 不要混搭脚本功能 — RC 解锁归 RevenueCat.js,去广告归 Rule/*.list。一个脚本文件只干一件事。不要写"RC解锁+去广告"大杂烩脚本。

  3. 脚本格式跟着仓库走 — 用户仓库的 .js 文件全部内嵌 [rewrite_local] 注释。不要另写 .conf 文件。看一个已有脚本的写法再写新的。

  4. Charles 默认不保存 response body — 勾选 Preferences → Recording → Include response bodies

  5. RevenueCat 304 缓存问题 — QX 抓到的 RC 请求可能全是 304(无 body)。必须用双阶段脚本:script-request-header 剥离 ETag headers(X-RevenueCat-ETag / If-None-Match)→ 强制返回 200 → script-response-body 改订阅数据。仓库的 RevenueCat.js 已经实现好了,直接引用即可。详见 references/revenuecat-dual-phase-unlock.md

  6. Telegram 不支持 .har 后缀 — 让用户改名为 .json 再发送

  7. paid_module_used 是必漏端点 — Openclaw 脚本多次遗漏此接口。App 在每个付费操作结束后调用扣减接口,不拦截则 VIP 用户实际使用一次扣一次。每次写脚本都要专项搜索这个端点,搜 POST + used/consume/deduct/cost/spend

  8. 用户给参考了就别自作聪明 — 用户已经有可用的写法(哪怕是 body.replace() 纯正则),就严格照搬,不要换成 JSON 解析版。纯正则看起来"简陋",但 JSON.stringify() 可能改变原始响应换行/缩进格式,让客户端检测到差异后弹非预期提示("1500+精选 限时特惠"那种)。能用的东西不要重新发明。

  9. 不要对用户说"做不到"——先试 — 即使觉得某个限制是服务端控制的,也要按用户给的参考方案试一遍。用户的实际测试比你的理论分析重要。

  10. 加载了这个技能就必须按步骤执行,不能看一眼就自己写 — 这个技能的流程顺序是:①克隆/检查仓库结构 → ②HAR域名分析 → ③定位关键端点 → ④判定限制类型 → ⑤写脚本 → ⑥推送到仓库。直接跳到第⑤步,跳过①-④,写出来的脚本100%是垃圾。每次"看了技能但还是自己乱写"都要被用户骂。如果已经加载了这个技能,commit to follow it — 每个步骤都有存在的理由。

  11. QX + Surge 必须同步推,不能只写一边 — 7452323 仓库是双平台(QX + Surge)。新建一个 App 脚本时,必须在同一个 commit 里同时推 script/<app>.jssurge/<app>.sgmodule。只推 QX 脚本会导致用户问"Surge没同步吗?"。先 ls 两个目录看两边都有什么,再创建。

  12. 不同 App 即使解锁模式相同也要分别建文件 — 仓库里已有同名功能脚本(如 Epub.js 拦截 verifyReceipt)不代表当前 App 能用它。每个 App 的 bundle ID 不同,永远新建 script/<不同名>.js + surge/<不同名>.sgmodule。不要修改或扩展现有同名脚本——用户会说"仓库的别动,两个不是同一个软件"。

  13. Flutter/Dart App 的 RevenueCat UA 特征 — HAR 中 User-Agent 为 Dart/x.y (dart:io)reader/xx CFNetwork/... 的 App 是 Flutter 项目。其 UA 不会匹配 RevenueCat.js 中任何 URL-encoded AppName mapping(如 Readervd_monthly_999),导致模式B匹配失败,回退到通用 com.rc.universal.pro.yearly。正确做法:写独立脚本只改 expires_date(模式A),不依赖 UAMapping。


s3-presigned-upload-reverse

S3 Presigned URL 上传模式逆向

适用判断

  • 网站提供文件/图片上传功能
  • 上传不走传统 multipart/form-data POST
  • JS bundle 中有 s3presignedPUTupload 关键词
  • 响应返回短链接/文件 ID 而非直接的文件 URL

三步工作流

Step 1: POST /api/s3 → 获取 presigned URL + key
Step 2: PUT {presigned_url} → 直传文件到 S3
Step 3: POST /api/upload → 提交元数据 → 返回短链接

端点模式

Step Method URL Body 返回
获取凭证 POST /api/s3 {"content_type":"image/png"} {"url":"https://...s3...","key":"abc.png"}
上传文件 PUT {url} (S3 presigned) 文件 bytes, Content-Type, Content-Encoding: base64 HTTP 200
提交链接 POST /api/upload {"content_type":"image","file_names":["abc.png"],"expire_days":30} {"data":"X7nBTBl"}

可选参数(提交时)

  • password — 访问密码
  • memo — 备注
  • max_views — 最大查看次数

纯短网址变体

POST /api/shortUrl
Body: {"long_url":"https://example.com"}
→ {"data":{"short_url":"abc123"}}

完整 Python 示例

import base64, requests

BASE = "https://target.com"

resp = requests.post(f"{BASE}/api/s3", json={"content_type":"image/png"})
data = resp.json()

with open("file.png","rb") as f:
    b64 = base64.b64encode(f.read()).decode()
requests.put(data["url"], data=b64,
    headers={"Content-Type":"image/png","Content-Encoding":"base64"})

resp = requests.post(f"{BASE}/api/upload", json={
    "content_type":"image","file_names":[data["key"]],"expire_days":30
})
print(f"{BASE}/{resp.json()['data']}")

发现方法

  1. 搜索 JS bundle 中的 s3 关键词
  2. 查看网络请求中的 PUT 方法
  3. S3 URL 特征:amazonaws.comX-Amz- 查询参数

案例

  • meee.com.tw — 免费图床,无认证,Nuxt 3 前端

web-font-anti-scraping

Web Font Anti-Scraping — 字体反爬逆向

适用场景

  • 目标站点的正文使用自定义字体渲染,直接提取 HTML 得到乱码/PUA 字符
  • 字体文件为 woff2/woff/otf 格式,通过 @font-face 加载
  • 同一站点每次请求可能使用不同字体文件(动态字体)
  • 字体 cmap 表将标准汉字映射到 PUA 区码点 (U+E000-U+F8FF)

识别特征

特征 说明
HTML 含不可见字符 复制粘贴得到乱码或空白字符
CSS @font-face 引用 woff2/woff 字体文件
字体子集 字体文件远小于完整字体(47KB vs 16MB)
PUA 码点 文本中大量 U+E000-U+F8FF 范围字符
字体来源声明 name 表可能含 "subset font of XXX"

方案优先级

方案 A: Glyph 轮廓对比(最可靠)

原理:混淆字体是标准字体的子集,glyph 轮廓完全相同。

from fontTools.ttLib import TTFont
from fontTools.pens.recordingPen import RecordingPen

def extract_outlines(font_path):
    font = TTFont(font_path)
    gset = font.getGlyphSet()
    outlines = {}
    for name in gset.keys():
        pen = RecordingPen()
        gset[name].draw(pen)
        ops = tuple(
            (op_type, tuple(tuple(a) if isinstance(a, (list, tuple)) else a for a in args))
            for op_type, args in pen.value
        )
        outlines[name] = ops
    font.close()
    return outlines

fq_outlines = extract_outlines('/tmp/fq_font.woff2')
src_outlines = extract_outlines('/tmp/SourceHanSansSC-Regular.otf')
src_outline_to_name = {v: k for k, v in src_outlines.items()}

fq_to_src = {}
for fq_name, fq_outline in fq_outlines.items():
    if fq_outline in src_outline_to_name:
        fq_to_src[fq_name] = src_outline_to_name[fq_outline]

# 通过 cmap 获取真实 Unicode
fq_font = TTFont('/tmp/fq_font.woff2')
src_font = TTFont('/tmp/SourceHanSansSC-Regular.otf')
fq_cmap = fq_font.getBestCmap()
src_cmap = src_font.getBestCmap()
fq_rev = {v: k for k, v in fq_cmap.items()}
src_rev = {v: k for k, v in src_cmap.items()}

pua_to_real = {}
for fq_name, src_name in fq_to_src.items():
    pua_unicode = fq_rev.get(fq_name)
    real_unicode = src_rev.get(src_name)
    if pua_unicode and real_unicode:
        pua_to_real[pua_unicode] = real_unicode

关键:需要获取原始字体文件作为参考。Noto Sans SC 与 SourceHanSansSC 轮廓不匹配。

方案 B: Playwright 渲染提取(无需参考字体)

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto(url, wait_until='networkidle', timeout=30000)
    page.wait_for_timeout(3000)
    content = page.evaluate('''() => {
        const el = document.querySelector('.chapter-content, .read-content');
        return el ? el.innerText : '';
    }''')
    browser.close()

注意:必须用 innerText(渲染后),不是 textContent

方案 C: 上下文推导映射表(兜底)

根据上下文语义推断 PUA 字符对应的真实汉字。

Pitfalls

  1. Noto Sans SC ≠ SourceHanSansSC — 不同家族,轮廓不匹配
  2. Adobe 字体链接 404 — GitHub adobe-fonts 经常失效
  3. 多对一映射 — 多个 PUA 字符映射到同一汉字
  4. innerText vs textContent — 必须用 innerText
  5. Desktop UA 必需 — 番茄小说 iPhone UA 返回空数据

实战案例

详见 references/fanqie-font-obfuscation.md(番茄小说完整逆向笔记)


Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/7452323-nexus-network-protocol-reverse/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

7452323-nexus-network-protocol-reverse.ocm.jsonjson
{
  "ocm": "1",
  "id": "7452323-nexus-network-protocol-reverse",
  "kind": "skill",
  "name": "network-protocol-reverse",
  "description": "Merged skill combining: protocol-reverse-engineering, har-to-proxy-script, s3-presigned-upload-reverse, web-font-anti-scraping.",
  "publisher": "7452323",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "general"
    ],
    "tags": [
      "skill-md",
      "reverse-engineering",
      "merged",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Merged skill combining: protocol-reverse-engineering, har-to-proxy-script, s3-presigned-upload-reverse, web-font-anti-scraping."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/7452323/Nexus",
      "path": "Hermes/skills/reverse-engineering/network-protocol-reverse/SKILL.md",
      "ref": "1e3cb847aec223f6370272ede12d531e8d264e1d",
      "url": "https://github.com/7452323/Nexus/blob/1e3cb847aec223f6370272ede12d531e8d264e1d/Hermes/skills/reverse-engineering/network-protocol-reverse/SKILL.md",
      "key": "7452323/Nexus/Hermes/skills/reverse-engineering/network-protocol-reverse/SKILL.md"
    }
  },
  "instructions": "# network-protocol-reverse\n\n> **Merged from**: protocol-reverse-engineering, har-to-proxy-script, s3-presigned-upload-reverse, web-font-anti-scraping\n> **Source count**: 4\n\n---\n\n## Table of Contents\n\n1. [protocol-reverse-engineering](#protocol-reverse-engineering)\n2. [har-to-proxy-script](#har-to-proxy-script)\n3. [s3-presigned-upload-reverse](#s3-presigned-upload-reverse)\n4. [web-font-anti-scraping](#web-font-anti-scraping)\n\n---\n\n## protocol-reverse-engineering\n\n# 协议逆向工程\n\n## 触发条件\n\n用户需要:\n- 分析未知网络协议结构\n- 还原API请求/响应格式\n- 解析自定义二进制协议\n- 分析IoT/游戏/设备通信协议\n- 分析WebSocket游戏协议\n- 绕过客户端版本检查\n\n## 工具链\n\n### protob",
  "cost": {
    "context_tokens": 7191
  }
}

Fetch it by URL: GET /api/v1/registry/7452323-nexus-network-protocol-reverse/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.