在开发彩票自动投注脚本中的一个关键技术发现。初始抓包数据显示请求主体包含大段不明字符串,初步判断可能采用某种加密算法。然而,经过系统的逆向工程分析,我们确认该数据并未使用任何加密技术,其本质是通过数据压缩配合Base64编码实现的标准化处理。
本文将通过一个彩票脚本的案例,完整展示一次JS压缩加密逆向的典型流程,其核心关键在于精准判断数据处理的本质是‘加密’还是‘编码’。
本文目录
- 数据抓包:发现神秘字符串
- XHR断点:定位加密入口
- 函数追踪:分析k函数逻辑
- 核心分析:揭开压缩编码面纱
- 关键发现:定位gzip压缩函数
- 代码扣取:获取压缩模块
- 本地实现:重构加密函数
- 测试验证:对比加密结果
- 常见问题与解决方案
- 获取完整课件
1. 数据抓包:发现神秘字符串
首先打开目标彩票网页,使用F12打开浏览器抓包工具,切换到网络面板。点击投注按钮后,通过网页抓包观察请求结果。
新手提示:如果您对如何使用浏览器开发者工具进行抓包还不太熟悉,可以参考我们之前整理的 数据抓包实战指南,里面详细介绍了基本的抓包方法和技巧。
在请求列表中,我们发现了一个关键接口:
目标接口:/tools/_ajax/OG15FT/betSingle
请求方法:POST
仔细观察请求主体,发现了一长串神秘的字符串:
H4sIAAAAAAAAA02PQWvDMAyF/4vOYUi2lTg5rrBRBtthZZemBzcRWyBORuMcQtv/PsdkUKHDQ+8TvHcF1zTjPIR9C5XRBTMZxRk0fSdDOHReoKIiV6Vmzk2pbAbfzstKw8cr8csBMuimaY4cKFRMRCpnvV6DeKiOcK3BS/gZ266toarh+XOHaBApbg1ZDcPsp+hQlBc5uyAJwyfEZIcY4t/34yBL1CrpVrZzCvA+pr+HEOm9idiUnNs2aG7J+e3dsl8zHU93OGUgQ3NZ3mSJTR4rl9qi0siaDeVsFWzkl+tTayzZliQOrSY6tyTGFOZcFFaLcC4a7n9RrEEnZQEAAA==
这串字符串没有参数名,就是单纯的一大段编码内容,看起来像是某种加密结果。

2. XHR断点:定位加密入口
要追踪这个无特征字符串的来历,我们需要借助XHR断点。打开“来源”面板,在“XHR断点”处点击+号,设置URL包含betSingle的断点规则。
点击投注按钮触发断点后,在调用堆栈中分析代码,发现关键线索:
var B = A ? t.Requirement : k(t, I);fetch(c, {
credentials: "same-origin",
method: "POST",
cache: "no-store",
headers: s()({}, A ? {} : {
"Content-Type": "application/json"
}, P && P.nFrontKey, {
"User-UUID": window.__uuid
}),
body: B // 请求主体由B参数赋值})
从代码中可以看到,请求主体body的值来自变量B,而B又是由k(t, I)函数生成的。这就是我们的突破口!
3. 函数追踪:分析k函数逻辑
接下来我们在k函数处设置断点,跟进分析其内部逻辑:
function k(t, e) {
return !0 === t.noAes || (i = t.Action) && a.i(w.c)(i) && S.some(function(t) {
return t.test(i)
}) || y.a.state.__no_aes ? u()(e) : function(t) {
var e = t;
return void 0 !== t && null !== t && (e =a.i(b.b)(u()(t))),
e
}(e);
var i
}
分析这段代码,我们发现最重要的部分是:
e = a.i(b.b)(u()(t))
通过控制台打印分析,我们得出:
u()(t)实际上就是JSON.stringify(t),将对象转为JSON字符串a.i(b.b)返回的是b.b函数本身- 所以整个表达式等价于:
b.b(JSON.stringify(t))
原始的参数t是一个投注信息对象:
{
"accountId": 437551425,
"clientTime": 1762941491259,
"gameId": "OG15FT",
"issue": "20251112719",
"item": [
"{\"methodid\":\"BSC004001001\",\"nums\":1,\"rebate\":\"0.00\",\"times\":1,\"money\":2,\"mode\":1,\"issueNo\":\"20251112719\",\"codes\":\"||||||||04|\",\"playId\":[]}"
],
"encryKey": "176294149126085458567108297",
"encryValue": "0ec063797926b4050330e283a53ab041"
}
4. 核心分析:揭开压缩编码面纱
现在进入最关键的b.b函数:
e.b = function(t) {
try {
var e = (new TextEncoder).encode(t) // 第一步:Uint8Array编码
, s = a.i(i.a)(e); // 第二步:压缩处理
return function(t) { // 第三步:Base64编码
for (var e = "", a = 0; a < t.length; a++)
e += String.fromCharCode(t[a]);
return btoa(e)
}(s)
} catch (t) {
throw new Error("Compression failed: " + t.message + ", ",t)
}
}
分析这个函数,我们发现了完整的处理流程:
- Uint8Array编码:使用
TextEncoder将字符串转为字节数组 - 数据压缩:通过
a.i(i.a)(e)进行压缩处理 - Base64编码:将压缩后的字节数组转为Base64字符串
前两步和后一步都有原生JavaScript函数支持,关键在于第二步的压缩处理。
5. 关键发现:定位gzip压缩函数
跟进i.a函数,我们发现它指向的是gzip函数:
gzip: function(t, e) {
return (e = e || {}).gzip = !0,
ee(t, e)
},
gzip函数又调用了ee函数:
function ee(t, e) {
const a = new te(e); // 创建压缩器实例 if (a.push(t, !0), // 压入数据
a.err) // 错误处理
throw a.msg || G[a.err];
return a.result // 返回压缩结果}
到这里我们终于明白了:整个”加密”过程实际上是gzip压缩 + Base64编码,并没有使用真正的加密算法!

6. 代码扣取:获取压缩模块
现在我们需要获取gzip压缩的完整代码。由于这个文件有12万行,而且是webpack模块化加载的,我们采用巧妙的方法来扣取:
- 在Sources面板找到gzip所在的JS文件
- 全部拷贝到Notepad++中
- 点击”视图” → “折叠所有层次”
- 搜索”gzip”找到对应模块
- 将整个模块代码拷贝出来

关键步骤:清理Webpack模块包装
拷贝下来的代码是被Webpack包裹的,我们需要手动清理。请按照以下说明操作:
// ==== 删除从这里开始的内容 ====
function(t, e, a) {
"use strict";
a.d(e, "a", function() {
return ta
}),
a.d(e, "b", function() {
return ea
});
// ==== 删除到上面结束 ====
function i(t) {
let e = t.length;
for (; --e >= 0; )
t[e] = 0
}
var te = function(t) {};
function ee(t, e) {}
function xxxxx(t, e) {}
xxxxxs省略一千行
var ta = Qe
, ea = Ze
// ==== 删除结尾的包装括号 ====
} // 删除这个花括号
7. 本地实现:重构加密函数
现在我们可以重构本地的加密函数:
function gzip(t) {
try {
const encoder = new TextEncoder();
var e = encoder.encode(JSON.stringify(t)) // JSON转Uint8Array
, s = ee(e, {"gzip": true}); // gzip压缩
return function(t) { // Base64编码
for (var e = "", a = 0; a < t.length; a++)
e += String.fromCharCode(t[a]);
return btoa(e)
}(s)
} catch (t) {
throw new Error("Compression failed: " + t.message + ", ", t)
}
}
8. 测试验证:对比加密结果
创建测试代码验证我们的实现:
const t = {
"accountId": 437551425,
"clientTime": 1762941491259,
"gameId": "OG15FT",
"issue": "20251112719",
"item": [
"{\"methodid\":\"BSC004001001\",\"nums\":1,\"rebate\":\"0.00\",\"times\":1,\"money\":2,\"mode\":1,\"issueNo\":\"20251112719\",\"codes\":\"||||||||04|\",\"playId\":[]}"
],
"encryKey": "176294149126085458567108297",
"encryValue": "0ec063797926b4050330e283a53ab041"
};
console.log("生成的加密字符串:", gzip(t));
如果一切正确,输出的结果应该与抓包得到的字符串完全一致。

9. 常见问题与解决方案
- Q1: 控制台报错
TextEncoder is not defined- 解决方案:在Node.js环境中,需要安装
util模块:const { TextEncoder, TextDecoder } = require('util');
- 解决方案:在Node.js环境中,需要安装
- Q2: gzip压缩结果与浏览器不一致
- 解决方案:检查压缩级别参数,确保与浏览器使用的参数一致,通常需要设置
{"gzip": true}
- 解决方案:检查压缩级别参数,确保与浏览器使用的参数一致,通常需要设置
- Q3: Base64编码格式不正确
- 解决方案:确保使用标准的
btoa函数,注意字符编码要统一为UTF-8
- 解决方案:确保使用标准的
- Q4: 扣取的压缩模块代码太大
- 解决方案:可以使用在线的gzip压缩库替代,如
pako库,使用方法类似
- 解决方案:可以使用在线的gzip压缩库替代,如
10. 获取完整课件
关注公众号孤狼网络科技,回复 gzip,获取我们为您精心准备的两种解决方案:其一是精炼后的1万行核心压缩模块源码,方便您研究学习;其二是已经扣取封装好的可执行代码,开箱即用,助您快速复现加密流程。
技术总结
“通过本次完整的 JS压缩加密逆向 过程我们可以发现,面对前端复杂的请求数据时,首要任务是准确判断其处理逻辑是加密还是编码。本例成功揭示了如何从庞杂的代码中定位并复现‘数据压缩+Base64编码’这一完整链条。”
- 减少数据传输量:压缩后显著减小请求包大小
- 隐藏明文数据:虽然不是加密,但能防止直接查看请求内容
- 提高传输效率:在网络传输中压缩数据节省带宽
掌握这种先分析后判断的思路,是解锁各类Web接口的关键。当您再遇到“看似加密”的字符串时,不妨先从基础的编码与压缩角度入手,这往往会事半功倍。如果您想系统学习更多此类实战技巧,《JS逆向实战案例》 中心页收录了丰富的案例,将是您的绝佳选择。
本文由林石工作室提供技术支持,转载请注明出处。
