title: Reverse Engineering Report: Manwa User-Agent Automation
date: 2026-02-08
author: promise
tags: - Protocol Analysis - Python - WAF Evasion - Network Security categories: - Engineering Log
协议逆向工程报告:基于 TLS 指纹伪造的 Web 自动化方案
摘要 (Abstract)
本文档详细记录了针对目标 Web 资源站点(Manwa)的自动化签到系统的开发过程。项目旨在通过逆向分析 HTTP 通信协议,绕过前端反调试机制与后端 Cloudflare WAF 防护,实现无人值守的账户状态维护。
分析过程涵盖了从 OSI 模型应用层(HTTP Payload)到表示层(TLS Handshake)的全链路调试。最终方案采用 Python 编写,集成了指纹伪造、会话劫持与多线程并发控制技术。
1. 项目背景与技术选型 (Background & Stack)
在 Web 自动化领域,基于 Selenium/Playwright 的“浏览器模拟”方案虽然直观,但资源占用高、执行效率低。为了在低功耗设备(如云服务器或后台服务)上长期运行,基于 HTTP 协议逆向 的“无头(Headless)”方案是最佳选择。
技术栈 (Tech Stack):
- Language: Python 3.10+
- Network Analysis: Chrome DevTools (Network/Sources Panel)
- WAF Bypass: Cloudscraper (基于 OpenSSL 修改的底层网络库)
- Session Management: Cookie Injection
2. 第一阶段:威胁建模与侦查 (Reconnaissance)
2.1 前端反调试机制 (Anti-Debugging Bypass)
在初始化侦查阶段,尝试开启 DevTools 控制台时,目标站点立即触发了基于 JavaScript 的无限 debugger 循环。这是一种常见的前端防御手段,旨在阻止逆向工程师挂起线程查看变量。
- 现象描述:页面渲染中断,主线程被锁死在
paused in debugger状态。 - 对抗策略:
- 进入 Chrome Sources 面板。
- 启用 Deactivate breakpoints(禁用所有断点)功能。
- 强制通过
Step over跳出陷阱逻辑。
2.2 流量持久化与会话重放
目标站点的登录逻辑伴随 302 Found 重定向,导致 Network 面板的日志在页面跳转瞬间被清空。
关键配置
启用 Network 面板的 Preserve log 选项,成功捕获了跳转前的 HTTP 数据包。分析发现,服务器并未通过 JSON 返回 Token,而是依赖 Set-Cookie 下发 PHPSESSID 进行状态管理。
3. 第二阶段:静态代码审计 (Static Analysis)
由于分析时账号处于“已签到”状态,无法通过点击按钮捕获动态请求。分析转向静态源码审计,旨在还原业务逻辑。
3.1 关键词检索与逻辑定位
利用 DevTools 的全局搜索(Ctrl+Shift+F),针对关键词 sign、daily、ucenter 进行检索。在 inject-web.js 与 ucenter 模块中发现核心逻辑。
3.2 接口反编译
通过阅读压缩后的 JavaScript 代码,重构出如下伪代码逻辑:
// 伪代码:重构后的前端业务逻辑
function checkUserStatus() {
// 目标并非独立的“签到按钮”,而是伴随“福利/积分”页面的加载触发
$.ajax({
url: "[https://manwa.me/users/welfare](https://manwa.me/users/welfare)", // [Target Endpoint]
type: "POST",
dataType: "json",
data: {
"action": "point", // [Action Dispatcher]
"page": 1
},
headers: {
"X-Requested-With": "XMLHttpRequest" // 必须携带,否则会被识别为直接访问
},
success: function(response) {
// 如果 response.msg == "ok",则触发前端 Toast 弹窗提示“已签到”
if (response.err === 0) {
renderPointList(response.list);
}
}
});
}
结论:该接口具备“查询即执行”的特性。访问积分查询接口 (/users/welfare) 会产生副作用(Side Effect),即服务端会自动检查并执行签到。
4. 第三阶段:WAF 渗透与防御规避 (WAF Evasion)
4.1 403 Forbidden 异常分析
初次使用 Python 标准库 requests 复现 Payload 时,服务器返回 403 Forbidden 状态码,响应体包含 Cloudflare 的 Challenge 页面。
- 异常原因:TLS 指纹识别(JA3 Fingerprinting)。
Cloudflare 的防火墙不仅检查 HTTP Headers(如 User-Agent),更会深入检查 TCP/IP 握手阶段的 TLS Client Hello 数据包。Python
requests库发出的加密套件(Cipher Suites)列表、TLS 扩展顺序与真实浏览器存在显著差异。
4.2 解决方案:TLS 指纹伪造
放弃标准库,引入 Cloudscraper 中间件。该库在底层修改了 OpenSSL 的握手行为,使其能够通过 Cloudflare 的启发式检测。
指纹配置策略
python # 模拟 Chrome 浏览器在 Windows 平台下的 TLS 特征 scraper = cloudscraper.create_scraper( browser={ 'browser': 'chrome', 'platform': 'windows', 'mobile': False } )
实施该策略后,HTTP 状态码由 403 恢复为 200 OK,成功建立加密隧道。
5. 第四阶段:多账户架构与数据清洗 (Architecture)
5.1 身份验证机制分析
分析 Cookie 结构发现,目标站点采用了较为传统的“明文哈希”验证:
uid: 用户唯一标识 ID。passwd: 用户密码的 Hash 值。
安全隐患:该机制允许永久性会话劫持(Persistent Session Hijacking)。只要 passwd Hash 不变,即使 PHPSESSID 过期,携带这两个字段的请求也会被服务器重新认可并颁发新会话。
5.2 数据清洗 (Sanitization)
在多账号迭代开发中,遇到 Invalid Header 异常。经调试,系从浏览器 Copy Cookie 时引入了 CRLF (回车换行符) 或 尾部空格。
为此,在请求构造层引入了预处理逻辑:
def sanitize_cookie(raw_cookie: str) -> str:
"""
清洗 Cookie 字符串,移除隐形字符,防止 HTTP 协议解析错误
"""
if not raw_cookie:
raise ValueError("Empty Cookie Detected")
return raw_cookie.strip()
5.3 并发控制与风控规避 (Jitter)
为了防止高频请求触发服务端的速率限制(Rate Limiting)或 IP 封禁,我们在任务队列中引入了 随机抖动 (Jitter) 机制。
- 策略:在每个账号任务执行完毕后,挂起线程随机时长(2~5秒)。
- 目的:模拟真实人类操作的非线性时间间隔,降低行为分析(Behavioral Analysis)风控的触发概率。
6. 最终成果与部署 (Conclusion)
6.1 执行结果
脚本运行日志显示,自动化流程已跑通:
[INFO] 🚀 [Account 1] Starting task...
[INFO] -> Accessing User Center (Pre-flight request)...
[INFO] -> Fetching welfare data points...
[SUCCESS] ✅ [Account 1] Operation successful. Response: {"msg":"ok","err":0,"list":[]}
[INFO] ⏳ Sleeping for 3 seconds...
6.2 部署建议
该脚本已去除所有阻塞性输入(如 input()),适配 CI/CD 环境或系统级定时任务(Windows Task Scheduler / Linux Crontab)。
风险提示 (Disclaimer):
本逆向工程仅用于学习与个人账号维护。鉴于 Cookie 中包含敏感凭证(passwd),建议在代码中通过环境变量(Environment Variables)注入 Cookie,严禁将凭证硬编码上传至公共代码仓库。
6.3 最终交付代码 (Source Code)
针对多账户并发与风控规避的需求,最终封装的 Python 脚本如下。该脚本已移除阻塞性交互,可直接部署于服务器定时任务。
点击展开:完整自动化脚本 (Multi-Account Support)
import cloudscraper
import time
import random
# ================= 配置区 =================
# 在这里填入你的两个账号的完整 Cookie
accounts = [
# 账号 1 的 Cookie
"这里填入第一个账号的完整Cookie...",
# 账号 2 的 Cookie
"这里填入第二个账号的完整Cookie..."
]
# ================= 核心代码 =================
def run_sign_in(cookie_str, index):
print(f"\n🚀 [账号 {index}] 正在启动任务...")
# ⚡️ 优化点:明确告诉 scraper 我们要伪装成什么浏览器
# 这能有效减少 403 的概率,模拟 Windows 平台的 Chrome
scraper = cloudscraper.create_scraper(
browser={
'browser': 'chrome',
'platform': 'windows',
'mobile': False
}
)
ucenter_url = "[https://manwa.me/ucenter](https://manwa.me/ucenter)"
welfare_url = "[https://manwa.me/users/welfare](https://manwa.me/users/welfare)"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "[https://manwa.me/ucenter](https://manwa.me/ucenter)",
"Origin": "[https://manwa.me](https://manwa.me)",
"X-Requested-With": "XMLHttpRequest",
"Cookie": cookie_str.strip() # 使用当前账号的 Cookie (自动去除首尾空格)
}
try:
# 1. 访问用户中心 (预热,触发隐式签到)
print(f" -> 正在访问用户中心...")
scraper.get(ucenter_url, headers=headers)
# 休息 1-3 秒,模拟真人操作速度 (Jitter)
time.sleep(random.randint(1, 3))
# 2. 请求福利接口 (查积分/补签)
print(f" -> 正在获取积分数据...")
data = {"action": "point", "page": "1"}
response = scraper.post(welfare_url, headers=headers, data=data)
# 3. 结果判断
if response.status_code == 200:
if "login" in response.url or "用户登录" in response.text:
print(f"❌ [账号 {index}] 失败:Cookie 已失效 (被重定向到登录页)。")
else:
# 截取返回的一小段看看
print(f"✅ [账号 {index}] 成功!服务器返回: {response.text[:60]}...")
elif response.status_code == 403:
print(f"🚫 [账号 {index}] 403 被拦截:请尝试断开网络(重启路由器/切热点)换个IP再试。")
else:
print(f"⚠️ [账号 {index}] 未知状态: {response.status_code}")
except Exception as e:
print(f"❌ [账号 {index}] 报错: {e}")
# ================= 主程序 =================
if __name__ == "__main__":
print(f"📋 共加载了 {len(accounts)} 个账号")
for i, cookie in enumerate(accounts, 1):
if not cookie or "PHPSESSID" not in cookie:
print(f"⚠️ 跳过账号 {i}:Cookie 格式看起来不对(为空或缺少PHPSESSID)")
continue
run_sign_in(cookie, i)
# ⚡️ 优化点:每个账号跑完后,强制休息 5 秒
# 这是为了防止服务器觉得“怎么这人一秒钟切一个号”,从而触发 IP 风控
if i < len(accounts):
print("⏳ 等待 5 秒切换下一个账号...")
time.sleep(5)
# input("\n所有账号运行完毕,按回车退出...") # 自动化部署时注释此行
Generated by Engineering Log System v1.0