背景

在逆向 B 站 Android APP 8.91.1(tv.danmaku.bili,build 8911100)时,发现 B 站使用了两套独立的 appkey/secret 体系,分别用于账号体系和业务接口,混用会导致签名失败。

逆向方法:Frida OkHttp hook + APK DEX 字符串扫描

两套签名体系

套件 AppKey 用途
Passport 783bbb7264451d82 注册/登录/SMS/SSO
App 1d8b6e7d45233436 播放/推荐/互动/投稿/心跳

差异细节

两套的签名算法结构相同,但 URL-encoding 行为不同:

  • Passporturllib.parse.quote(v, safe='') — 全编码
  • Appurllib.parse.quote(v, safe='-_.~') — 保留 unreserved chars

签名算法实现

import hashlib, time, urllib.parse

def sign_app(params: dict, appkey: str, secret: str) -> dict:
    params = dict(params)
    params['appkey'] = appkey
    params['ts'] = str(int(time.time()))
    # 按 key 字典序排列
    sorted_items = sorted(params.items())
    # App 套件:safe='-_.~'
    raw = '&'.join(f'{k}={urllib.parse.quote(str(v), safe="-_.~")}' for k, v in sorted_items)
    raw += secret
    params['sign'] = hashlib.md5(raw.encode()).hexdigest()
    return params

x-bili-ticket:HMAC-SHA256 鉴权票据

B 站 gRPC 和部分 REST 接口需要携带 x-bili-ticket 请求头,这是一个基于 HMAC-SHA256 的时间戳签名,从 APK DEX 扫描中定位到密钥。

算法(已通过 /x/web-interface/nav HTTP 200 验证)

import hmac, hashlib, time

_TICKET_KEY = b'XgwSnGZ1p'

def gen_ticket(ts: int = None) -> str:
    ts = ts or int(time.time())
    msg = f'ts{ts}'.encode()
    return hmac.new(_TICKET_KEY, msg, hashlib.sha256).hexdigest()

def ticket_headers(ts: int = None) -> dict:
    ts = ts or int(time.time())
    return {
        'x-bili-ticket': gen_ticket(ts),
        'x-bili-ticket-ts': str(ts),
    }

B 站支持将 APP 登录态转换为 Web Cookie,全链路:

access_token
   POST /x/passport-login/web/sso/ticket   -> sso_ticket(一次性短效)
  -> GET  /x/passport-login/web/crossDomain  -> Set-Cookie: SESSDATA / bili_jct
def sso_get_cookie(access_token: str) -> dict:
    # Step 1: 换取 sso_ticket
    r1 = session.post('https://passport.bilibili.com/x/passport-login/web/sso/ticket',
        data=sign_app({'access_key': access_token}, PASSPORT_KEY, PASSPORT_SECRET))
    sso_ticket = r1.json()['data']['sso_ticket']

    # Step 2: 用 sso_ticket 获取 Web Cookie
    r2 = session.get('https://passport.bilibili.com/x/passport-login/web/crossDomain',
        params={'sso_ticket': sso_ticket},
        allow_redirects=True)
    return {c.name: c.value for c in r2.cookies}

逆向依据:APK DEX 扫描发现 SSOActivityisSupportSSOLoginsetSSOCookie 等类名

已发现的 GAIA 风控接口

APK DEX 扫描中发现了隐秘的风控上报接口,目前未公开文档:

接口 说明
POST /x/internal/gaia-gateway/ExClimbCongLing 设备环境特征上报
POST /x/internal/gaia-gateway/ExClimbKunLun 行为特征上报
POST /x/internal/gaia-gateway/ExClimbAppleTrees 轨迹/图形上报

这些接口在 SMS 发送触发风控时被调用,Protobuf 格式,字段含义待进一步逆向。