跳转至

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 状态。
  • 对抗策略
    1. 进入 Chrome Sources 面板。
    2. 启用 Deactivate breakpoints(禁用所有断点)功能。
    3. 强制通过 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),针对关键词 signdailyucenter 进行检索。在 inject-web.jsucenter 模块中发现核心逻辑。

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