「JS压缩加密逆向」实战案例 – 彩票自动投注中的GZIP压缩与Base64编码分析

「JS压缩加密逆向」实战案例 – 彩票自动投注中的GZIP压缩与Base64编码分析

在开发彩票自动投注脚本中的一个关键技术发现。初始抓包数据显示请求主体包含大段不明字符串,初步判断可能采用某种加密算法。然而,经过系统的逆向工程分析,我们确认该数据并未使用任何加密技术,其本质是通过数据压缩配合Base64编码实现的标准化处理。

本文将通过一个彩票脚本的案例,完整展示一次JS压缩加密逆向的典型流程,其核心关键在于精准判断数据处理的本质是‘加密’还是‘编码’。

本文目录

  1. 数据抓包:发现神秘字符串
  2. XHR断点:定位加密入口
  3. 函数追踪:分析k函数逻辑
  4. 核心分析:揭开压缩编码面纱
  5. 关键发现:定位gzip压缩函数
  6. 代码扣取:获取压缩模块
  7. 本地实现:重构加密函数
  8. 测试验证:对比加密结果
  9. 常见问题与解决方案
  10. 获取完整课件
1. 数据抓包:发现神秘字符串

首先打开目标彩票网页,使用F12打开浏览器抓包工具,切换到网络面板。点击投注按钮后,通过网页抓包观察请求结果。

新手提示:如果您对如何使用浏览器开发者工具进行抓包还不太熟悉,可以参考我们之前整理的 数据抓包实战指南,里面详细介绍了基本的抓包方法和技巧。

在请求列表中,我们发现了一个关键接口:

目标接口:/tools/_ajax/OG15FT/betSingle
请求方法:POST

仔细观察请求主体,发现了一长串神秘的字符串:

H4sIAAAAAAAAA02PQWvDMAyF/4vOYUi2lTg5rrBRBtthZZemBzcRWyBORuMcQtv/PsdkUKHDQ+8TvHcF1zTjPIR9C5XRBTMZxRk0fSdDOHReoKIiV6Vmzk2pbAbfzstKw8cr8csBMuimaY4cKFRMRCpnvV6DeKiOcK3BS/gZ266toarh+XOHaBApbg1ZDcPsp+hQlBc5uyAJwyfEZIcY4t/34yBL1CrpVrZzCvA+pr+HEOm9idiUnNs2aG7J+e3dsl8zHU93OGUgQ3NZ3mSJTR4rl9qi0siaDeVsFWzkl+tTayzZliQOrSY6tyTGFOZcFFaLcC4a7n9RrEEnZQEAAA==

这串字符串没有参数名,就是单纯的一大段编码内容,看起来像是某种加密结果。

图1: 彩票投注接口抓包发现的加密字符串
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)
    }
}

分析这个函数,我们发现了完整的处理流程:

  1. Uint8Array编码:使用TextEncoder将字符串转为字节数组
  2. 数据压缩:通过a.i(i.a)(e)进行压缩处理
  3. 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编码,并没有使用真正的加密算法

JS压缩加密逆向之gzip压缩流程分析
图2: gzip压缩与Base64编码流程分析
6. 代码扣取:获取压缩模块

现在我们需要获取gzip压缩的完整代码。由于这个文件有12万行,而且是webpack模块化加载的,我们采用巧妙的方法来扣取:

  1. 在Sources面板找到gzip所在的JS文件
  2. 全部拷贝到Notepad++中
  3. 点击”视图” → “折叠所有层次”
  4. 搜索”gzip”找到对应模块
  5. 将整个模块代码拷贝出来
展开的模块拷贝代码
箭头指错了


关键步骤:清理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));

如果一切正确,输出的结果应该与抓包得到的字符串完全一致。

图3: 本地测试生成的加密字符串
9. 常见问题与解决方案
  • Q1: 控制台报错 TextEncoder is not defined
    • 解决方案:在Node.js环境中,需要安装util模块:const { TextEncoder, TextDecoder } = require('util');
  • Q2: gzip压缩结果与浏览器不一致
    • 解决方案:检查压缩级别参数,确保与浏览器使用的参数一致,通常需要设置{"gzip": true}
  • Q3: Base64编码格式不正确
    • 解决方案:确保使用标准的btoa函数,注意字符编码要统一为UTF-8
  • Q4: 扣取的压缩模块代码太大
    • 解决方案:可以使用在线的gzip压缩库替代,如pako库,使用方法类似
10. 获取完整课件

关注公众号孤狼网络科技,回复 gzip,获取我们为您精心准备的两种解决方案:其一是精炼后的1万行核心压缩模块源码,方便您研究学习;其二是已经扣取封装好的可执行代码,开箱即用,助您快速复现加密流程。

技术总结
“通过本次完整的 JS压缩加密逆向 过程我们可以发现,面对前端复杂的请求数据时,首要任务是准确判断其处理逻辑是加密还是编码。本例成功揭示了如何从庞杂的代码中定位并复现‘数据压缩+Base64编码’这一完整链条。”

  • 减少数据传输量:压缩后显著减小请求包大小
  • 隐藏明文数据:虽然不是加密,但能防止直接查看请求内容
  • 提高传输效率:在网络传输中压缩数据节省带宽

掌握这种先分析后判断的思路,是解锁各类Web接口的关键。当您再遇到“看似加密”的字符串时,不妨先从基础的编码与压缩角度入手,这往往会事半功倍。如果您想系统学习更多此类实战技巧,JS逆向实战案例 中心页收录了丰富的案例,将是您的绝佳选择。

本文由林石工作室提供技术支持,转载请注明出处。

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注