资讯动态

某广告推广平台 API 签名算法逆向分析还原

发布时间:2026/9/26 4:08:58 来源:尧图企业网站定制
阅读须知本文章中所有内容仅供学习交流使用不用于其他任何目的不提供完整代码抓包内容、敏感网址、数据接口等均已做脱敏处理严禁用于商业用途和非法用途否则由此产生的一切后果均与作者无关擅自使用本文讲解的技术而导致的任何意外作者均不负责若有侵权请在公众号联系作者立即删除分析概览|参数|所在位置|算法|密钥/参数来源|| — | — | — | — ||‎username|登录请求 Body|AES-256-CBC|Key/IV 硬编码在 JS bundle||‎password|登录请求 Body|RSA-1024 PKCS[#1](javascript: v1.5|公钥硬编码在 JS bundle||‎iamData|每个请求 Header|RSA-1024 PKCS[#1](javascript: v1.5|明文 ‎dspAccountId,dspRoleId来自登录响应||‎Extra-Data|每个请求 Header|RSA-1024 PKCS[#1](javascript: v1.5|明文 ‎{}或 ‎{providerId, adAccountId}|JS 源码溯源分析从抓包入手核心代码全部位于 Vite 打包产物https://xxx.com/assets/index-Cnrq_27l.js ← 主 bundle670 KB https://xxx.com/assets/index-Dzn80_QX.js ← 登录页组件10 KB模块别名追踪链Vite tree-shaking 后函数名被混淆|原始函数|主 bundle 导出别名|登录组件导入别名|作用|| — | — | — | — ||‎MH|‎ai|‎we|username AES 加密||‎tv|‎ah|‎he|RSA 加密password / header||‎bH|‎aj|‎ye|登录 API 调用|登录组件核心调用index-Dzn80_QX.jsconst h{username: we(String(n.username).trim().toLowerCase()), // MH()password: he(n.password)// tv()};const tawait ye(h);// POST /account/login请求拦截器index-Cnrq_27l.js每次请求自动注入gt.interceptors.request.use(async e{const tda();// Pinia store const{accessToken: n, dspAccountId: r, dspRoleId: o}t.userInfo||{};if(ro)e.headers.iamDatatv(${r},${o});e.headers[UBiX-Auth]Bearer n;const{adAccountId: s, providerId: a}t.operatedAccountInfo||{};leti{};if(sa)i{providerId: a, adAccountId: s};e.headers[Extra-Data]tv(JSON.stringify(i));returne;});加密算法还原1. username — AES-256-CBCJS 源码函数名MH使用 CryptoJSfunctionMH(e){const tmn.enc.Utf8.parse(9h1ho2lhu0gr67fpe56fi09n9ned6g6h);// key(32bytes)const nmn.enc.Utf8.parse(com.ubix.platfor);// iv(16bytes)const rmn.enc.Utf8.parse(e);const omn.AES.encrypt(r, t,{iv: n, mode: mn.mode.CBC, padding: mn.pad.Pkcs7});returnmn.enc.Base64.stringify(o.ciphertext);}算法参数|项|值|| — | — ||算法|AES-256-CBC||Key|‎9h1ho2lhu0gr67fpe56fi09n9ned6g6h32 字节 UTF-8 字符串||IV|‎com.ubix.platfor16 字节 UTF-8 字符串||Padding|PKCS7||输入预处理|‎trim().toLowerCase()||输出格式|标准 Base64|验证输入ddddddddqq.comPHP 输出: Po67jmVvqLVmekwpghfGDpm/HXTLl6KBQ9e8UfnSiQA抓包实际: Po67jmVvqLVmekwpghfGDpm/HXTLl6KBQ9e8UfnSiQA匹配: ✅ 完全一致PHP 实现define(UBIX_AES_KEY,9h1ho2lhu0gr67fpe56fi09n9ned6g6h);define(UBIX_AES_IV,com.ubix.platfor);functionubix_encrypt_username(string$username): string{$usernamestrtolower(trim($username));$encryptedopenssl_encrypt($username,aes-256-cbc, UBIX_AES_KEY, OPENSSL_RAW_DATA, UBIX_AES_IV);returnbase64_encode($encrypted);}2. password / Extra-Data / iamData — RSA-1024 PKCS[#1](javascript: v1.5三者共用同一个加密函数tv()区别仅在于明文内容。JS 源码tv→fNfunctiontv(e){returnfN(e,MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDV-h87Pz_xcHOG...);}functionfN(e, t){const nnew JSEncrypt();// JSEncrypt 库PKCS#1 v1.5n.setPublicKey(t);// 公钥为 URL-safe Base64 编码的 SPKI const rn.getKey();// 超过117UTF-8 字节时分段加密 // 每段 ≤117字节分别 RSA 加密hex 拼接后统一编码 const lr.encrypt(e);// 返回 hex 字符串returnQ2(l);// 自定义 hex → base64见下节}RSA 公钥JS 中为 URL-safe Base64PHP 中需转换为标准 PEMJS bundle 原始值URL-safe Base64- 代替 _ 代替 /: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDV-h87Pz_xcHOGIlu6bizg... **算法参数**|项|值||----|-----||算法|RSA-1024PKCS#1 v1.5 填充 ||公钥格式|SPKISubjectPublicKeyInfoDER 编码Base64 包装||最大单次明文|117字节128 字节模数 −11字节 padding overhead||超长分段策略|每段 ≤117UTF-8 字节各段独立 RSA 加密原始字节拼接后统一 Base64||输出格式|标准 Base64128 字节密文 →172字符1 个填充|**PHP 实现** phpfunctionubix_rsa_encrypt(string$plaintext): string{$pubKeyopenssl_pkey_get_public(UBIX_RSA_PUBLIC_KEY);if(strlen($plaintext)117){openssl_public_encrypt($plaintext,$result,$pubKey, OPENSSL_PKCS1_PADDING);returnbase64_encode($result);}// 超长分段$chunks[];$current;$currentBytes0;foreach(preg_split(//u,$plaintext, -1, PREG_SPLIT_NO_EMPTY)as$char){$cbstrlen($char);if($currentBytes$cb117$current!){$chunks[]$current;$current;$currentBytes0;}$current.$char;$currentBytes$cb;}if($current!)$chunks[]$current;$bin;foreach($chunksas$chunk){openssl_public_encrypt($chunk,$out,$pubKey, OPENSSL_PKCS1_PADDING);$bin.$out;}returnbase64_encode($bin);}关键发现Q2 函数等价于标准 Base64JS bundle 中对 RSA 密文未使用btoa()而是自定义Q2()函数进行编码const vsABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/;functionQ2(hexStr){lett0, n, r;for(;t 3hexStr.length;t3){nparseInt(hexStr.substring(t, t 3),16);// 取3个 hex 字符12bits rvs.charAt(n6) vs.charAt(n63);// 输出2个 Base64 字符}// 处理剩余1或2个 hex 字符并补填充...}初看像自定义编码实际上是标准 Base64 的等价实现。数学证明对于任意 3 字节[B1, B2, B3]其十六进制串为 6 个字符Q2 第一组h1h2h3B1 的两个 nibble B2 高 nibble: nB1*16 (B24)→ n6B12Base64 标准第1字符 n63B1[1:0]4|B2[7:4]Base64 标准第2字符 Q2 第二组h4h5h6B2 低 nibble B3 的两个 nibble: nB2[3:0]*256 B3 → n6B2[3:0]2|B36Base64 标准第3字符 n63B3[5:0]Base64 标准第4字符结论Q2(hex(bytes))≡base64(bytes)PHP 直接base64_encode()即可无需实现 Q2。请求头生成逻辑iamData明文${dspAccountId},${dspRoleId}来源登录响应data字段直接提供{dspAccountId:qq123456789,dspRoleId:9}示例明文qq123456789,9生成ubix_rsa_encrypt(qq123456789,9)→ 172 字符 Base64免责声明本报告仅供安全研究和学习目的请勿用于非法用途。本文章中所有内容仅供学习交流使用不用于其他任何目的不提供完整代码抓包内容、敏感网址、数据接口等均已做脱敏处理严禁用于商业用途和非法用途否则由此产生的一切后果均与作者无关擅自使用本文讲解的技术而导致的任何意外作者均不负责若有侵权请在公众号联系作者立即删除

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑