返回列表 发新帖
楼主: Cynnie - 

流光助手-另类逆向

  [复制链接]

0

主题

4537

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
5480
好评
0
信誉
96
发表于 3 天前 来自手机  | 显示全部楼层  来自 吉林
感谢分享
回复

使用道具 举报

0

主题

428

回帖

1042

积分

高中生

Rank: 4

金币
472
好评
0
信誉
100
发表于 3 天前 来自手机  | 显示全部楼层  来自 河北
则铭 发表于 2026-8-24 16:40
本地的话,应该没有ssl吧,cloudflare穿透自带有

明白了,感谢大佬。等下我去看看
回复

使用道具 举报

0

主题

47

回帖

153

积分

小学生

Rank: 2

金币
44
好评
0
信誉
100
发表于 3 天前 来自手机  | 显示全部楼层  来自 浙江
回复

使用道具 举报

31

主题

1312

回帖

7194

积分

硕士生

Rank: 6Rank: 6

金币
3032
好评
14
信誉
101
发表于 3 天前 | 显示全部楼层  来自 重庆
本帖最后由 则铭 于 2026-8-24 19:23 编辑
Orcle00 发表于 2026-8-24 16:48
明白了,感谢大佬。等下我去看看
配合cloudflare内网穿透,或者其他有ssl的穿透服务使用。服务器应该也能用吧

嗯,新账号有一天会员。弄不了


  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 反代 + 篡改脚本(生产可用版)
  5. 总并发能力:4 × 1000 = 4000 个连接

  6. 部署方式
  7. 1. 安装依赖
  8. pip install gunicorn gevent flask requests
  9. 2. 启动(推荐 gevent 异步模型)
  10. gunicorn -w 4 -k gevent \
  11.          --worker-connections 1000 \
  12.          --bind 127.0.0.1:8080 \
  13.          --timeout 120 \
  14.          --keep-alive 30 \
  15.          --max-requests 10000 --max-requests-jitter 1000 \
  16.          --access-logfile - --error-logfile - \
  17.          app:app

  18. 参数解释:
  19. -w 4        4 个 worker 进程
  20. -k gevent        用 gevent 协程,单 worker 内可挂 1000 并发
  21. --worker-connections 1000        每 worker 1000 并发连接
  22. --timeout 120        慢请求/大文件上传下载给够时间
  23. --keep-alive 30        长连接保活 30 秒,降低 TLS 握手开销
  24. --max-requests 10000        防 worker 内存泄漏,每处理 1 万请求重启 worker


  25. Windows 用户请使用下面的命令启动:
  26. # 安装Waitress(Windows 友好的生产级 WSGI 服务器)
  27. pip install waitress flask requests

  28. # 启动
  29. waitress-serve --host 127.0.0.1 --port 8080 --threads 50 "流光助手替换服务:app"
  30. 流光助手替换服务:app(流光助手替换服务.py) 你的脚本文件名,比如 app.py 就是 "app:app"

  31. 流光助手需要把libapp.so文件里的 https://pan-api.yodlx.com (只有三处)改为 你的cloudflared内网穿透的url,长度必须一致,否则会出错。
  32. 比如我的是 https://pan-apiy.luac.top (68 74 74 70 73 3A 2F 2F 70 61 6E 2D 61 70 69 79 2E 6C 75 61 63 2E 74 6F 70)

  33. 运行脚本,浏览器测试访问 https://yourUrl/api/v1/auth/me
  34. 如  https://pan-apiy.luac.top/api/v1/auth/me
  35. 返回{"error":{"code":"missing_token","message":"需要 Bearer 登录凭证"}}  说明你的脚本就能用了
  36. cloudflared tunnel 仍指向 http://127.0.0.1:8080 即可。
  37. """

  38. from flask import Flask, request, Response, stream_with_context
  39. import requests
  40. from requests.adapters import HTTPAdapter
  41. from urllib3.util.retry import Retry
  42. import json
  43. import logging
  44. import threading

  45. # ---------- 日志 ----------
  46. logging.basicConfig(
  47.     level=logging.INFO,
  48.     format='%(asctime)s [%(process)d] %(levelname)s - %(message)s'
  49. )
  50. logger = logging.getLogger("proxy")

  51. app = Flask(__name__)

  52. # ---------- 配置 ----------
  53. REAL_SERVER = "https://pan-api.yodlx.com"
  54. TAMPER_PATHS = {"/api/v1/auth/me", "/api/v1/auth/login"}
  55. FAKE_EXPIRES = "2099-12-31 23:59:59"

  56. # 必须由 Flask/上游重新生成的响应头
  57. REMOVE_HEADERS = {
  58.     'transfer-encoding', 'content-encoding', 'content-length',
  59.     'connection', 'keep-alive', 'proxy-connection', 'upgrade',
  60.     'content-type',   # 响应 Content-Type 由 Flask 根据 content_type 参数设置
  61. }

  62. # 转发请求时需要移除的头(避免与 requests 自动生成冲突)
  63. FORWARD_EXCLUDE_HEADERS = {
  64.     'host',
  65.     'content-length',
  66.     'transfer-encoding',
  67.     'connection',
  68. }

  69. # 单进程内连接池配置(gunicorn 多 worker 时每个 worker 各自有池)
  70. POOL_CONNECTIONS = 50    # 不同 host 的连接缓存
  71. POOL_MAXSIZE     = 200    # 同一 host 的最大并发连接
  72. CONNECT_TIMEOUT  = 5      # TCP/TLS 建立超时
  73. READ_TIMEOUT     = 120    # 上游响应读超时(大文件也要传完)
  74. CHUNK_SIZE       = 64 * 1024

  75. # ---------- 全局 Session(线程安全,连接复用) ----------
  76. _session = None
  77. _session_lock = threading.Lock()

  78. def get_session() -> requests.Session:
  79.     global _session
  80.     if _session is None:
  81.         with _session_lock:
  82.             if _session is None:
  83.                 s = requests.Session()
  84.                 retry = Retry(
  85.                     total=2,
  86.                     backoff_factor=0.2,
  87.                     status_forcelist=(502, 503, 504),
  88.                     allowed_methods=("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"),
  89.                     raise_on_status=False,
  90.                 )
  91.                 adapter = HTTPAdapter(
  92.                     pool_connections=POOL_CONNECTIONS,
  93.                     pool_maxsize=POOL_MAXSIZE,
  94.                     max_retries=retry,
  95.                     pool_block=False,
  96.                 )
  97.                 s.mount('http://', adapter)
  98.                 s.mount('https://', adapter)
  99.                 _session = s
  100.                 logger.info("HTTP session/pool initialized")
  101.     return _session


  102. # ---------- 工具函数 ----------
  103. def _filter_headers(raw_headers):
  104.     """过滤掉会引起冲突的响应头,保留其他原始头(含大小写)。"""
  105.     return [(k, v) for k, v in raw_headers.items() if k.lower() not in REMOVE_HEADERS]


  106. def _build_response(content, status_code, headers_list, content_type):
  107.     """构造响应,正确处理重复的 Set-Cookie 等头。"""
  108.     resp = Response(content, status=status_code, content_type=content_type)
  109.     for k, v in headers_list:
  110.         if k.lower() == 'set-cookie':
  111.             resp.headers.add(k, v)          # 允许多个 Set-Cookie
  112.         else:
  113.             resp.headers[k] = v
  114.     return resp


  115. def _tamper_body(content: bytes, current_path: str) -> bytes:
  116.     """篡改响应体;失败则原样返回。"""
  117.     try:
  118.         j = json.loads(content)
  119.     except json.JSONDecodeError as e:
  120.         logger.warning(f"[{current_path}] json decode failed: {e}")
  121.         return content

  122.     data = j.get('data') if isinstance(j, dict) else None
  123.     if isinstance(data, dict) and 'user' in data:
  124.         data['user']['is_pro'] = 1
  125.         data['user']['pro_expires_at'] = FAKE_EXPIRES
  126.         logger.info(f"✅ Tampered {current_path} -> is_pro=1")
  127.     else:
  128.         logger.warning(f"[{current_path}] unexpected json structure")
  129.     return json.dumps(j, ensure_ascii=False).encode('utf-8')


  130. # ---------- 路由 ----------
  131. @app.route('/', defaults={'path': ''},
  132.            methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'])
  133. @app.route('/<path:path>',
  134.            methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'])
  135. def proxy(path):
  136.     full_url = REAL_SERVER + request.full_path
  137.     logger.info(f"-> {request.method} {request.path}")

  138.     # 请求头:去掉 host 和会冲突的长度/连接头
  139.     headers = {
  140.         k: v for k, v in request.headers.items()
  141.         if k.lower() not in FORWARD_EXCLUDE_HEADERS
  142.     }

  143.     # 请求体:完整读取,避免流式 body 与 Content-Length 冲突
  144.     data = request.get_data() if request.method in ('POST', 'PUT', 'PATCH') else None

  145.     current_path = request.path
  146.     needs_tamper = current_path in TAMPER_PATHS

  147.     try:
  148.         resp = get_session().request(
  149.             method=request.method,
  150.             url=full_url,
  151.             headers=headers,
  152.             data=data,                          # 完整 bytes 请求体
  153.             allow_redirects=False,
  154.             verify=True,
  155.             timeout=(CONNECT_TIMEOUT, READ_TIMEOUT),
  156.             stream=True,                        # 流式下载
  157.         )
  158.     except requests.exceptions.RequestException as e:
  159.         logger.error(f"forward failed [{current_path}]: {e}")
  160.         return Response(f"Proxy error: {e}", status=502)

  161.     # 清理响应头
  162.     response_headers = _filter_headers(resp.headers)
  163.     status_code = resp.status_code
  164.     content_type = resp.headers.get('content-type', '')

  165.     # ---------- 篡改路径:必须读全文 ----------
  166.     if needs_tamper and status_code == 200 and 'application/json' in content_type:
  167.         try:
  168.             content = resp.content
  169.         except Exception as e:
  170.             logger.error(f"read tamper body failed: {e}")
  171.             resp.close()
  172.             return Response("Proxy error: read body", status=502)
  173.         finally:
  174.             resp.close()

  175.         content = _tamper_body(content, current_path)

  176.         # 因为可能改了长度,去掉原始 Content-Length,让 Flask 自动算
  177.         response_headers = [(k, v) for k, v in response_headers
  178.                             if k.lower() != 'content-length']

  179.         return _build_response(
  180.             content,
  181.             status_code,
  182.             response_headers,
  183.             content_type
  184.         )

  185.     # ---------- 非篡改路径:纯流式转发 ----------
  186.     def generate():
  187.         try:
  188.             for chunk in resp.iter_content(chunk_size=CHUNK_SIZE):
  189.                 if chunk:
  190.                     yield chunk
  191.         except Exception as e:
  192.             logger.error(f"stream error [{current_path}]: {e}")
  193.         finally:
  194.             resp.close()

  195.     # HEAD 请求没有 body,Flask 会自动处理
  196.     return _build_response(
  197.         stream_with_context(generate()),
  198.         status_code,
  199.         response_headers,
  200.         content_type or None
  201.     )


  202. # ---------- 健康检查(cloudflared / 负载均衡用) ----------
  203. @app.route('/__health', methods=['GET'])
  204. def health():
  205.     return Response('ok', status=200, content_type='text/plain')


  206. if __name__ == '__main__':
  207.     # 仅本地调试;生产请用 gunicorn
  208.     app.run(host='127.0.0.1', port=8080, threaded=True)
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

2

主题

479

回帖

1576

积分

高中生

Rank: 4

金币
364
好评
0
信誉
100
发表于 3 天前 来自手机  | 显示全部楼层  来自 广东
看看
回复

使用道具 举报

0

主题

428

回帖

1042

积分

高中生

Rank: 4

金币
472
好评
0
信誉
100
发表于 3 天前 来自手机  | 显示全部楼层  来自 河北
则铭 发表于 2026-8-24 17:57
配合cloudflare内网穿透,或者其他有ssl的穿透服务使用。服务器应该也能用吧

嗯,新账号有一天会员。弄不 ...

新用户会有一天会员点击解绑,就是无会员了
回复

使用道具 举报

0

主题

428

回帖

1042

积分

高中生

Rank: 4

金币
472
好评
0
信誉
100
发表于 3 天前 来自手机  | 显示全部楼层  来自 河北
则铭 发表于 2026-8-24 17:57
配合cloudflare内网穿透,或者其他有ssl的穿透服务使用。服务器应该也能用吧

嗯,新账号有一天会员。弄不 ...

刚刚看一个佬发的免广告完成签到的
回复

使用道具 举报

12

主题

155

回帖

621

积分

初中生

Rank: 3Rank: 3

金币
44
好评
4
信誉
100
QQ
发表于 3 天前 来自手机  | 显示全部楼层  来自 广东
Orcle00 发表于 2026-8-24 15:40
用termux运行脚本试了,然后apk里的url改用127.0.0.1,需要会员的网盘还是不行。 ...

需要ssl
回复

使用道具 举报

12

主题

155

回帖

621

积分

初中生

Rank: 3Rank: 3

金币
44
好评
4
信誉
100
QQ
发表于 3 天前 来自手机  | 显示全部楼层  来自 广东
优梨 发表于 2026-8-24 15:55
成品发一下  亲。

需要自行搭建接口
回复

使用道具 举报

12

主题

155

回帖

621

积分

初中生

Rank: 3Rank: 3

金币
44
好评
4
信誉
100
QQ
发表于 3 天前 来自手机  | 显示全部楼层  来自 广东
则铭 发表于 2026-8-24 17:57
配合cloudflare内网穿透,或者其他有ssl的穿透服务使用。服务器应该也能用吧

嗯,新账号有一天会员。弄不 ...

你这用的是我的吗 我这边也是新账号,另外这个软件是不存在加速下载的,只是方便下载不用下这么多网盘
回复

使用道具 举报

31

主题

1312

回帖

7194

积分

硕士生

Rank: 6Rank: 6

金币
3032
好评
14
信誉
101
发表于 3 天前 来自手机  | 显示全部楼层  来自 重庆
Cynnie 发表于 2026-8-24 20:09
你这用的是我的吗 我这边也是新账号,另外这个软件是不存在加速下载的,只是方便下载不用下这么多网盘 ...

先退出网盘的登录吧

这个 App 的网盘 cookie 不是本地存,而是 WebView 登录后提取 → 上传到自己后端(PUT /api/v1/<网盘>/cookie)集中保存。如果你后续要写 Python 脚本绕过 App 拉网盘资源,这条链路意味着服务端存有全部网盘 cookie,而网盘接口的调用凭证就是它。
回复

使用道具 举报

12

主题

155

回帖

621

积分

初中生

Rank: 3Rank: 3

金币
44
好评
4
信誉
100
QQ
发表于 3 天前 来自手机  | 显示全部楼层  来自 广东
则铭 发表于 2026-8-24 20:17
先退出网盘的登录吧

这个 App 的网盘 cookie 不是本地存,而是 WebView 登录后提取 → 上传到自己后端( ...

我这个只换了获取账号信息的,其他的例如cookie储存用的还是原来的
回复

使用道具 举报

12

主题

155

回帖

621

积分

初中生

Rank: 3Rank: 3

金币
44
好评
4
信誉
100
QQ
发表于 3 天前 来自手机  | 显示全部楼层  来自 广东
则铭 发表于 2026-8-24 20:17
先退出网盘的登录吧

这个 App 的网盘 cookie 不是本地存,而是 WebView 登录后提取 → 上传到自己后端( ...

这个东西能提速吗 感觉不能吧 网盘账号没有会员感觉做不到提速
回复

使用道具 举报

31

主题

1312

回帖

7194

积分

硕士生

Rank: 6Rank: 6

金币
3032
好评
14
信誉
101
发表于 3 天前 来自手机  | 显示全部楼层  来自 重庆
Cynnie 发表于 2026-8-24 20:19
我这个只换了获取账号信息的,其他的例如cookie储存用的还是原来的

IDA MCP 验证完成,结论比预想的还要实锤。分两部分汇报:

一、作者能否获取 cookie?—— 不是"能获取",是"cookie 的唯一权威存储就在作者服务器"
IDA 里 QuarkApiService 的 cookie 管理面是完整的生命周期闭环,四个接口齐活(blutter 地址与 IDA 完全对应,基址 0x0):

函数    地址    请求    作用
saveCookie    0x5a5c50    PUT /api/v1/quark/cookie    上传 WebView 提取的 cookie,body {"cookie": "..."}
getCookie    0x5a88f0    GET /api/v1/quark/cookie    从服务端取回 cookie(响应 data.cookie 字段)
refreshCookie    0x439958    POST /api/v1/quark/cookie/refresh    服务端主动刷新 cookie
verifyCookie    0x5a8478    POST /api/v1/quark/cookie/verify    验证 cookie 有效性
含义:

App 本地不持久化 cookie——要用时 GET 从作者服务器取回,服务端是唯一 source of truth
作者服务器持有所有用户的全部网盘 cookie,且能主动 refresh(即使客户端不在线也能刷新/续期)
还发现自动刷新闭环:夸克页面 _autoRefreshCookie @0x5a59dc —— 等 WebView 加载 → extractCookie → cookie 长度≥25 就 saveCookie 上传 → verifyCookie 验证,失败延时重试最多 3 轮。用户 cookie 过期时 App 会自动重新登录并再次上传,作者手里的 cookie 永远是最新的
一句话:用户把夸克/百度/UC/微云/天翼/蓝奏等网盘登录态主动交给了 App 运营方,作者拥有全部用户的网盘凭证。
回复

使用道具 举报

12

主题

155

回帖

621

积分

初中生

Rank: 3Rank: 3

金币
44
好评
4
信誉
100
QQ
发表于 3 天前 来自手机  | 显示全部楼层  来自 广东
则铭 发表于 2026-8-24 20:20
IDA MCP 验证完成,结论比预想的还要实锤。分两部分汇报:

一、作者能否获取 cookie?—— 不是"能获取" ...

感觉不安全
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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