【小程序逆向】某游快爆 AI 逆向 sign参数:AI 辅助分析与 MCP 工具链实战

声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!

逆向目标

目标:某游快爆微信小程序

目标页面:游戏论坛

逆向参数:sign

工具总结

工具 用途 链接
Reqable 抓包工具,自带 MCP 服务器 官网GitHub 仓库
Qoder AI 智能体客户端,串联所有工具(也可换成 Claude Code / Codex / Cursor 等) 官网
reverse-skill 逆向技能路由包,让 AI 具备完整的逆向分析能力 GitHub 仓库
First 微信小程序安全调试工具(Frida + CDP + MCP),解包、Hook、调试一体化 GitHub 仓库

逆向分析

前两篇我们逆向的是 APP 里的 so,又是 Frida 反调试绕过、又是 OLLVM 解混淆,属于硬碰硬。这一篇换个小程序试试------并且全部由 AI 配合工具链完成。

一、工具配置

工欲善其事,必先利其器。这次的核心思路是:把抓包工具、调试工具通过 MCP 协议接入 AI

1. Reqable:抓包 + MCP 服务器

Reqable 是一个抓包工具(官网GitHub 仓库),它自带 MCP 服务器,能让 AI 直接读取抓包记录、生成 curl、重放请求等等。

打开 Reqable,在"调试"菜单里找到 MCP 选项:

点击后,弹出手动配置 MCP 的窗口,核心信息就一个------MCP 服务器的可执行文件路径,以及对应的 JSON 配置:

配置 JSON 长这样:

json 复制代码
{
  "mcpServers": {
    "reqable": {
      "command": "D:\\App\\reqable\\Reqable\\mcp-server.exe",
      "args": []
    }
  }
}

可选参数有三个:

参数 说明
--host Reqable 所在设备的 IP 地址,默认本机 127.0.0.1
--port Reqable 的代理端口,默认自动获取(通常是 8888)
--scope 注册哪些工具:minimal 仅常用工具(默认),all 注册全部工具
2. 给 AI 接上 Reqable

我这里用的 AI 客户端是 Qoder(官网),大家也可以换成自己习惯的 Agent。在 Qoder 的连接器页面,选择"通过 JSON 导入",把刚才的配置粘贴进去:

导入成功后,AI 就具备了"控制 Reqable "的能力。

3. 安装 reverse-skill

接下来安装 reverse-skill(逆向技能路由包,20k+ stars)。这是一个专门给 AI 用的技能包,里面封装了几十条逆向相关的工作流,覆盖 APK 逆向、JS 签名、协议分析、渗透测试等方向,兼容 Claude Code /Codex / Cursor 等主流 AI 客户端。

安装很简单:把仓库地址发给 AI,让它自己装就行(装进自己的 skills 目录,然后加载)。

安装这个主要是让AI有一个基本的逆向思路,不会胡乱跑。

其实这个skill本身是没有小程序逆向部分的,不过有前端JS逆向的部分,两者思路基本是相通的,所以拿过来用一下。

4. First:小程序安全调试工具

小程序逆向和 APP 逆向不一样,我们没法直接对小程序做动态调试。这里用到的是 FirstGitHub 仓库,基于 WMPFDebugger 二次开发,感兴趣的可以了解一下)。

它通过 Frida 注入微信进程,再用 CDP(Chrome DevTools Protocol)协议桥接 ,让浏览器里的 DevTools 直接调试小程序,支持 wxapkg 解密解包、wx.* API 捕获、云函数动态捕获、路由枚举等。自 v1.1.0 起闭源,最新版直接去 GitHub Releases 下载编译好的版本Releases 地址)。

注意它对微信版本有要求,仓库主页有 WMPF 版本支持列表,目前推荐微信 4.1.10,可以按列表去下载对应版本。

打开 First,点击启动,它会自动注入微信:

注意运行日志,Frida 注入成功后会提示 you can now open any miniapps,并给出一段 DevTools 调试链接。

First 同样自带 MCP 服务器(端口 4554),在 First 的 MCP 页面开启服务,然后把它的配置也导入 Qoder:

json 复制代码
{
  "mcpServers": {
    "first-miniapp": {
      "url": "http://127.0.0.1:4554/sse"
    }
  }
}

然后打开任意小程序,验证一下 First 是否工作正常:复制控制台里的链接:

复制代码
devtools://devtools/bundled/inspector.html?ws=127.0.0.1:62000

粘贴到浏览器地址栏,如果打开了 DevTools 调试界面,说明 First 启动成功:

5. 连接器就绪

回到 Qoder,确认三个连接器全部开启:QoderWork(本机执行能力)、reqable(抓包数据)、first-miniapp(小程序调试)

工具全部安装好了,接下来我们开始。

二、抓包

打开 Reqable,然后在微信里打开好游快爆小程序,进入香肠派对的游戏论坛,随便滑动一下列表。回到 Reqable,可以看到抓到了一条 POST 请求:

请求信息如下:

项目 内容
请求地址 https://m.3839.com/miniapp/api.php?m=api&c=topic&a=section_topic&v=1.1
请求方式 POST(application/x-www-form-urlencoded)
接口作用 获取某个游戏版块(section_id)的帖子列表,按编辑时间排序、分页
关键参数 sign:32 位十六进制字符串,典型 MD5 hex 特征

请求体里的参数:

参数 说明
version 1.0.0 接口版本
timestamp 1787542341 请求时间戳
type 1 登录态类型
token xxxxx 登录凭证
uid xxxxx 用户 ID
section_id 11631 游戏版块 ID(香肠派对)
topic_list_type all 帖子列表类型
sort edit_time 排序方式
last_id 0 分页游标
cursor 0 分页游标
screen_id 0 屏幕来源

我们要分析的就是 sign 参数

三、交给 AI

1. 一句话下指令

在 Qoder 里输入指令:

加载 📦 reverse-skill;使用 🧩 reqable 熟悉请求;使用 🧩 first-miniapp 尝试破解 sign 参数。

然后,就是泡杯茶静静的等待了~~

2. 分析结果

大约 15 分钟后,结果出来了。

AI 做的事分两步:先通过 first-miniapp 对小程序解包,拿到 JS 源码 ;再通过 reqable 读取抓包记录,拿到真实参数。两者一对照,直接在源码里定位到了签名函数。

签名函数定位在 app-service.js 第 10562 行 ,是一个叫 l() 的通用请求封装函数,所有接口请求都会走它。核心逻辑如下(为便于阅读,做了格式化):

javascript 复制代码
var l = function (e) {
    // 盐值,硬编码在源码里
    var r = "miniapp_kgb77Mdac046167ec7bd4d3cde27ad6NBV";
    // 基础参数 {version, timestamp} + 登录态 {type, token, uid} + 业务参数
    var c = Object.assign({version: "1.0.0", timestamp: parseInt(Date.now() / 1e3)}, t, ...业务参数);
    delete c.sign;                     // 删掉旧的 sign
    c = h(c);                          // 空值参数置空(本例参数全非空,可跳过)
    // 按 key 字典序排序,拼成 key=value&key=value 的串
    var u = Object.keys(c),
        l = u.sort().map(e => e + "=" + c[e]).join("&");
    l += r;                            // 末尾直接拼盐
    c.sign = Hash("md5").update(l).digest("hex");   // 整串做 MD5
    formPost(e, c);                    // 表单提交
};

大概就是:把除 sign 外的所有参数,按参数名排序,拼成 key=value&key=value 的串,末尾加上盐,整串做 MD5。

用抓包里的真实参数复算验证一下:

复制代码
cursor=0&last_id=0&screen_id=0&section_id=11631&sort=edit_time&timestamp=1787542341&token=【脱敏】&topic_list_type=all&type=1&uid=【脱敏】&version=1.0.0
+ miniapp_kgb77Mdac046167ec7bd4d3cde27ad6NBV

得到 e0a1d6b18a3ecc8f4f63c2c8f82b438a------与抓包里的 sign 完全一致

AI 还顺手发现了两个彩蛋:同一个源码里,login 接口的签名用的不是这个盐,而是 9C198906FAD3D528EB5A504D77065913,且 key 是倒序拼接;另一个接口也用了不同的盐。不同接口的签名格式不一样,不要搞混了。

3. AI 的经验总结

AI 还总结了一套"学到什么":

两个重点:一是小程序不像 APP 有 so 层原生保护,wxapkg 解包后就是明文 JS ,salt 直接硬编码在里面,难度低了一个量级;二是分析路径非常标准:抓包看参数结构 → 反编译搜 md5/sign 关键词 → 提取函数上下文 → 用真实请求复算闭环验证。

四、脚本验证

我这边让 AI 把算法用 Python 复现一遍:

接下来我需要进行验证,请你写一个 python 脚本复现此算法,参数就使用刚刚抓包抓到的值。

AI 生成的 sign_reproduce.py 如下(token/uid 已脱敏):

python 复制代码
# -*- coding: utf-8 -*-
"""
好游快爆小程序 API 签名复现脚本
目标接口: POST https://m.3839.com/miniapp/api.php?m=api&c=topic&a=section_topic&v=1.1
请求格式: application/x-www-form-urlencoded

签名算法(逆向自 app-service.js 中的通用请求封装函数 l()):
    1. 收集参数: 基础参数 {version, timestamp} + 登录态 {type, token, uid} + 业务参数
    2. 删除值为空字符串 / null 的参数(小程序端 h() 函数干的活,本例参数全非空,可跳过)
    3. 按参数名(key)字典序升序排序
    4. 拼成 "k1=v1&k2=v2&..." 的字符串
    5. 字符串末尾直接拼接盐值
    6. 整串做 MD5,输出 32 位小写 hex,就是 sign

盐值(硬编码在小程序 JS 里):
    miniapp_kgb77Mdac046167ec7bd4d3cde27ad6NBV

运行: python sign_reproduce.py
"""

import hashlib

# ---------- 配置区 ----------

# 盐值,从反编译源码 app-service.js 提取
SALT = "miniapp_kgb77Mdac046167ec7bd4d3cde27ad6NBV"

# 抓包参数(2026-08-24 11:32 Reqable 记录 ID=57, 不含 sign 本身)
params = {
    "version": "1.0.0",
    "timestamp": "1787542341",
    "type": "1",
    "token": "【脱敏:发布前替换为真实值或打码】",
    "uid": "【脱敏:发布前替换为真实值或打码】",
    "section_id": "11631",
    "topic_list_type": "all",
    "sort": "edit_time",
    "last_id": "0",
    "cursor": "0",
    "screen_id": "0",
}

# 抓包中的真实 sign,用来比对验证
captured_sign = "e0a1d6b18a3ecc8f4f63c2c8f82b438a"


# ---------- 签名复现 ----------

def calc_sign(data: dict, salt: str) -> tuple[str, str]:
    """复现小程序端签名逻辑,返回 (sign, 拼接串)"""
    # 第 3 步: 按 key 字典序排序, 第 4 步: 拼成 k=v&k=v
    raw = "&".join(f"{k}={data[k]}" for k in sorted(data.keys()))
    # 第 5 步: 末尾拼盐
    raw += salt
    # 第 6 步: 整体 MD5
    sign = hashlib.md5(raw.encode("utf-8")).hexdigest()
    return sign, raw


# ---------- 主流程 ----------

if __name__ == "__main__":
    sign, raw_string = calc_sign(params, SALT)

    print(f"参与签名的参数个数: {len(params)}")
    print()
    print("拼接串(排序后 k=v&k=v + 盐):")
    print(raw_string)
    print()
    print(f"本脚本算出的 sign: {sign}")
    print(f"抓包里的真实 sign: {captured_sign}")
    print()
    if sign == captured_sign:
        print(">>> MATCH: 算法复现成功, sign 与抓包完全一致")
    else:
        print(">>> 不匹配!请检查参数是否与抓包一致、是否有空值参数被过滤")

运行:

11 个参数参与签名,脚本算出的 sign 和抓包里的真实 sign 一模一样,算法复现成功。

五、实战测试

为了严谨一点,我们换一个游戏的论坛,完整模拟一次带上自己 sign 的请求。

根据 AI 的分析,本接口的游戏标识参数是 section_id :11631 代表香肠派对,93 代表原神------我们就拿原神测试。

接下来你写一个 python 脚本,将 section_id 换成 93,然后模拟完整请求。

AI 生成的 sign_request.py 核心逻辑如下(签名计算函数与上面完全一致,不重复贴,只贴请求部分):

python 复制代码
import gzip
import hashlib
import io
import json
import time
import urllib.request
import urllib.parse

# ---------- 配置区 ----------

SALT = "miniapp_kgb77Mdac046167ec7bd4d3cde27ad6NBV"

API_URL = "https://m.3839.com/miniapp/api.php?m=api&c=topic&a=section_topic&v=1.1"

# 业务参数: section_id 换成 93, 其余沿用抓包值(uid/token 是你自己的登录态)
params = {
    "version": "1.0.0",
    "timestamp": str(int(time.time())),     # 当前时间戳, 动态生成
    "type": "1",
    "token": "【脱敏:发布前替换为真实值或打码】",
    "uid": "【脱敏:发布前替换为真实值或打码】",
    "section_id": "93",                     # <<< 换游戏: 11631 -> 93
    "topic_list_type": "all",
    "sort": "edit_time",
    "last_id": "0",
    "cursor": "0",
    "screen_id": "0",
}

# 请求头, 照抄抓包(Referer 和 UA 都是小程序环境标识, 缺了可能被风控拦)
headers = {
    "Host": "m.3839.com",
    "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                   "(KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 "
                   "MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI "
                   "MiniProgramEnv/Windows WindowsWechat/WMPF "
                   "WindowsWechat(0x63090a13) UnifiedPCWindowsWechat(0xf2541a35) XWEB/19977"),
    "Content-Type": "application/x-www-form-urlencoded",
    "Referer": "https://servicewechat.com/wxcce6440ef3727704/10/page-frame.html",
    "Accept": "*/*",
    "xweb_xhr": "1",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "zh-CN,zh;q=0.9",
}


# ---------- 主流程 ----------

if __name__ == "__main__":
    # 1) 算签名
    sign, raw_string = calc_sign(params, SALT)
    params["sign"] = sign
    print(f"[1] 签名计算完成: sign={sign}")

    # 2) 组装表单并发送
    body = urllib.parse.urlencode(params)
    req = urllib.request.Request(API_URL, data=body.encode("utf-8"), headers=headers, method="POST")
    print(f"[2] 发送请求: POST {API_URL}")

    # 3) 接收响应(注意响应体是 gzip 压缩的, urllib 不会自动解压, 需要手动处理)
    with urllib.request.urlopen(req, timeout=15) as resp:
        status = resp.status
        raw = resp.read()
        if resp.headers.get("Content-Encoding", "").lower() == "gzip":
            raw = gzip.decompress(raw)
        raw = raw.decode("utf-8", errors="replace")

    print(f"[3] 响应状态码: {status}")

    # 4) 解析 JSON 并展示关键信息
    data = json.loads(raw)
    print(f"    code: {data.get('code')}   msg: {data.get('msg')}")
    result = data.get("result") or {}
    posts = result.get("data") or []
    print(f"    返回帖子数: {len(posts)}")
    if posts:
        first = posts[0]
        print(f"    首条帖子: id={first.get('id')} 内容={str(first.get('content'))[:40]}"
              f"  版块sid={first.get('sid')}")

运行:

输出关键信息:code: 100、msg: ok、返回帖子数: 10、首条帖子版块 sid=93------服务端验签通过,返回的正是原神版块的数据。

测试成功,本次逆向完成。

总结

总的来说,这个小程序的加密不算很复杂,用更聪明的模型应该可以更快分析出来。

不过,AI 的发展可真的令人惊叹啊,以前要几天的工作量,现在泡杯茶的功夫就搞定了。

至此,本篇结束。

相关推荐
Huazhongzhanhui1 小时前
2026武汉国际汽车线束及连接器工业展览会智能线束新地标
大数据·人工智能
selia10781 小时前
AI手撕代码笔记
人工智能·笔记·深度学习
一只旭宝1 小时前
预约系统版本2(pyhton+flask可视化版本)
服务器·数据库·c++·笔记·python·flask
树獭哥1 小时前
量化金融入门:从数据规则到随机游走与凯利公式
人工智能·算法·金融·量化金融
联盟分享专家1 小时前
2026独立站引流推广:低成本获取网站精准流量的7个渠道
人工智能
G智爱AI1 小时前
2026工作汇报PPT AI工具实测|主流省时办公工具横向对比
人工智能·powerpoint
迪康coolmu1 小时前
当大模型遇上终端安全—AI驱动的智能运维实践
运维·人工智能·安全
小飞学编程...1 小时前
【equals 、Comparable、Comparator 三者的区别】
java·python
财迅通Ai1 小时前
科源制药携手腾讯云推进AI办公迭代升级
人工智能·云计算·腾讯云·科源制药