【JS逆向】某审批局加解密逆向分析

查看 84|回复 11
作者:littlewhite11   
文章主要分享的内容是本人在逆向过程中遇到标准加解密算法时使用各种库来模拟还原的一些经验,因本人并不擅长密码学,请路过的各位大佬多多指教。
逆向目标
  • 网址:aHR0cHM6Ly9jcmVkaXQuaGQuZ292LmNuL3h5eHhncy8=
  • 目标:接口参数加密,响应数据解密

    抓包分析
    进入网站后,点击加载更多,会发送一个xzxkfr数据包。
    其中请求参数中的queryContent和sign需要逆向分析,nonceStr只是一个随机字符串。


    1.png (38.67 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传

    响应头为application/octet-stream,因此响应数据的解密也需要逆向分析。


    2.png (37.56 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传



    3.png (32.9 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传

    逆向分析
    话不多说,直接开始逆向。
    加密
    刷新网站,尝试搜索关键词queryContent,在某个JS文件中搜索到。


    4.png (75.23 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传

    跳转到文件对应的地方下断,点击加载更多,成功断住,此时,参数queryContent已经生成,为i。


    5.png (28.84 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传

    我们往前看i如何生成。可以知道i && null != r.headers && "1" === r.headers["C-GATEWAY-QUERY-ENCRYPT"] && n.encryptType === P.ENCRYPT_TYPE.SM4这个条件是成立的,因此会走i = ls.sm4.encrypt(i, e.encryptKey)的逻辑。其实也可以在i = ls.sm4.encrypt(i, e.encryptKey)和i = us(i, e.encryptKey)处分别下断后重新请求,然后单步跟,看走哪个函数。
    可以看到传入了请求参数的字符串以及SM4加密的key,然后生成了queryContent。


    6.png (10.67 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传

    我们直接跟进去encrypt函数,其中t是明文,e是key。


    7.png (10 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传

    我们可以通过代码的一些特征去网上搜索或者问AI,看有什么库能够实现这种方式的加密(这里是SM4)。
    我就直接贴代码了(因为我已经搜索过了)。
    const sm4 = require('sm-crypto').sm4
    const msg = '' // 可以为 utf8 串或字节数组
    const key = '' // 可以为 16 进制串或字节数组,要求为 128 比特
    let encryptData = sm4.encrypt(msg, key) // 加密,默认输出 16 进制字符串,默认使用 pkcs#7 填充(传 pkcs#5 也会走 pkcs#7 填充)
    let encryptData = sm4.encrypt(msg, key, {padding: 'none'}) // 加密,不使用 padding
    let encryptData = sm4.encrypt(msg, key, {padding: 'none', output: 'array'}) // 加密,不使用 padding,输出为字节数组
    let encryptData = sm4.encrypt(msg, key, {mode: 'cbc', iv: 'fedcba98765432100123456789abcdef'}) // 加密,cbc 模式
    那我们直接尝试通过nodejs模拟一下。
    const sm4 = require('sm-crypto').sm4
    const msg = ''  // 明文
    const key = ''  // key
    console.log(sm4.encrypt(msg, key, {output: 'string'}))
    对比一下网页上和本地的执行结果,发现是一致的,那queryContent加密的模拟就完成了。


    8.png (13.06 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传



    9.png (18.04 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传


    有的时候并不是那么顺利,可能还需要尝试padding、mode之类的,如果是魔改的,那就另当别论了。

    我们继续跟sign的加密逻辑,n.sign = o,且signType为SM2,那应该是需要跟o = Zi(o = ls.sm2.signature(a, e.appSignPrivateKey, e.appSignPublicKey, e.appId))的逻辑。
    其中a是包括queryContent在内的一系列参数组成的字符串,代码可以直接扣。


    10.png (16.97 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:41 上传

    我们继续跟signature这个函数,可以发现应该是个签名算法。


    11.png (9.6 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:42 上传

    还是老规矩,网上搜
    const sm2 = require('sm-crypto').sm2
    const msgString = ''
    const privateKey = ''
    const userId = ''
    let sigValueHex6 = sm2.doSignature(msgString, privateKey, {
        hash: true,
        publicKey,
        userId,  // 默认 userId 为 1234567812345678
    })
    SM2是一种基于椭圆曲线公钥密码算法的标准,由中国国家密码管理局发布,每次加密时会生成一个新的随机数,且参与密文的生成过程。因此,我们无法确定本地模拟的参数是否正确,那我们可以通过请求接口,如果返回数据了,就表示参数没问题。
    SM2的结果有了,那我们还需要跟一下Zi这个函数,才能拿到最终的sign值从而模拟请求接口。
    Zi可以直接扣代码就行,需要注意的是,扣代码过程中遇到的Xi和Yi函数内的BigInteger可以通过导库实现,具体还是网上搜索,直接贴代码了。
    const BigInteger = require('jsbn').BigInteger;
    function Xi(t) {
        return new BigInteger(t,10).toString(16)
    }
    function Yi(t) {
        return new BigInteger(t, 16).toString(10)
    }
    参数模拟完成,我们直接请求接口看能不能返回数据。
    可以看到,成功返回数据,说明参数模拟没有问题。


    12.png (117.01 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:42 上传

    解密
    下面我们再看响应数据的解密。
    既然接口是xhr请求,那我们就尝试搜索响应拦截器interceptors.response.use,可以在某个JS文件中搜索到。


    13.png (71.27 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:42 上传

    我们直接跳去源文件,看起来像是个异步,具体就不跟了,解密的逻辑就在下图所指的oo函数,其中r就是响应,e对象包含了解密所需key等信息。


    14.png (31.01 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:42 上传

    我们直接跟进去oo函数,把一些条件判断去掉后的逻辑代码如下,之后缺什么扣什么就行,t.data是响应的arraybuffer,解密还是用的SM4。
    // 处理响应数据
    var r = t.data
      , n = new DataView(r)
      , i = new Uint8Array(r)
      , s = {}
      , a = 40;
    D.forEach((function(t, e) {
        var r = n.getInt32(4 * e);
        s[t] = i.subarray(a, a + r),
        a += r
    }
    ));
    // 这里在验证响应数据的签名是否正确
    var o = ao(s, e)  
      , u = o[0]
      , h = o[1];
    if (!u)
        return Promise.reject(new Error("验签失败"));
    // 数据解密(SM4)
    var c = "{}";
    c = kr.sm4.decrypt(function(t) {
        for (var e = "", r = 0; r
    我们扣代码的时候,并不知道var o = ao(s, e)这里是在验证响应数据的签名,所以会跟着扣。
    其中遇到一些需要注意的点是:
    Qi函数可以导库实现
    // 网页上
    function Qi(t) {
        var e = Ai.lib.WordArray.create(t);
        return Ai.MD5(e).toString(Ai.enc.Hex)
    }
    // 本地模拟
    const CryptoJS = require('crypto-js')
    function Qi(t) {
        var e = CryptoJS.lib.WordArray.create(t);
        return CryptoJS.MD5(e).toString(CryptoJS.enc.Hex)
    }
    ji函数在nodejs可以直接模拟
    // 网页上
    var Pi = "function" == typeof Buffer
    var ji = Pi ? function(t) {
        return Buffer.from(t).toString("base64")
    }
    : function(t) {
        for (var e = [], r = 0, n = t.length; r
    验证签名时的SM2算法
    // 网页上
    Vr.doVerifySignature(a, Zi(i), e.platformPublicKey, {
        hash: !0,
        userId: e.appId
    })
    // 本地模拟
    const sm2 = require('sm-crypto').sm2
    sm2.doVerifySignature(a, Zi(i), e.platformPublicKey, {
        hash: !0,
        userId: e.appId
    })
    然后就是最终的SM4解密了,既然前面的加密能够在网上搜索到,那解密也不在话下了。
    // 网页上
    c = kr.sm4.decrypt(function(t) {
        if (!(t instanceof Uint8Array))
            throw new Error("Invalid Uint8Array");
        for (var e = "", r = 0; r
    最后,我们直接模拟请求接口,通过导库实现各种加解密算法,进行最终数据的获取。

    我们先对响应进行base64,然后在JS中将base64字符串转为arraybuffer

    // base64 字符串转 arraybuffer
    function base64ToArrayBuffer(base64) {
        var binary_string = atob(base64);
        var len = binary_string.length;
        var bytes = new Uint8Array(len);
        for (var i = 0; i
    模拟请求:


    15.png (190.47 KB, 下载次数: 0)
    下载附件
    2025-3-15 18:42 上传

    成功!!!

    下载次数, 下载附件

  • littlewhite11
    OP
      


    weilairuhe 发表于 2025-3-25 21:38
    接单吗,给我个联系方式

    网站好像不让发联系方式
    littlewhite11
    OP
      


    TankGuo 发表于 2025-3-23 23:58
    大佬,我想请教一下,我在python里base64后的结果和浏览器里看到的差太多了是怎么回事,

    差太多是什么意思
    beautiful008   

    学习一下,感谢大佬分享
    sanfengzhang   

    看得云里雾里。膜拜大神之中。
    littlewhite11
    OP
      


    sanfengzhang 发表于 2025-3-15 19:26
    看得云里雾里。膜拜大神之中。

    多搞就不云里雾里了,我也是个小菜鸡
    KaliHt   

    大佬有比较的完美方法去扣加密代码吗
    mzhsohu   

    不明觉厉~!菜鸡路过~!!!
    希望看到完整的视频教程~!!!
    littlewhite11
    OP
      


    KaliHt 发表于 2025-3-15 21:19
    大佬有比较的完美方法去扣加密代码吗

    没有,起码我没有
    kowade003   

    表示看不明白,部分代码能不能用Ai去编写实现呢?
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部