Featured image of post 外卖优惠券的灰色技术链:从抓包到批量领券全解

外卖优惠券的灰色技术链:从抓包到批量领券全解

闲鱼低价代券背后的技术链路——SSL Pinning绕过、接口逆向、签名破解、自动化领券脚本的全流程拆解。

你在闲鱼搜"汉堡王"“必胜客"“麦当劳”,会看到大量低于官方价的代下单套餐——汉堡王招牌牛堡8件套券 ¥39.9、必胜客2件披萨券 ¥26.4。卖家标注"下单前确认好手机号"“有效期14天"“快速到账"“单次限购1单可重复下单”。

这些券从哪来?这不是一个简单的问题。答案是一条从网络抓包接口逆向自动化领券的完整技术链。

本文仅供技术学习与安全研究。批量领券倒卖违反平台协议,可能触犯《反不正当竞争法》。文中所有接口、参数均已脱敏,不提供可直接用于刷券的完整代码。

一、券的三种来路

在谈技术之前,先厘清代券市场的货源结构。V2EX 上有一条 讨论帖 把这行当的底色说得很直白:

  • 抓包+批量领券脚本——分析平台 App 的领券接口,抢隐藏券、店铺满减、定向券后转卖,这是主流技术路线
  • 企业/员工福利券流出——公司团建券、员工折扣码、银行优惠价从个人手里收来的卡,批量倒卖
  • 黑灰产——有回复直接说"大部分是黑灰产洗钱或者盗刷”,但这不是全部

你看到的汉堡王、必胜客代券,前两种最常见。判断依据:卖家在郑州+大量同类套餐+虚拟券有效期 14 天+“单次限购可重复下单”——这是批量领券脚本倒卖的典型特征,不是个人囤券的手工模式。

二、技术链路全景

1
2
3
4
5
6
7
8
9
① 抓包工具截 HTTPS(Charles / mitmproxy / Fiddler)
   ↓ 障碍:App 有 SSL Pinning(证书绑定),普通抓包看不到明文
② Frida hook 绕过 SSL Pinning → 看到明文请求
   ↓ 障碍:No Proxy 检测、VPN 检测、native 层自实现网络
③ 分析领券接口的 URL / 参数 / 签名 / 加密逻辑
   ↓ 障碍:mtgsig / waimai_sign 等动态签名,请求体加密
④ Python 脚本批量调用该接口 → 定时抢券
   ↓ 障锐:平台反爬识别高频请求拉黑 token
⑤ GitHub Action / 腾讯云函数定时跑(11/17/21 点放券时段)

每一步都有一个"墙”。下面逐层拆解。

三、第一道墙:SSL Pinning

什么是证书绑定

普通 App 走系统信任的 CA 证书列表——你装个抓包工具的自签证书到设备,App 就信了,HTTPS 流量就能解密。

但 99% 的 App 不会主动加这道防护,剩下 1% 的高安全 App(银行、大厂核心 App)会做 证书绑定(Certificate Pinning):App 代码里硬编码只信任特定证书颁发者,不信任系统 CA 列表。这样即使你装了抓包证书,App 也拒绝信任,抓包工具看到的全是加密乱码。

Google 现在的文档已经明确建议不要用 SSL Pinning——因为它本质是"安全剧场”,对中间人攻击的防御有限,却阻断了设备主人对自己设备的控制权。但国内大厂外卖/电商 App 仍然普遍使用。

Frida 是什么

Frida 是一个跨平台动态插桩框架——你可以用 JavaScript 写脚本,在 App 运行时修改它的行为。可以 hook 任意函数、改返回值、记录参数、禁用功能。对 Android 来说,它通过一个运行在 root 设备上的 frida-server,让你从电脑端实时操控手机上的 App。

用它绕过 SSL Pinning 的核心思路:找到 App 里做证书校验的那个函数,hook 它,让它永远返回"校验通过"。

BoringSSL 通用绕过方案

最有价值的技术来自吾爱破解论坛的一条帖子——《字节系app通用抓包方案》。它针对字节跳动旗下 App(抖音、番茄小说等),但其原理对所有用 BoringSSL(Google 的 OpenSSL 分支)做网络层的大厂 App 都适用。

原理:BoringSSL 的证书校验通过 SSL_CTX_set_custom_verify 注册一个回调函数:

1
2
3
4
5
6
7
8
void SSL_CTX_set_custom_verify(
    SSL_CTX *ctx,
    int mode,
    enum ssl_verify_result_t (*callback)(SSL *ssl, uint8_t *out_alert)
) {
    ctx->verify_mode = mode;
    ctx->custom_verify_callback = callback;
}

校验结果是个枚举:

1
2
3
4
5
enum ssl_verify_result_t {
    ssl_verify_ok,      // 0 — 校验通过
    ssl_verify_invalid, // 1 — 校验失败
    ssl_verify_retry,   // 2 — 需重试
};

绕过方法:hook 这个回调函数,让它的返回值永远保持为 0ssl_verify_ok)。这样无论证书是不是你抓包工具自签的,App 都认为"校验通过",明文 HTTPS 流量就被你的抓包工具看到了。

实际脚本框架

Frida 脚本要解决两个问题:

问题1:so 库加载时机。直接 Module.getExportByName('libsscronet.so', 'SSL_CTX_set_custom_verify') 会报错 unable to find module 'libsscronet.so'——因为你注入太早了,这个动态库还没加载。解决方案是监听 android_dlopen_ext(系统加载 so 库的函数),等目标 so 加载完再 hook:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function onLoad(name, callback) {
    const android_dlopen_ext = Module.findExportByName(
        null, "android_dlopen_ext"
    );
    if (android_dlopen_ext != null) {
        Interceptor.attach(android_dlopen_ext, {
            onEnter: function (args) {
                if (args[0].readCString().indexOf(name) !== -1) {
                    this.hook = true;
                }
            },
            onLeave: function (retval) {
                if (this.hook) {
                    callback();  // so 加载完了,现在可以 hook 了
                }
            }
        });
    }
}

问题2:替换回调返回值。拿到 SSL_CTX_set_custom_verify 后,hook 它的参数3(callback),让回调永远返回 0:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function main() {
    Java.perform(function () {
        let SSL_CTX_set_custom_verify = Module.getExportByName(
            'libsscronet.so', 'SSL_CTX_set_custom_verify'
        );
        Interceptor.attach(SSL_CTX_set_custom_verify, {
            onEnter: function (args) {
                let callback = args[2];  // 参数3:回调函数指针
                // 替换为永远返回 0 的函数
                Interceptor.replace(callback, new NativeCallback(function () {
                    return 0;  // ssl_verify_ok
                }, 'int', []));
            }
        });
    });
}

启动方式

1
2
3
4
5
# spawn 模式(App 启动时就注入,适合 hook 早期加载的逻辑)
frida -U -f com.ss.xxx.aweme -l bytedance_bypass.js

# attach 模式(注入到已运行的 App,适合 hook 后期逻辑)
frida -U -n "抖音" -l bytedance_bypass.js

-U = 通过 USB 连接设备,-f = spawn 启动并注入,-l = 加载脚本。

GitHub 上 CYRUS-STUDIO/frida-ssl-pinning-bypass 提供了 Java 层 + Native 层全自动的 SSL Pinning 绕过脚本,不需要针对每个 App 单独写,通用性更强。Frida CodeShare 上也有 universal robust bypass 可直接用。

四、绕过 Pinning 还不够:反抓包对抗

绕过 SSL Pinning 只是第一步。现在的 App 还有更深的反抓包手段:

No Proxy(禁用代理)

App 代码里主动设置不走系统代理,直接连服务器:

1
2
3
val client = OkHttpClient.Builder()
    .proxy(Proxy.NO_PROXY)  // 禁用系统代理
    .build()

这样 Charles 在系统代理模式下根本抓不到包。绕过:用 VPN 强制转发——比如 Drony 这个 Android 代理客户端,在 VPN 层把 App 流量重定向到你的抓包工具,绕过 Java 层的 Proxy.NO_PROXY 设置。

VPN 检测

App 会检测 VPN 是否活跃:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fun isVpnActive(context: Context): Boolean {
    val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE)
        as? ConnectivityManager ?: return false
    cm.allNetworks.forEach { network ->
        val caps = cm.getNetworkCapabilities(network)
        if (caps?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true)
            return true
    }
    return false
}

检测到 VPN 就拒绝请求或降级。绕过:Frida hook 这个方法让它永远返回 false,或者用 ADB 代理代替 VPN。

ADB 代理方案

不依赖 VPN/V-Proxy,直接用 ADB 命令在系统层设代理:

1
2
3
4
5
6
7
8
# 设置系统代理(不在 Wi-Fi 设置里显示,隐蔽)
adb shell settings put global http_proxy 127.0.0.1:8888

# 启用 USB 端口转发(让设备的 8888 流量经 USB 到电脑)
adb reverse tcp:8888 tcp:8888

# 清除代理
adb shell settings put global http_proxy :0

这个方案绕过了 App 层的 VPN/代理检测,因为流量在系统底层就被转走了。

五、第二道墙:接口签名逆向

抓到明文请求后,你不能直接拿这个 URL 去批量领券——因为大厂接口都有请求签名

美团外卖的双签名机制

美团外卖 API 有两个关键加密参数(详见 这篇分析):

  • waimai_sign:请求内容数字签名,对 URL + 请求体 + 时间戳做加密
  • mtgsig:动态令牌,具有时效性和唯一性,由客户端 JS/native 层实时生成

缺少或错误的参数返回 403 Forbidden:

1
{"code": 403, "message": "Forbidden", "data": null}

典型请求结构:

1
2
3
4
5
6
headers = {
    "mtgsig": "v1.a1MjAxMjAyM...",  # 动态加密令牌
    "User-Agent": "MeituanGroup/7.70.5",
    "Host": "wmapi.meituan.com",
    "Content-Type": "application/json"
}

mtgsig 逆向方法

mtgsig 的生成逻辑在客户端 JS 里,做了混淆。博客园有一篇 某团小程序 token/mtgsig 分析流程 详细拆解了方法:

  1. 定位加密入口:在 Chrome DevTools 里给 SSL_CTX_set_custom_verify 或加密函数打断点,触发领券操作,看调用栈
  2. 识别混淆模式:美团的混淆是"n 函数替换"——一个 n(数字) 形式的函数调用替换常量。用 Babel AST 遍历可以批量还原:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
traverse(ast, {
    CallExpression(path) {
        let { callee, arguments } = path.node;
        if (arguments.length !== 1) return;
        if (!types.isIdentifier(callee, { name: "n" })) return;
        if (!types.isNumericLiteral(arguments[0])) return;
        let value = eval(path.toString());
        path.replaceWith(types.valueToNode(value));
    },
});
  1. 找到生成核心:搜索关键词 w1.3,往上找到 require JSguardrequire /rohr.js——这两个是 mtgsig 的生成入口
  2. 扣代码:把加密函数全部扣出来,本地用 Node.js 跑,生成可用的 mtgsig

Frida 直接调用加密函数

如果 JS 混淆太重扣不动,还有一招:Frida 直接调用 App 内部的加密函数。不还原算法,而是在 App 运行时用 Frida 调它的 native 加密方法,让它帮你算签名。这绕过了所有混淆——因为你不需要理解算法,只要知道调用入口。

1
App 内部加密函数(明文参数) → [Frida hook 调用] → 拿到 mtgsig

这是逆向工程里"最懒也最有效"的方法:不破算法,直接用你的运行时帮你算。

六、第三道墙:反爬与风控

拿到接口、能生成签名后,还不能无脑刷——平台有行为风控。

美团的 openresty 反爬

美团后端用 openresty(Nginx + Lua)识别高频请求,判定为爬虫后拉黑 token。GitHub 上 jiuzhi-1/meituan-shenquan 的 README 里明确警告:

不建议让脚本时刻运行着,建议按照默认定时任务每天三个时间段即可,否则会被美团后端服务器的 nginx 插件 openresty 识别为爬虫并拉黑美团 token。

美团天天神券放券时间是北京时间 11 点、17 点、21 点,脚本就只在这三个时间点跑。

设备指纹识别

请求参数里包含多个设备相关参数(uuid、设备型号、IMEI 哈希等)构建唯一标识。同一个设备指纹高频请求会被识别。绕过:轮换设备指纹参数,或用多设备/多账号分布式跑。

行为模式分析

服务器分析请求的频率、顺序。正常的领券行为:打开 App → 浏览 → 点领券。脚本行为:直接高频调接口。绕过:脚本模拟浏览路径,先请求页面再请求领券,加随机延时。

七、自动化部署:GitHub Action

领券脚本不需要自己有服务器——GitHub Action 免费跑定时任务。

meituan-shenquan 项目的工作流:

  1. Fork 项目到自己的 GitHub 账号
  2. 在 Settings → Secrets 里加三个环境变量:MTTOKEN(美团 web token)、PUSHPLUSTOKEN(推送)、SERVERKEY(推送)
  3. .github/workflows/action.yml 里配置 cron 定时(默认 11/17/21 点)
  4. GitHub Action 到点自动跑脚本,抢到的券通过 pushPlus/server酱推送到微信

token 从美团网页版抓——浏览器登录美团,DevTools 里拿 cookie 里的 token 字段。token 会过期,脚本有失效告警功能,推送告诉你什么时候要换。

饿了么的方案类似(见 wss1029681084/Autosign):从 h5.ele.me H5 页面抓 cookie(要包含 userid 字段),Python 脚本在 10/14/17 点定时抢 10 元无门槛红包。

八、完整工具链清单

抓包工具(截流量):

工具特点
CharlesGUI 友好,SSL Proxying 成熟,Mac 上最流行
mitmproxy命令行+脚本化,rewritemap local 可改请求,开源免费
FiddlerWindows 老牌,企业版有强力的协议分析

SSL Pinning 绕过(解密 HTTPS):

方案适用
CYRUS-STUDIO/frida-ssl-pinning-bypassJava+Native 全自动通用绕过
52pojie 字节系通用方案hook BoringSSL SSL_CTX_set_custom_verify
httptoolkit 教程从零开始的 Frida 入门全流程
B站 BV1ZT421k7xw2024 Frida 逆向与抓包实战视频教程

接口逆向(破签名):

资源内容
美团外卖 waimai_sign/mtgsig 逆向双签名机制+参数结构
某团小程序 token/mtgsig 分析AST 反混淆+扣代码方法
iOS 美团逆向工程分析iOS 端的接口分析

自动化领券项目(GitHub 源码):

项目平台特点
jiuzhi-1/meituan-shenquan美团文档最完整,GitHub Action 部署,token 失效告警
wss1029681084/Autosign饿了么H5 抓 cookie,10/14/17 点定时抢
chenbool/python-selenium多平台美团/饿了么/OFO/QQ 空间签到合集

论坛社区(找最新教程):

  • 吾爱破解 52pojie.cn — 国内逆向安全第一论坛,搜索"抓包"“领券"“SSL pinning”
  • 看雪 bbs.kanxue.com — 更偏底层逆向,native 层分析质量高
  • V2EX — 不是技术论坛但讨论灰产生意模式很有价值

九、法律与伦理边界

这条技术链的每一环都有明确的法律风险:

  • 抓包+绕过 SSL Pinning:用于自己的设备/App 分析自己的流量是合法的安全研究;但用于批量刷券违反平台用户协议
  • 接口逆向+批量领券:可能构成《反不正当竞争法》第二条的不正当竞争,或《计算机信息系统安全保护条例》的非法侵入
  • 倒卖:批量领券倒卖是明确的灰产行为,大额可能涉及非法经营
  • Frida 注入他人设备:如果用于别人的手机(非自己设备),可能构成非法侵入计算机信息系统

V2EX 讨论里有人直接说"大部分是黑灰产洗钱或者盗刷”——这条产业链的上游确实有盗刷信用卡洗钱的成分,不只是技术刷券。你看到的低价代券,无法分辨是技术刷出来的还是黑灰产洗出来的。

技术本身是中性的——抓包、逆向、自动化是安全研究的基础工具。但把它们组合起来批量刷券倒卖,就从"研究"变成了"灰产"。

十、如果你想做的是另一件事

如果你的目标不是刷券倒卖,而是理解这套技术做安全研究或自己的自动化工具,价值最高的学习路径是:

  1. 先学 Frida——它是整条链的支点。B站 BV1ZT421k7xw 的视频教程从 Python 入门到 Hook 实战一周可学完
  2. 再学 AST 反混淆——美团 mtgsig 用的混淆模式是行业通用的,Babel traverse 批量还原是核心技能
  3. 最后学接口自动化——拿 meituan-shenquan 的源码读,理解 token 获取、定时调度、反爬规避的工程实现

这三个技能组合起来,不只是刷券——它们是所有 App 自动化(签到、抢购、数据采集)的通用底座。安全研究员、爬虫工程师、自动化测试工程师用的都是同一套工具链。


本文所有接口、参数、代码示例均已脱敏处理,仅供安全研究学习。完整可用的刷券代码不在本文范围内。