JS逆向 - 某程 w-payload-source 纯算与补环境实战剖析
1. 逆向目标与核心参数解析这次我们要破解的目标是某程网站航班查询接口中的关键加密参数w-payload-source。这个参数出现在请求头中看起来像是一串经过复杂处理的字符串比如102!by0EACXB4jiQ...这样的格式。在实际测试中发现如果直接修改或删除这个参数服务器会返回403错误说明它起到了重要的身份校验作用。通过抓包分析可以看到这个参数有几个明显特征开头固定带有102!前缀后面跟着的字符串长度不固定但总是Base64字符集的组合。更关键的是每次刷新页面时这个值都会变化说明它可能包含了时间戳、随机因子或浏览器指纹等动态元素。在逆向工程中这类参数通常有两种破解思路要么完全还原它的生成算法纯算要么模拟浏览器环境让它自然生成补环境。2. 快速定位加密入口的技巧实战中定位加密逻辑最快的方法就是搜索关键字符串。在开发者工具的Sources面板中全局搜索w-payload-source通常能直接找到设置该参数的代码位置。在我这次的案例中发现它在XMLHttpRequest.setRequestHeader方法中被设置。更高效的做法是在Network面板找到该请求右键选择Search in headers功能。定位到具体代码后可以观察到类似这样的调用链window.c_sign.toString(params) - Bt(params) - z_charCodeAt(z_array)其中c_sign是个关键函数通过toString()方法转换输入参数。这里有个技巧在控制台输入window.c_sign.toString查看函数体会发现它来自一个独立的c-sign.js文件。3. 补环境的核心思路与实现补环境的本质就是让JS代码感觉自己运行在真实的浏览器环境中。首先需要把c-sign.js文件完整保存下来然后在Node.js或补环境框架中运行。这时候通常会遇到各种报错比如navigator.plugins未定义、window.callPhantom不存在等。我常用的补环境方案是这样的结构// 基础浏览器环境 const fakeWindow { navigator: { plugins: [], mimeTypes: [], hardwareConcurrency: 4, platform: Win32 }, screen: { width: 1920, height: 1080 } } // 代理检测点 const handler { get(target, prop) { console.log(读取 ${prop}); return target[prop] || undefined; }, has(target, prop) { console.log(检测 ${prop}); return true; // 让所有属性检测都通过 } } window new Proxy(fakeWindow, handler);特别注意要补全这些关键点BOM检测window.outerWidth、window.nameDOM检测document.documentElement.clientWidthCanvas指纹HTMLCanvasElement.prototype.toDataURLWebGL渲染WebGLRenderingContext.prototype.getParameter4. 纯算法还原的详细步骤当补环境遇到困难时纯算法还原就是终极解决方案。通过分析Bt()函数的执行流程发现最终结果是由多个部分拼接而成随机因子8位随机字符串F用做盐值请求体签名对POST数据计算MD5再拼接F后二次加密环境指纹包括[ 536870911, // 特征码 , // 插件信息 Date.now(), // 时间戳 -new Date().getTimezoneOffset()/60, // 时区 1, // 全局变量检测 09031147110931135002 // 客户端ID ]关键的加密转换发生在z_charCodeAt()函数中function z_charCodeAt(arr) { const result []; for (let str of arr) { for (let i0; istr.length; i) { result.push(str.charCodeAt(i) 255); } result.push(0); // 每个字符串后加0分隔 } return result; }最终通过Base64编码的salt字符串进行位移运算let output 102!; for(let i0; ibytes.length; ) { const h bytes[i]; const k bytes[i] || NaN; const b bytes[i] || NaN; const p h 2; let T, F, K; const V h 3; // 三字节转四字符的逻辑 if(!isNaN(k)) { T (V 4) | (k 4); if(!isNaN(b)) { F ((k 15) 2) | (b 6); K b 63; } else { F (k 15) 2; K 64; } } else { T V 4; F K 64; } output salt[p] salt[T] salt[F] salt[K]; }5. 常见检测点与绕过方案在实际操作中会遇到各种环境检测这里列举几个典型的PhantomJS检测// 检测window.callPhantom callPhantom in window // 需要返回falseChrome扩展检测// 检测chrome对象 typeof chrome ! undefined // 需要根据实际情况处理字体指纹检测// 检测可用字体 document.fonts.check(12px Noto Sans)WebGL渲染检测// 通过canvas获取WebGL指纹 const canvas document.createElement(canvas); const gl canvas.getContext(webgl); gl.getParameter(gl.VENDOR);对于这些检测我的经验是使用Proxy代理关键对象对不确定的属性返回合理默认值保持属性访问的顺序和次数与真实浏览器一致6. 实战中的调试技巧在逆向过程中有几个调试技巧特别实用断点策略在Object.defineProperty上设断点捕获属性定义对Function.prototype.toString设断点发现隐藏函数日志记录// 记录所有属性访问 const log []; const handler { get(target, prop) { log.push(GET ${prop}); return Reflect.get(target, prop); } } window new Proxy(window, handler);差异对比在真实浏览器和控制台分别运行代码对比关键变量的值差异性能优化// 加速补环境执行 delete window.performance; window.performance { now: () Date.now() };7. 完整实现方案结合纯算和补环境的优势最终的解决方案可以这样设计环境准备阶段// 固定环境参数 const env { timezone: -8, plugins: [], webglVendor: Google Inc. (Intel) };核心算法实现function generatePayload(queryParams) { // 1. 生成随机因子 const F randomString(8); // 2. 计算请求体签名 const bodyHash md5(JSON.stringify(queryParams)); const K customMd5(${bodyHash}-${F}, 31); // 3. 构建环境数组 const z [ 536870911, , Date.now().toString(), env.timezone.toString(), 1, 09031147110931135002 ]; // 4. 字符编码转换 const bytes z_charCodeAt(z); // 5. Base64编码转换 return encodeWithSalt(bytes); }请求发送示例const payload generatePayload({ depdate: 2025-12-26, cabin: Y_S_C_F }); fetch(/online/list, { headers: { w-payload-source: payload } });这种混合方案既避免了纯补环境的性能开销又比纯算法还原更易维护。在实际项目中建议先尝试补环境方案遇到难以模拟的检测点再针对性进行算法还原。