百趣云 百趣云的博客

美团 mtgsig 参数逆向分析

mtgsig:把签名元信息摆在明面上的设计

美团 H5 接口的请求头里有一个 mtgsig,值是一串编码后的 json。解码后字段含义几乎写在脸上:

{
  "a1": "1.1",                // 算法版本
  "a2": 1787968800000,        // 毫秒时间戳
  "a3": "f3d4...",            // 随机数/nonce
  "a4": "dfp值",              // 设备指纹
  "a5": "GET",                // 请求方法
  "a6": "/api/xxx",           // 请求路径
  "a7": "query摘要",
  "a8": "body摘要",
  "a9": "最终签名"
}

字段编号是语义化的:a5、a6 告诉你签名覆盖了方法和路径,a7、a8 告诉你 query 和 body 都参与了摘要。这种"元信息透明"的设计让服务端校验策略可以灵活升级,也给逆向者画了张地图。

定位:签名是异步的

美团 H5 的签名 SDK 是独立加载的 mtgsig 相关 chunk。定位时注意一个特点:签名函数返回 Promise——因为它要先异步采集设备指纹(a4 字段的 dfp 值),再算签名。所以 hook 点要选在异步完成之后:

const _open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
    this._method = method; this._url = url;
    return _open.apply(this, arguments);
};
const _setH = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function(k, v) {
    if (k.toLowerCase() === 'mtgsig') {
        console.log('mtgsig for', this._method, this._url);
        debugger;  // 断下后看调用栈,Promise then 链的上一环就是签名函数
    }
    return _setH.apply(this, arguments);
};

算法拆解

跟进签名主函数后,流程很标准:

  1. 采集 dfp:设备指纹(ua、canvas、屏幕、字体等),独立模块异步生成,结果有缓存
  2. 拼接待签串:method + path + 排序后的 query + body 的 SHA256 + 时间戳 + nonce + dfp,按固定模板拼接
  3. HMAC-SHA256:密钥来自混淆字符串池,按接口分组多组 key
  4. 组装输出:签名连同 a1-a8 元信息一起编码成 mtgsig 头

密钥的抠取技巧:美团前端把密钥做了字符串加密存储,运行时通过解密函数还原。别去静态分析解密算法——直接在密钥使用点下断点(HMAC 的 key 参数),内存里拿到的就是明文,抄下来即可。

工程实现

import hashlib, hmac, json, time, random, string

def make_mtgsig(method, path, query, body, dfp, key):
    ts = int(time.time() * 1000)
    nonce = ''.join(random.choices(string.hexdigits.lower(), k=16))
    body_hash = hashlib.sha256(body.encode()).hexdigest() if body else ''
    # 待签串模板以实际版本为准,结构示意:
    raw = "&".join([method.upper(), path, query, body_hash, str(ts), nonce, dfp])
    sign = hmac.new(key.encode(), raw.encode(), hashlib.sha256).hexdigest()
    return {"a1": "1.1", "a2": ts, "a3": nonce, "a4": dfp,
            "a5": method.upper(), "a6": path, "a7": query, "a8": body_hash, "a9": sign}

精髓:四个实战要点

  1. dfp 是独立 SDK,可以固定。dfp 采集一次后有缓存机制,协议模拟时从真实浏览器采一个固定使用。但注意 dfp 和 ua 的匹配关系,换 ua 必须换 dfp。
  2. 时间戳容忍窗口约 5 分钟。a2 和服务器时钟偏差超窗直接返回 mtgsig expired。走代理链路时注意延迟,必要时本机校时。
  3. App 端是另一套(MTGuard)。美团 App 用的是 com.meituan.android.common.mtguard 的 native 方案,和 H5 的 mtgsig 不通用。接 App 协议要走 Frida hook MTGuard 的路线,别拿 H5 的成果硬套。
  4. 签名错误无明确报错。mtgsig 校验失败常表现为返回空数据或跳转验证页,调试时用"我的"这类简单登录态接口做签名正确性基准。

总结

  1. mtgsig 解码后是"签名+元信息"的透明结构,字段本身就是逆向地图
  2. 签名是异步的(先采指纹),hook 点要选在 Promise 完成之后
  3. 密钥在使用点内存里拿明文,别静态分析字符串解密
  4. H5 的 mtgsig 和 App 的 MTGuard 是两套体系,别混用
By 百趣云 阅读量:1 On