爬虫-Webpack逆向实战

2022-04-30  本文已影响0人  我只要喝点果粒橙
全文目录

webpack打包是前端js模块化压缩打包常用的手段,特征明显,比如下方的形式的代码就是webpack分发器

// 分发器
!function(x){
    function xx(n){
        return ..call(**.exports, ***, ***.exports, xx)
    }
}()

又或者更直观的表现n["xxx"]这种,你可以大概知道了这是调用了webpack打包的js模块代码。

webpack打包后JS依赖模块代码的固定结构

(this["webpackJsonpzsgk-pc"] = this["webpackJsonpzsgk-pc"] || []).push([[15], [function(e, t, n) {
    "use strict";
    e.exports = n(693)
}
// 参数固定为e, t, n
, function(e, t, n) {
    e.exports = n(697)()
}

说个逆向webpack的通用方法:

  1. 先去找加密网站的加密入口。这应该是加密网站都必须要做的==> 直接根据参数名搜索参数

  2. 找到分发器的位置,或者说是加载器,n["xxx"]这种的n就是分发器,就比如下方中的exports的位置,最后执行了d函数==>一般是runtimexxx.js中(提供环境);一般以! function(e) {的形式出现

分发器.jpg
  1. 寻找分发编号、加密使用模块(用到了哪些模块就导入哪些模块)==>一般在chunk-lib.js,以(window.webpackJsonp = window.webpackJsonp || []).push([的形式出现

  2. 将函数入口的地方返回全局变量,最终返回: var sign; var window = global;!function(){... sign = d}, 赋值为分发器返回的d

  3. 使用自定义的sign代替webpack代码中的n进行加密

from: https://blog.csdn.net/weixin_41586984/article/details/116268341

调试技巧

定位请求参数

  1. 打开开发者工具后,F5刷新后Ctrl + Shift + F搜索参数名,如signdata,会显示多个JS文件,选择后仔细查看(点击左下角{}美观格式化按钮)。

    more: 如果文件太多,则直接通过请求的链接去找,比如user/login

  2. Network找到新发出的xhr条目后,查看Initiator里的调用栈信息,如Login;

注: 如果加密参数名称比较简单如s,比较难定位的话,可以借助请求的其他参数来查找,比如verificationCode

调试工具

附录-Js记录

做题记录

n["str"]题型:

天安财险

var m = this.newEncrypt(JSON.stringify(h));

财新网

password: this.encode(this.encrypt(this.form.password)),

中远海运

天翼云

var t = encodeURIComponent(c["c"].Des.encrypt(this.form.email, this.form.pwd)),

webpack实现

c = (mycode("ac6a"), mycode("b3ae"))

自己实现:直接扒下来encrypt加密的JS内容

看准网

企名片

  • u = i("x4Ab")
  • return e.encrypt_data && (e.data = Object(u.a)(e.encrypt_data)),

n[num]题型:

大麦

掌上高考

  1. 分发器在html文件内
  2. o = (u=a(42), a.n(u)),使用到了a.n(u)即点n函数
  3. 依赖函数的给出是以数组的形式,而不是字典的形式
  4. 模块中依赖更多模块==>引入整个模块文件,但是跟"xxx"模式不同的是,由于没有用字典{"xxx": function()}的形式,因此直接require也没用TypeError: Cannot read property '42' of undefined,而是将依赖模块数组作为参数写入到分发器依赖函数中!function(e){}([...])即方括号中,从而才能找到42函数

酷我

t.data.reqId = n,

文章: https://blog.csdn.net/weixin_43189702/article/details/119860838

注意:

心得

①所有webpack打包的的js都要先看懂打包后代码运行的顺序,找到加密处;②找到webpack对象,一般是 n(数字) 调用③确定分发器。④找依赖模块,有时候各包的依赖关系太多,可以直接把文件爬下来引入,如果各个包的依赖关系不多,就可以只把调用到的函数找出来放到依赖中。⑤最后剩下的就是找到你要的代码,慢慢复现调用加密/解密函数就好了。

做题案例学习视频

进阶资料

掌上高考解密过程

解析响应data.text

相应的data.text是加密的,页面通过JS解密后渲染

  1. 确定加密位置

    return null != l && null !== (a = l.data) && void 0 !== a && a.text && (l.data = (n = (e = {
                iv: u.uri,
                text: l.data.text,
                SIGN: h
            }).iv,
    
  2. 确定分发器位置,在html内

    通过打断o = (u=a(42), a.n(u)) // 等价于 o = a(42)

  3. 确定依赖模块:给return e[a].call(c.exports, c, c.exports, r),打断点后console输出e["42"]查看a(42)位置:

function(e, t, n) {
    e.exports = (e = n(21),
    n(201),
    n(825),
    ...
    n(847), 
    e)
}

可以看到需要依赖多个,因此直接把整个文件引入

  1. 扣解密函数:注意return表达式后是逗号的情况:会从左到右执行执行,并返回最后一个。注意:JS函数并不能返回多个返回值
then((function(l) {
var e, a, t, b, n;
return null != l && null !== (a = l.data) && void 0 !== a && a.text && (l.data = (n = (e = {
            iv: u.uri,
            text: l.data.text,
            SIGN: h
        }).iv,
        a = e.text,
        e = e.SIGN,
        e = o.a.PBKDF2(e, "secret", {
            keySize: 8,
            iterations: 1e3,
            hasher: o.a.algo.SHA256
        }).toString(),
        n = o.a.PBKDF2(n, "secret", {
            keySize: 4,
            iterations: 1e3,
            hasher: o.a.algo.SHA256
        }).toString(),
        a = o.a.lib.CipherParams.create({
            ciphertext: o.a.enc.Hex.parse(a)
        }),
        n = o.a.AES.decrypt(a, o.a.enc.Hex.parse(e), {
            iv: o.a.enc.Hex.parse(n)
        }),
        // data.text解析结果
        JSON.parse(n.toString(o.a.enc.Utf8)))),
    v && (t = r,
        b = l,
        null !== (n = window.apiConfig) && void 0 !== n && null !== (n = n.filterCacheList) && void 0 !== n && n.length ? window.apiConfig.filterCacheList.forEach((function(l) {
            new RegExp(l).test(t) || d.set(t, b)
        })) : d.set(t, b)),
    l
}

难点:

获得加密参数signsafe

大致流程跟data.text差不多,但是p = c()(g)执行时,会报错

Md5.prototype.update = function(e) {
    if (!this.finalized) {
        var t, n = typeof e;
        if ("string" != n) {
            if ("object" != n)
                throw ERROR;
            if (null === e)

根据一步步调试之后发现,还是c = (u=a(291),a.n(u))直接替换出的问题

  1. Ctrl + shift + F定位参数

    g = void 0,
        g = (t = {
        SIGN: h,
        str: f.replace(/^\/|https?:\/\/\/?/, "")
    }).SIGN,
        t = t.str,
        g = o.a.HmacSHA1(o.a.enc.Utf8.parse(t), g),
        g = o.a.enc.Base64.stringify(g).toString(),
        p = c()(g),
        u.signsafe = p,
    
  2. 往上找c和o.a: o = (u = a(42),a.n(u)), c = (u = a(291),a.n(u))

  3. 找到分发器扣出==>这次不能删除分发器中多余的函数,比如r.a、r.d、r.n因为后面得用

  4. 将依赖模块跟data.text一样,放入分发器依赖模块中

  5. 扣加密函数

    网页上是return后多段内容,以及g变量不断被修改,因此通过一步步调试确定入参,以及分解return抽离出真正的加密参数signsafe

    h = "D23ABC@#56"
    var o = {}
    
    o.a = mycode(42)
    // ▲
    c = (u = mycode(291), mycode.n(u))
    
    /**
     * 对url进行加密
     */
    function encrypt(f) {
        g = (t = {
                SIGN: h,
                str: f.replace(/^\/|https?:\/\/\/?/, "")
            }).SIGN,
            t = t.str;
        // console.log(t, g);
        g = o.a.HmacSHA1(o.a.enc.Utf8.parse(t), g);
        // console.log(g)
        g = o.a.enc.Base64.stringify(g).toString();
        // console.log(g)
        p = c()(g);
        return p;
    }
    
    res = encrypt("https://api.eol.cn/web/api/counter?cid=1&did=263")
    console.log(res)
    

可以看到o.a和c的赋值是不一样的,虽然说大多数情况x = a.n(u)等价于x=u,但难免有时会有不一样,因此谨慎期间,还是还原到底最好。

Python调用

import execjs
def get_signsafe_by_javascript(url):
    # 两个 JavaScript 脚本,两种方法均可
    with open('gk_signsafe.js', 'r', encoding='utf-8') as f:
        exec_js = f.read()
    signsafe = execjs.compile(exec_js).call('encrypt', url)
    return signsafe
signsafe = get_encrypted_password_by_javascript("https://api.eol.cn/web/api/counter?cid=1&did=263")
print(signsafe)

RSA的加密步骤

  1. 获取公钥
  2. 实例化 ===> 扣出网站RSA实例化对象的代码
  3. 设置公钥
  4. 对文本进行加密 ==> 扣出复现RSA加密的逻辑代码

注: var window=globalvar navigator={}

加密、摘要算法结果特征

urlencode

urlencode是一个函数,可将字符串以URL编码,用于编码处理。

URL编码(URL encoding),也称作百分号编码(Percent-encoding), 是特定上下文的统一资源定位符 (URL)的编码机制。

Base64特征

最常见的用于传输8Bit字节码编码方式之一

注:跟下面的算法区分一下,base64是编码方式,并不能算加密算法。应用场景还有传输图片:data:image/jpg;base64,/9j/4QMZRXhpZgAASUk...

md5特征

消息摘要算法

RSA特征

注:RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题;

▲.一般会使用 JSEncrypt 库,会有 new 一个实例对象的操作;

SHA 系列

SHA 是比 MD5 更安全一点的摘要算法,SHA 通常指 SHA 家族算法,

sha1

字母(a-f)和数字(0-9)混合

密文特征跟MD5差不多,只不过数字是40位,bit位数(160)==>4位十六进制表示一个数

Sha256

字母(a-f)和数字(0-9)混合

对于任意长度的消息,SHA256都会产生一个256位的哈希值,即64位十六进制数,称作消息摘要。

HMAC

在md5和sha1加密的基础上引入了秘钥,而秘钥又只有传输双方才知道,所以基本上是破解不了的,常用于接口签名验证

AES、DES、3DES、RC4、Rabbit 等

AES、DES、3DES、RC4、Rabbit 等加密算法的密文通常没有固定的长度,他们通常使用crypto-js库来实现

参考:https://juejin.cn/post/7052978567390429215

上一篇 下一篇

猜你喜欢

热点阅读