Skip to content

webpack

理解:
webpack就是将很多个函数模块,全部统一用一个函数进行维护。
代码例子:(具体不是这个运行逻辑)
会有一个变量用来维护函数执行的结果。通过全局变量的方式导出结果。
function main(num) {
	a = {
		0: "func1",
		1: "func2",
		2: "func3"
	}
	b = {
		0: "32dx8s",
		1: "3xfg63",
		2: "df65g3"
	}
	c = [function () {},
		function () {}
		function () {}]
}
通常会有一个加载器将所有模块动态添加到一个数组或字典进行维护
调用的时候就调用那个用来缓存所有模块的变量
通过下表的形式进行调用。一般会先判断是否存在这个模块,没有则添加,有则直接用


判断依据:
1.搜索关键字webpack
2.看函数的调用方式 return e[t].call()

扣代码过程:
拿到webpack的核心整个代码
先把加载器拿过来
注释他自己的入口(入口会将所有模块进行添加,禁止执行添加所有模块。因为传入的参数是空,所以会需要添加模块,我们直接将所需要执行的模块写死传给他就行了)
全局导出加载器(因为webpack下的函数被处理过,不是原函数,会有其他的辅助变量,不好将函数单独提取出来,通过导出的方式保留处理)
查看缺少的模块
在网页中获取到对应的模块函数放到传参列表里面(通常会有一个加载器的属性用来储存所有的模块。通过加载器的这个属性log出对应方法)
再执行代码
...

最核心的内容是加载器。只需要加载器就好了

webpack项目需要思考代码逻辑
扣代码考虑两种情况:
    扣函数方法
    扣对象:
        找附近的对象复制语句,然后断点检查是否是同一对象。(这个对象可能是全局对象,需要刷新整个页面加载)

形如:s = n("e9bd") 这样的形式,一般都是webpack调用

扣webpack代码的时候,有时候外面会嵌套多层自定义函数,我们可以通过我们需要的模块,然后关键字搜索,快速定位webpack的代码,
找到对应的外层函数,然后扣webpack的代码,其他的不用管,扣完之后,函数function前面加一个感叹号,不然会报错,找ai问原因,感叹号的作用

全局变量导出加载器。(加载器的名字可能不一致,传参的时候把加载器名字改了,实际是用一个)

function i(t) {
        if (r[t])  # 特征:如果模块存在则直接调用;否则初始化添加
            return r[t].exports;
        var n = r[t] = {
            i: t,
            l: !1,
            exports: {}
        };
        return e[t].call(n.exports, n, n.exports, i),
            n.l = !0,
            n.exports
    }

    bc = i;


报错是缺少模块的意思:(无法调用这个模块)
        return e[t].call(n.exports, n, n.exports, i),
                    ^

TypeError: Cannot read properties of undefined (reading 'call')
在执行之前打印,输出需要调用的模块是哪一个模块。最后输出的模块就是不存在的模块,需要扣的模块

<img src="https://gitee.com/kualk/pic-go/raw/master/imgs/image-20250914205505546.png" alt="image-20250914205505546" style="zoom:50%;" />

加载器里面一般有一个缓存变量,缓存用过的,或者需要用到的模块
if (r[t]) {...} 这个r就是一个缓存模块的数组。存有所有的模块,可以通过“收集模块”的方法,快速收集需要的所有模块,防止需要扣N个模块而累死。
快速收集模块:
断点之后,清空缓存变量,令为空;
给缓存变量添加一个条件断点:收集它的模块ID,以及函数体。“注入”新的变量收集(新的变量是一个全局变量,在控制台里面定义,可以随时获取值,区别于局部变量)

再在外部下一行代码下一个断点,这样调用模块的时候,缓存里面没有就会自动添加到缓存里面,执行完之后,直接提取缓存变量就是功能需要的所有模块了。

第一个模块需要手动获取

<img src="C:/Users/Senior/AppData/Roaming/Typora/typora-user-images/image-20250914224913231.png" alt="image-20250914224913231" style="zoom:67%;" />

JS代码学习

通过Object(func)(args)调用函数方法

obj.push.bind()

js
aa = [11, 22, 33]
l = aa.push.bind(aa)
  • 当下一次aa更新的时候,l也会随之更新

jsencrypt(库)

  • 没有window对象,则进入到库里面,令window=global

函数接参

  • function () {}未直接定义也可以传参
  • 传参用arguments[index]接收每个参数

consolo.log()

  • 当列表数据很多时,控制台不会输出,会显示为一个Array对象。需要手动遍历打印

copy()

  • 在浏览器执行copy函数,可以将变量的值复制到粘贴板上

  • undefined的数据不会被copy到!!!

obj.shift()

  • 约等于pop的作用,从列表中取出一个值,列表元素减少
  • image-20250821053014187
js
obj.shift()

返回值

  • 返回值用逗号隔开时,表示还没有结束。会先执行逗号的表达式(可能对数据进行加密处理),返回最后一个末值
  • image-20250821053208352
js
return a,b,c,d

调用函数的另类形式

  • 用括号括起来的,前面一个括号是返回的方法对象,只会返回最后一个值,前面的不返回。
  • 后面一个括号是参数列表。
  • image-20250821053258498
js
(0, obj['func_name'])(arg1, arg2)

encodeURIComponent()

  • image-20250821053351761

NodeJS中的window对象

  • 两者相似但不全是

  • js
    w = globalThis

btoa() atob()

  • 使用base64进行转码
js
const encodedData = btoa("Hello, world"); // 编码字符串
console.log(encodedData);
const decodedData = atob(encodedData); // 解码字符串
console.log(decodedData);

+new Date()

  • 会将日期对象转换为对应的时间戳

JSON.stringify()

  • python的json库执行时结果跟js直接后不同。有区别,python的json库可以进行处理
python
data = json.dumps(data, separators=(',', ':'))

加密定位

关键词搜索

  • AES加密:

    • AES、mode、padding
  • DES加密:

    • DES、mode、padding
  • encrypt

  • decrypt

异步加密(发送请求的拦截器)

  • 得手动进入异步调用代码块里面添加断点,否则不会自动进入
  • image-20250821051955800

分支语句

  • if else

  • try catch

  • 当不知道具体走的哪个分支时,打断点进行测试

  • 有可能每个分支都有加密方式,不能一次就全删了。成功有成功的加密方式,失败有失败的加密方式

  • image-20250821054313872

this对象

  • 很多this的情况下,一般考虑扣上级类对象

  • 搜索关键词:

    • js
      this.aes =   (一个类)
      obj = {
      	aes: ""
      }
      aes =
  • 缩小this的范围

经验

一个函数多次调用,搞清楚到底是哪次调用是加密调用(观察数据)

  • image-20250828162808943

请求头加密方式

  • 多个函数调用for循环,通过dict对象设置,防止暴露headers的关键字。需打断点查看到底是哪个函数调用的此for循环进行的设置headers。再追堆栈
  • image-20250828154743952
  • image-20250828155328316
  • image-20250828155455628

跟栈的时候有时候会跳过一些代码,得注意

调用外层参数,并且外层参数第一次并没有加密,中途多次调用的时候才加密的

  • 面对多次调用的时候,那么可以添加条件断点进行判断,自行跳过。数据长度进行debug

解密数据

  • 要么是对称,要么是非对称
  • 一定会有关键字:decrypt
  • 对json.parse进行hook,注意刷新页面,hook的是对应解密之后的数据,多次调用

重放xhr

  • 查看请求是否为一次性请求
  • 多次请求可以测试哪些数据是必要的

扣代码的坑

  • 有时候有些参数是定值,但是多次出现进行了改值,不能直接把最终的值拿来中,中途有变化过程,每个阶段的该变量值不同,导致函数执行结果有差异,函数没错,参数错了

扣代码顺序

  • 函数跟参数都是未知的时候,先扣参数,再扣函数。函数可以先随便写一个防止报错undefined

this定位

  • 关键字搜索
    • this.obj =
    • obj:
    • obj =

判断函数方法类型(是否自定义)

  • 将鼠标悬停在函数方法上,有FunctionLocation的就是自定义的方法,没有的则是自带的系统函数
    • image-20250821115722638
    • image-20250821120326437

命名空间重叠

  • 有时候单个字母的函数名、变量名极易重复,需要手动区分

扣代码先扣整体

  • image-20250821121909252

  • 这整个一坨直接是个定值就直接赋值

  • 看的时候将鼠标放在函数名上!

变量作用域

  • 当一个局部函数体内出现了没有定义的变量时,找大范围的”全局变量“,一般都在同一个大括号括起来。
  • 这个”未定义的变量“可能不是动态加载的,页面一加载就会有,注意断点刷新页面还是刷新动态数据
  • !!!扣代码一定要打断点看作用域

对数据怀疑是否为动态参数

  • 拿到一些数据,要判断是否为动态的,可以刷新几次试试,或者更换参数
  • 在控制台多次执行这个函数,是否为动态值

核对数据正确性

  • 固定一些频繁变化的值,进行核对。(时间戳)

找函数方法

  • 一定要在函数体内打断点,才一定是执行到的函数方法,要不然可能作用域不同,函数还未定义或者被更改,同名但不是同函数

确定简单的加密算法

  • 快速确定简单算法(没有对算法进行魔改)
    • 直接找到加密的位置函数,对指定内容进行加密再进行比对,如果对的上就直接用
    • 查看加密之后的数据的位数:32位、40位...(加密算法特征匹配)
    • 密算法加时,必须保证每个字符都一样,有时候代码中会有换行符,手动复制值无法复制换行符导致加密结果不同。还是加密指定值最稳定

浏览器大小写

  • 浏览器会自动将headers里面的键的首字母大写,实际上还是小写。搜索(hook)关键字时是小写的

window对象

  • window.pid关于window的一般是全局对象,直接搜索赋值。源代码,...

加了断点,刷新数据时没有进入断点

  • 可能是页面加载的时候进行的赋值!

响应的数据经过了加密

  • 必定会有解密的函数进行数据解析
  • 定位解析函数
    • 搜索关键字:decrypt、padding、iv...
    • hook法:JSON.parse()
    • xhr法:查找ajax的成功回调函数

扣类

  • 当代码有混淆的时候,扣一个加密或解密函数比较麻烦,直接扣一个完整的类可能更迅速,复杂度可能更低
  • 这个类可以是大类,也可以是大类的小类(局部类)(上一级类)

复制值的细节

  • 有些加密过后的数据(响应的数据加密)长度非常长,直接从浏览器手动复制的话,可能会有省略号,会把省略号复制下来,数据不全,适用copy()函数的话就不会有这种问题了。

execjs的编码报错

  • 没有指定默认编码,会适用gbk进行编码

  • 解决方式(execjs会调用subprocess库)

    • python
      # 手动设置编码格式(一次性)
      import subprocess
      from functools import partial
      subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
    • 进入subprocess库的源代码,搜索"encoding=None",手动指定编码为"utf-8"。永久性

Demos

宝钢股份

  • 请求头加密
    • content-md5
    • x-date
    • x-nonce
    • x-signature
    • x-user

报错

Malformed UTF-8 data

  • 这是数据加解密的时出错了
    1. 密钥出错(多次赋值)
    2. 数据出错(不全)
    3. IV

不同加密的特征

针对Base64,介绍一下特征,快速猜测的技巧,用途场景、NodeJS中如何实现等内容

编码

Base64

  • 字符集:大小写字母、数字、"+"、"/"

  • 长度为4的倍数

  • 末尾长度不够时用 "=" 填充

  • 实现示例:(浏览器自带函数)

    • js
      BASE64 = {
          enc: function (text) { return btoa(text) },
          dec: function (text) { return atob(text) }
      }

URL编码

  • 字符集:以%开头,两位16进制,3字节一组,大小写不敏感

  • 仅对非安全字符编码

  • 常见编码值:

    • 空格:%20
    • 键值分隔符=:%3D
    • 查询参数起始符?:%3F
    • 参数分隔符&:%26
  • 实现示例:(浏览器自带函数)

    • js
      URL = {
          enc: function (text) { return encodeURIComponent(text) },
          dec: function (text) { return decodeURIComponent(text) }
      }

哈希算法

MD5

  • 长度固定:32位16进制字符串

  • 使用场景:

    • 密码存储(非安全场景)
    • 接口参数签名
    • 文件完整性校验
    • 数据去重
  • 实现示例:(crypto-js)

    • js
      const CryptoJS = require("crypto-js")
      MD5 = { enc: function (text) { return CryptoJS.MD5(text).toString() } }

SHA系列

  • 长度固定:除了SHAKE系列外。长度越长,耗时越长,安全性越高

  • 使用场景:

    • 数据完整性校验
    • 身份认证(签名认证)
  • 分类:

    • SHA-1(安全性低,缺陷)

    • SHA-2(当前主流)

      • 种类16进制长度
        SHA-224(256的删减版)56
        SHA-256(广泛使用)64
        SHA-384(512的删减版)96
        SHA-512(64位计算机更快,64位计算)128
    • SHA-3(未来趋势)

      • 种类16进制长度
        SHA3-22456
        SHA3-25664
        SHA3-38496
        SHA3-512128
  • 实现示例:(crypto-js)

    • js
      const CryptoJS = require("crypto-js")
      SHA = {
          encSHA1: function (text) { return CryptoJS.SHA1(text).toString() },
          encSHA224: function (text) { return CryptoJS.SHA224(text).toString() },
          encSHA256: function (text) { return CryptoJS.SHA256(text).toString() },
          encSHA384: function (text) { return CryptoJS.SHA384(text).toString() },
          encSHA512: function (text) { return CryptoJS.SHA512(text).toString() },
          encSHA3_224: function (text) { return CryptoJS.SHA3(text, { outputLength: 224 }).toString() },
          encSHA3_256: function (text) { return CryptoJS.SHA3(text, { outputLength: 256 }).toString() },
          encSHA3_384: function (text) { return CryptoJS.SHA3(text, { outputLength: 384 }).toString() },
          encSHA3_512: function (text) { return CryptoJS.SHA3(text, { outputLength: 512 }).toString() }
      }

HMAC

  • HMAC 本质是 “密钥 + 哈希算法” 的组合,其特征由底层哈希算法和密钥共同决定。(哈希算法的增强版)

  • 输出特征与底层哈希算法一致

  • 实现示例:

    • js
      const CryptoJS = require("crypto-js")
      HMAC = {
          encHmacMD5: function (text, key) { return CryptoJS.HmacMD5(text, key).toString() },
          encHmacSHA1: function (text, key) { return CryptoJS.HmacSHA1(text, key).toString() },
          encHmacSHA224: function (text, key) { return CryptoJS.HmacSHA224(text, key).toString() },
          encHmacSHA256: function (text, key) { return CryptoJS.HmacSHA256(text, key).toString() },
          encHmacSHA384: function (text, key) { return CryptoJS.HmacSHA384(text, key).toString() },
          encHmacSHA512: function (text, key) { return CryptoJS.HmacSHA512(text, key).toString() }
      }

对称加密

DES

  • 64位为1组分组加密
  • 密钥56位(每8位用作奇偶检验,共64位)
js
const CryptoJS = require("crypto-js")
DES = {
    _mode: ["ECB", "CBC", "CTR", "CFB", "OFB"],
    _padding: ["Pkcs7", "Iso97971", "AnsiX923", "Iso10126", "ZeroPadding", "NoPadding"],

    enc: function (text, key, iv, mode = this._mode[1], padding = this._padding[0]) {
        return CryptoJS.DES.encrypt(text, CryptoJS.enc.Utf8.parse(key), {
            iv: CryptoJS.enc.Utf8.parse(iv),
            mode: CryptoJS.mode[mode],
            padding: CryptoJS.pad[padding]
        }).toString()
    },
    dec: function (text, key, iv, mode = this._mode[1], padding = this._padding[0]) {
        return CryptoJS.DES.decrypt(text, CryptoJS.enc.Utf8.parse(key), {
            iv: CryptoJS.enc.Utf8.parse(iv),
            mode: CryptoJS.mode[mode],
            padding: CryptoJS.pad[padding]
        }).toString(CryptoJS.enc.Utf8)
    }
}

AES

  • 密钥位数:AES128、AES192、AES256(位数不够,自动填充)
js
const CryptoJS = require("crypto-js")
AES = {
    _mode: ["ECB", "CBC", "CTR", "CFB", "OFB"],
    _padding: ["Pkcs7", "Iso97971", "AnsiX923", "Iso10126", "ZeroPadding", "NoPadding"],

    enc: function (text, key, iv, mode = this._mode[1], padding = this._padding[0]) {
        return CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(key), {
            iv: CryptoJS.enc.Utf8.parse(iv),
            mode: CryptoJS.mode[mode],
            padding: CryptoJS.pad[padding]
        }).toString()
    },
    dec: function (text, key, iv, mode = this._mode[1], padding = this._padding[0]) {
        return CryptoJS.AES.decrypt(text, CryptoJS.enc.Utf8.parse(key), {
            iv: CryptoJS.enc.Utf8.parse(iv),
            mode: CryptoJS.mode[mode],
            padding: CryptoJS.pad[padding]
        }).toString(CryptoJS.enc.Utf8)
    }
}

非对称加密

常用来加密签名、验证等内容,而不是加解密数据

网页都是用的jsencrypt库,只加密解密,不能生成密钥

私钥长度公钥长度明文长度密文长度
4281281~5388
8122161~117172
15883921~245344

RSA

  • 下载库:node-rsa
  • 密钥长度:
    • 512:认为不安全
    • 1024:安全性较低,不建议用于生产
    • 2048:最常用
    • 4096:计算开销大
js
const NodeRSA = require("node-rsa")
const fs = require("fs")
RSA = {
    // 密钥是随机生成的
    generate_pri_pub_key: function (bits=2048) {
        const key = new NodeRSA({b: bits}) //生成2048位秘钥(最常用)
        const publicKey = key.exportKey('pkcs8-public')
        const privateKey = key.exportKey('pkcs8-private')
        return { publicKey: publicKey, privateKey: privateKey }
    },
    save_pri_pub_key: function (publicKey, privateKey, path) {
        fs.writeFileSync(path + 'publicKey.pem', publicKey)
        fs.writeFileSync(path + 'privateKey.pem', privateKey)
    },
    read_pri_pub_key: function (path) {
        const publicKey = fs.readFileSync(path + 'publicKey.pem', 'utf8')
        const privateKey = fs.readFileSync(path + 'privateKey.pem', 'utf8')
        return { publicKey: publicKey, privateKey: privateKey }
    },
    enc: function (text, publicKey) { return new NodeRSA(publicKey, 'pkcs8-public').encrypt(text, 'base64') },
    dec: function (text, privateKey) { return new NodeRSA(privateKey, 'pkcs8-private').decrypt(text, 'utf8') }
}

DSA

对响应数据进行逆向解密

  • 对称|非对称算法

  • 返回的是加密的数据,浏览器肯定得要进行解密再显示

  • 定位解密位置的方法:

    • 关键词:decrypt
    • hook:JSON.parse(),堆栈调用的数据
    • xhr:请求到数据后会对数据进行处理,一直点击下一步即可
  • 报错:Error: Malformed UTF-8 data

    • 原因:
      • 密钥不对
      • iv不对
      • 数据不对

异步控制流类型

模拟代码流程:

function main() {
    A = function() 需要追踪的函数方法(对数据进行了处理)
    v = A.value 对最终的结果取值
    return isDone ? func : Promise.resolve(v).then(s, f) 对取值发送网络请求,处理响应结果(异步请求)
}
结论:最终返回的是一个 异步请求,在异步请求方法里面会对参数进行加密,得追踪到这个异步请求的逻辑才能找到加密的过程

进入A函数进行查看逻辑

A = function() {
    while(1) { 说明了,这个函数会被多次调用,第一次是执行 0 这个情况,下次是 2 ,最后到 4 结束
        switch(A.prev = A.next) {
            case 0:
                A.next = 2
                return B()
            case 2:
                A.next = 4
                return B()
            case 4:
                return A.stop()
        }
    }
}

这个B()函数肯定也是一个异步调用的方法,再次追踪B方法,查看实现过程

B = function() {
    while (1) {
        switch(B.prev = B.next) {
            case 0:
                post()
                return B.next = 4,
                    y() 关键
            case 4:
                加密过程...
                return B.next = 10,
                	post(param)
            case 6:
            	...
            case 10:
                stop()
        }
    }
}

总结:
    B方法与A方法具有类似的结构,进行了二次嵌套实现(还只是简单版本),多次调用,干扰迷惑逆向逻辑
    虽然函数方法被多次调用,但是每次调用携带的参数不同,执行的分支逻辑不同,返回的结果不同。
        例如:
            第一次是发送异步请求去获取加密的密钥,将密钥作为参数传递
            第二次是用密钥对载荷进行加密
            第三次是将加密之后的载荷作为参数,返回请求数据的异步请求
            最终返回数据
    函数的参数中,
        一般有两个参数是用来控制分支逻辑的,当前的分支跟下次的分支
        有两个参数是方法参数,回调的成功方法与失败方法
        其他可能是异步携带的参数
  • 实例图:五矿集团案例
  • image-20250830072717163

载荷的数据类型是String,但是原来可能是Json格式,所以可以尝试hook一下JSON.stringify。载荷看body|data

有时候堆栈里的变量数据可能是局部数据外面的全局变量,需要在外部下断点重新追踪堆栈

webpack传入模块的方式:1.直接参数写死 2.动态传参,window[]重写push方法,全局变量

webpack扣模块思路

image-20250902003137840

webpack只会在页面刷新的时候加载一次。记得刷新的是页面

快速获取webpack模块的方法

  • 清空加载器已经加载过了的模块,复制为空数据,再用一个全局变量copy加载器的值进行读取模块,
  • 再再下一行代码打一个断点防止加载其他模块,
  • 最后获取全局变量的值即为所需的所有模块了。键值对转为字符串stringify再copy。
  • 第一个模块需要手动扣。

!function是什么意思?

记录学习,分享技术