Files
reverse-skills-max/vendor-new/xbsReverseSkill/web-reverse-algorithm/scripts/new_case_scaffold.py

370 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Create a structured case scaffold for Web reverse pure-calc research."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
CASE_TYPES = {
"sign": {
"title": "标准签名 / Header 纯算",
"goal": "从最终请求倒推 builder,恢复原始串并本地复现。",
"fields": ["url", "method", "target_field", "raw_string", "token_sources"],
"risks": [
"重点核对 query/json/path 的规范化方式。",
"固定时间戳、token、cookie、UA 后再比最终值。",
"不要把标准算法问题误判成环境问题。",
],
},
"mixed": {
"title": "混合加密 / 密钥包装",
"goal": "分离业务明文、对称层、非对称包装层,再本地复现。",
"fields": ["url", "method", "target_fields", "plaintext", "encrypted_layers"],
"risks": [
"不要把业务密文和密钥包装混成一个 sign。",
"分别保存每层输入输出,不只比较最终值。",
"优先确认 key/iv/public key 的真实来源。",
],
},
"cookie": {
"title": "Cookie / 会话值纯算",
"goal": "从 document.cookie 写入点回溯 builder 与环境采集链。",
"fields": ["writer", "target_cookie", "upstream_sources", "raw_payload", "final_cookie"],
"risks": [
"区分初始化 cookie 和业务 cookie。",
"优先 hook sink,不要只盯网络请求。",
"先证明哪些环境值是真校验项。",
],
},
"jsvmp": {
"title": "JSVMP / VMP 纯算",
"goal": "从最终 writer 倒推中间数组或 payload,再恢复最终编码层。",
"fields": ["url", "method", "target_field", "entry", "builder", "state_objects"],
"risks": [
"不要从文件头正推整个 VM。",
"先做低风险 AST 降噪,再做 hook / logpoint。",
"中间数组和随机数/时间戳检查点要先固定。",
],
},
"captcha": {
"title": "验证码 / Challenge / Verify",
"goal": "拆 challenge、图像、参数、环境、verify 五条线,并收敛成 solver。",
"fields": ["init_url", "image_url", "verify_url", "target_fields", "recognition_result"],
"risks": [
"不要把图像线和参数线混写。",
"先确认真正失败的是 verify 哪个字段。",
"collect、w、fs、pow_answer 一类字段要单独建检查点。",
],
},
"wasm": {
"title": "Wasm / 协议纯算",
"goal": "先证明输入输出边界,再决定是否反编译或直接改写。",
"fields": ["loader", "exported_functions", "target_field", "protocol_frames", "io_samples"],
"risks": [
"先看导出函数和包段,不要一上来全量反编译。",
"协议题先数包,再拆首包和验证包。",
"保留 Wasm / protobuf / WS 的输入输出样本。",
],
},
}
FAMILY_HINTS = {
"generic": ["按题型模板拆 writer / builder / entry / source。"],
"xhs": ["重点记录 window._webmsxyw、x1/x2/x3/x4、payload、X-S-Common。"],
"douyin": ["重点记录双 SM3、41/50/96/128/136 位数组、最终编码层。"],
"tencent": ["重点记录 prehandle、pow_answer、pow_calc_time、collect、track、verify。"],
"geetest": ["区分三代和四代,分别记录 h/u 或 captcha_id/challenge/load 对象。"],
"netease-shield": ["重点记录 validateCodeObj、userAnswer、P1~P9、w。"],
"taobao": ["重点记录 token、t、appKey、紧凑 JSON data。"],
"youdao": ["同时记录请求 sign 与响应 AES-CBC 解密链。"],
"baidu": ["区分 token、固定 seed 与 sign 函数。"],
"cloudflare": ["拆 challenge 阶段、fingerprint 层、计算层。"],
}
OVERVIEW_TEMPLATE = """# {title}
## 案例目标
- 案例名:`{case_name}`
- 题型:`{case_type}`
- 站点家族:`{family}`
- 目标:{goal}
- 目标字段:
- 最终成功条件:
## 入口判断
- 最终请求 / 最终 sink:
- writer:
- builder:
- entry:
- source:
## 当前阻塞点
- 入口没找对 / 原始串没对齐 / 中间态没采到 / 环境没补齐 / 图像线没拆开 / 协议边界没证明:
## 家族提示
{family_lines}
## 风险点
{risk_lines}
"""
REQUEST_CHAIN_TEMPLATE = """# 请求链
## 闭环
```text
source
-> entry
-> builder
-> writer
-> final output
```
## 请求或写出顺序
1. 初始化 / 页面态:
2. 中间参数生成:
3. 最终请求 / verify / cookie / WS:
## 目标字段
- 字段名:
- 位置:header / body / cookie / payload / ws
- 当前样本值:
## 必要输入集合
- token / cookie:
- 时间戳 / 随机数:
- 页面态参数 / load 返回对象:
- 图像识别结果 / 轨迹:
- 环境值 / 指纹值:
"""
EVIDENCE_TEMPLATE = """# 证据日志
## 浏览器检查点
1. writer 检查点:
2. builder 输入:
3. 原始串 / payload:
4. 中间数组 / 中间对象:
5. 最终输出:
## 本地检查点
1. writer 检查点:
2. builder 输入:
3. 原始串 / payload:
4. 中间数组 / 中间对象:
5. 最终输出:
## 差异定位
- 从哪一步开始分叉:
- 猜测原因:
- 下一步验证:
"""
TODO_TEMPLATE = """# TODO
- [ ] 抓成功样本并保存最终请求或最终 sink
- [ ] 标出 writer / builder / entry / source
- [ ] 固定时间戳、随机数、cookie、token 等不稳定输入
- [ ] 保存浏览器端检查点
- [ ] 保存本地端检查点
- [ ] 比对差异并收敛到最小可复现函数
- [ ] 记录易错点和版本风险
"""
SOLVER_JS = """// {title}
// 最小本地复现骨架。建议把 context、payload、crypto/vm、validate 分层。
function buildContext() {{
return {{
source: {{}},
unstableInputs: {{}},
}};
}}
function buildPayload(ctx) {{
return {{
ctx,
raw: null,
intermediate: [],
}};
}}
function solve(ctx = buildContext()) {{
const payload = buildPayload(ctx);
return {{
ctx,
payload,
finalOutput: null,
checkpoints: {{
writer: null,
builderInput: null,
raw: null,
intermediate: [],
final: null,
}},
}};
}}
module.exports = {{
buildContext,
buildPayload,
solve,
}};
"""
SOLVER_PY = """# {title}
# 最小本地复现骨架。建议把 context、payload、crypto/vm、validate 分层。
def build_context():
return {{
"source": {{}},
"unstable_inputs": {{}},
}}
def build_payload(ctx):
return {{
"ctx": ctx,
"raw": None,
"intermediate": [],
}}
def solve(ctx=None):
if ctx is None:
ctx = build_context()
payload = build_payload(ctx)
return {{
"ctx": ctx,
"payload": payload,
"final_output": None,
"checkpoints": {{
"writer": None,
"builder_input": None,
"raw": None,
"intermediate": [],
"final": None,
}},
}}
"""
def slugify(value: str) -> str:
value = value.strip().lower()
value = re.sub(r"[^a-z0-9\u4e00-\u9fff._-]+", "-", value)
value = re.sub(r"-{2,}", "-", value).strip("-")
return value or "case"
def parse_csv(text: str) -> list[str]:
if not text:
return []
return [item.strip() for item in text.split(",") if item.strip()]
def checkpoints_for(case_type: str) -> dict:
return {
"meta": {
"case_type": case_type,
"target_fields": [],
},
"browser": {
"writer": None,
"builder_input": None,
"raw": None,
"intermediate": [],
"final": None,
},
"local": {
"writer": None,
"builder_input": None,
"raw": None,
"intermediate": [],
"final": None,
},
"field_hints": CASE_TYPES[case_type]["fields"],
}
def main() -> int:
parser = argparse.ArgumentParser(description="Create a structured case scaffold for Web reverse pure-calc tasks.")
parser.add_argument("case_name", help="Case name shown in generated files.")
parser.add_argument("--type", choices=sorted(CASE_TYPES), required=True, help="Case type.")
parser.add_argument("--family", default="generic", help="Site family such as xhs, douyin, tencent, geetest.")
parser.add_argument("--tags", default="", help="Comma-separated tags.")
parser.add_argument("--sources", default="", help="Comma-separated source URLs.")
parser.add_argument("--language", choices=["js", "py"], default="js", help="Solver stub language.")
parser.add_argument("--output", required=True, help="Directory where the case folder will be created.")
args = parser.parse_args()
case_slug = slugify(args.case_name)
base_dir = Path(args.output).expanduser().resolve() / case_slug
base_dir.mkdir(parents=True, exist_ok=True)
case_type = CASE_TYPES[args.type]
family_hints = FAMILY_HINTS.get(args.family, FAMILY_HINTS["generic"])
family_lines = "\n".join(f"- {line}" for line in family_hints)
risk_lines = "\n".join(f"- {line}" for line in case_type["risks"])
metadata = {
"case_name": args.case_name,
"case_slug": case_slug,
"case_type": args.type,
"family": args.family,
"tags": parse_csv(args.tags),
"sources": parse_csv(args.sources),
"solver_language": args.language,
}
(base_dir / "case-overview.md").write_text(
OVERVIEW_TEMPLATE.format(
title=case_type["title"],
case_name=args.case_name,
case_type=args.type,
family=args.family,
goal=case_type["goal"],
family_lines=family_lines,
risk_lines=risk_lines,
),
encoding="utf-8",
)
(base_dir / "request-chain.md").write_text(REQUEST_CHAIN_TEMPLATE, encoding="utf-8")
(base_dir / "evidence-log.md").write_text(EVIDENCE_TEMPLATE, encoding="utf-8")
(base_dir / "todo.md").write_text(TODO_TEMPLATE, encoding="utf-8")
(base_dir / "metadata.json").write_text(
json.dumps(metadata, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
(base_dir / "checkpoints.json").write_text(
json.dumps(checkpoints_for(args.type), ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
solver_name = f"solver_stub.{args.language}"
solver_content = SOLVER_JS if args.language == "js" else SOLVER_PY
(base_dir / solver_name).write_text(solver_content.format(title=case_type["title"]), encoding="utf-8")
print(f"[OK] Created scaffold: {base_dir}")
print("[OK] Files: case-overview.md, request-chain.md, evidence-log.md, checkpoints.json, metadata.json, todo.md")
print(f"[OK] Solver stub: {solver_name}")
return 0
if __name__ == "__main__":
raise SystemExit(main())