关于js解密的一些事

本人新手菜鸡一个,但是由于工作中对数据采集的能力要有比较大的考验,之前接手了公司的数据采集系统,主要负责一个日常改版、改接口、改加密方式的某购物网站(具体不透露)。这个网站确实很坑,坑的地方不在于他的商品怎么样,而是以一个纯技术的角度去分析他的接口加密,说在开始,js解密是一个非常枯燥并且很考验一个人的耐心和细心程度。我在这方面还是有待加强。当前这里还有很多技巧性的插件我没有用到,纯用自己的感觉去抽取代码和debug,这里给大家推荐一个插件叫做油猴子,看大神用的不错,但是本人还是不怎么会用~~~。

对了吗,最重要的一点,每次重新开始debug的时候一定要清缓存或者重新打开一个无痕浏览器,这样会少走很多弯路,经验之谈~~~

好的步入正题:

首先,我们要对我们需要解决的参数做一个搜索,某网站的加密参数是****_***tent(为了安全,大家见谅),在做数据采集的时候难免会遇到搜索,这个关键词就是在抓包后发现的,对于怎么抓包可以采用chrome自带的功能也可以用charles甚至大佬用wireshark来对数据进行抓包。拿到这个关键词后,你要找到这个关键词是在哪生成的需要一个技巧,就是在浏览器中的network中按 command + f 键来对这个关键词进行搜索,windows下用ctrl + f,类似这样的一个框:

这样你就可以对你的想要搜索的关键词进行筛选,这个方法同样适用于关键词的抓包,是一个不错的技巧。

对我们想要关键词进行搜索,会出现这样的几个js

将这个js打开,再次对我们的关键词进行搜索(同样的搜索方法),就能发现是我们需要的js代码。

搜索结果有三个,这样就需要我们自己去判断哪一段代码是来初始化这个加密函数的地方,这里贴一下搜索出来的代码

第一个
this.****_***tent && (this.params.****_***tent = this.****_***tent),
第二个
return r.riskController.getRiskControlInfoAsync().then(function(e) {
                        e && (t.params.****_***tent = e)
第三个
this.****_***tent && (e.****_***tent = this.****_***tent)

是不是很明显的看到是第二个中的riskController.getRiskControlInfoAsync()这个函数是来用执行入口函数的,这样就找到了第一个函数注入点。

继续对这个函数进行搜索

出现如下的代码

r.riskController.initRiskController()

这段代码很明显是用来初始化riskController()这个函数的,对其打断点debug可以看到进入了

key: "initRiskController",
value: function() {
       this.riskControlCrawler || (this.riskControlCrawler = new n.a({
           serverTime: this.serverTime,
           _2827c887a48a351a: !1
       }),
       this.riskControlCrawler.init())
                }

这个函数中,继续一步debug,可以看到是进入了这个函数中

e[p("0xd3", "ai[I")] = function(e) {
                            return te || (te = new ee(e))
                        }

这个代码其实是要去执行 ee(){}这个方法。在这一段代码的debug中真正的入口其实是this.riskControlCrawler.messagePackSync这个方法,这个方法也是指向 ee这个方法的,所以我们的主调函数就是这个可以叫他main函数。

                        function ee() {
                            var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}
                              , t = {};
                            t[p("0xb5", "5Bg9")] = p("0xb6", "zANS"),
                            t[p("0xb7", "jLF%")] = function(e) {
                                return e()
                            }
                            ,
                            t[p("0xb8", "UT1q")] = p("0xb9", "XdCJ"),
                            t[p("0xba", "#*n4")] = function(e) {
                                return e()
                            }
                            ,
                            t[p("0xbb", "X^fs")] = function(e) {
                                return e()
                            }
                            ;
                            for (var r = t[p("0xbc", "iFuj")][p("0xbd", "Gq*t")]("|"), n = 0; ; ) {
                                switch (r[n++]) {
                                case "0":
                                    I = Date[p("0xbe", "Vfyd")]();
                                    continue;
                                case "1":
                                    t[p("0xbf", "X#ub")]($);
                                    continue;
                                case "2":
                                    this[p("0xc0", "NKkM")](e[t[p("0xc1", "Yy&w")]] || 879609302220);
                                    continue;
                                case "3":
                                    t[p("0xc2", "]iEY")](J);
                                    continue;
                                case "4":
                                    t[p("0xc3", "szDT")](X);
                                    continue
                                }
                                break
                            }
                        }

在对这个进行debug的时候 你应该会发现ee这个方法是调用的(J)这个方法,继续向上查找发现J这个方法是去调用了z{}这个对象,而在z这个方法中调用了o()方法,继续向上寻找,o这个方法调用了i()这个方法并且对i这个方法传递了一个参数而i这个方法是这样的:

            function i(e) {
                return (i = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(e) {
                    return typeof e
                }
                : function(e) {
                    return e && "function" == typeof Symbol && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e
                }
                )(e)
            }

经过这么一系列的调用 我们发现这个函数其实是一个主调函数 代码太长  就不贴了  行数是从621行到1107行,在这个函数中 我们会发现有很多r(9)之类的函数,这些函数其实就是我们正常的函数调用,只不过是用了js加密方法做了混淆。

a = r(6), s = r(9), c = r(4), l = r(16),

我们在对这行函数进行debug的时候,我们可以在console中看这些被混淆过的函数都是哪些函数

r(6)
e.exports = function(e, t) {
                        if (null == e)
                            throw new Error("Illegal argument " + e);
                        var r = n.wordsToBytes(s(e, t));
                        return t && t.asBytes ? r : t && t.asString ? i.bytesToString(r) : n.bytesToHex(r)
                    }
r(9)
function(e, t, r) {
                    "use strict";
                    var n = r(10)
                      , o = r(0)
                      , a = r(14)
                      , i = r(3)
                      , s = r(15)
                      , c = Object.prototype.toString
                      , l = 0
                      , u = -1
                      , p = 0
                      , f = 8;
                    function d(e) {
                        if (!(this instanceof d))
                            return new d(e);
                        this.options = o.assign({
                            level: u,
                            method: f,
                            chunkSize: 16384,
                            windowBits: 15,
                            memLevel: 8,
                            strategy: p,
                            to: ""
                        }, e || {});
                        var t = this.options;
                        t.raw && t.windowBits > 0 ? t.windowBits = -t.windowBits : t.gzip && t.windowBits > 0 && t.windowBits < 16 && (t.windowBits += 16),
                        this.err = 0,
                        this.msg = "",
                        this.ended = !1,
                        this.chunks = [],
                        this.strm = new s,
                        this.strm.avail_out = 0;
                        var r = n.deflateInit2(this.strm, t.level, t.method, t.windowBits, t.memLevel, t.strategy);
                        if (r !== l)
                            throw new Error(i[r]);
                        if (t.header && n.deflateSetHeader(this.strm, t.header),
                        t.dictionary) {
                            var h;
                            if (h = "string" == typeof t.dictionary ? a.string2buf(t.dictionary) : "[object ArrayBuffer]" === c.call(t.dictionary) ? new Uint8Array(t.dictionary) : t.dictionary,
                            (r = n.deflateSetDictionary(this.strm, h)) !== l)
                                throw new Error(i[r]);
                            this._dict_set = !0
                        }
                    }
                    function h(e, t) {
                        var r = new d(t);
                        if (r.push(e, !0),
                        r.err)
                            throw r.msg || i[r.err];
                        return r.result
                    }
                    d.prototype.push = function(e, t) {
                        var r, i, s = this.strm, u = this.options.chunkSize;
                        if (this.ended)
                            return !1;
                        i = t === ~~t ? t : !0 === t ? 4 : 0,
                        "string" == typeof e ? s.input = a.string2buf(e) : "[object ArrayBuffer]" === c.call(e) ? s.input = new Uint8Array(e) : s.input = e,
                        s.next_in = 0,
                        s.avail_in = s.input.length;
                        do {
                            if (0 === s.avail_out && (s.output = new o.Buf8(u),
                            s.next_out = 0,
                            s.avail_out = u),
                            1 !== (r = n.deflate(s, i)) && r !== l)
                                return this.onEnd(r),
                                this.ended = !0,
                                !1;
                            0 !== s.avail_out && (0 !== s.avail_in || 4 !== i && 2 !== i) || ("string" === this.options.to ? this.onData(a.buf2binstring(o.shrinkBuf(s.output, s.next_out))) : this.onData(o.shrinkBuf(s.output, s.next_out)))
                        } while ((s.avail_in > 0 || 0 === s.avail_out) && 1 !== r);return 4 === i ? (r = n.deflateEnd(this.strm),
                        this.onEnd(r),
                        this.ended = !0,
                        r === l) : 2 !== i || (this.onEnd(l),
                        s.avail_out = 0,
                        !0)
                    }
                    ,
                    d.prototype.onData = function(e) {
                        this.chunks.push(e)
                    }
                    ,
                    d.prototype.onEnd = function(e) {
                        e === l && ("string" === this.options.to ? this.result = this.chunks.join("") : this.result = o.flattenChunks(this.chunks)),
                        this.chunks = [],
                        this.err = e,
                        this.msg = this.strm.msg
                    }
                    ,
                    t.Deflate = d,
                    t.deflate = h,
                    t.deflateRaw = function(e, t) {
                        return (t = t || {}).raw = !0,
                        h(e, t)
                    }
                    ,
                    t.gzip = function(e, t) {
                        return (t = t || {}).gzip = !0,
                        h(e, t)
                    }
                }

其中比较坑的是r(4)这个调用,你会发现你怎么也找不到能匹配的属性,这里其实也是对属性做了混淆,将一些属性变成了0x35这种类型的东西,但是可以发现有一个es的属性是很多的,其中有一个es和en是出现在同一个方法中,你再将这里打断点,你会发现

o("0xb", "&S@A")

这个东西在console中可以输出成咱们可以识别的文字,这样就又找到了一个加密的函数。同理可以找到r(16)的方法,找到这些方法后会发现这些方法中还存在很多混淆之后的调用,这样需要继续用刚才的方法去将这些函数抽出来,但是要注意这些方法本身的嵌套和调用关系。

将这些方法都抽出来以后,我们回头去看一下这个主调函数中的参数传递,在debug中是有一个e的参数传递,我们可以看到

{i: 5, l: false, exports: {…}, deprecate: ƒ, paths: Array(0), …}

是这样的一个东西,那我们是不是可以将这个参数作为一个写死的参数呢?答案是肯定的,但是要注意的是在这些加密函数中有很多次这样的参数传递,但是实际传递的值却是不同的,需要在debug中仔细观察才能不会漏掉这些参数。

在将所有的加密函数都抽出来之后需要做的一件事就是找加密的参数,这个也是js解密中比较头疼的一个内容。

我们回到最开始的地方,在进行riskController.initRiskController中,我们会发现一个e的参数,这个参数中我们可以发现一个listID,是不是很熟悉,是不是我们在对这个接口进行请求的时候在url中就有这个参数呢,所以这个参数是请求这个接口必须带的一个参数,当然首当其冲的一个时间戳肯定是要参与加密运算的。

再次,我们在对加密函数进行抽取的时候发现了一个window 对象,我们在console中将window对象进行打印可以看到很多的参数,但是那些是参与运算的呢,这里还是需要从我们的加密函数中进行获取,

我们在加密函数中其实可以获取到这些

M = window[p("0x14", "HlWl")],
L = window[p("0x15", "Gq*t")])

将这个打印出来  其实就是一个窗口的大小,我们继续对加密的函数继续debug,在

H[p("0x66", "Gtlg")] = function() {
   this[T] = a(A[w][g] ? A[w][g] : "")
   }

这代码中,可以看到A 是一个对象,w和g分别是location和href,通过这两个参数,在windows对象中找到了对应的参数,发现location中的href就是你的referer,并且是一个动态的,所以我们需要在调用这个js的时候就将你的url作为参数通过命令行传进来

var referer_page_url = process.argv.splice(2)[0]

同理在你的加密函数中可以将你的screen参数找到其中又包含了availHeight和availWidth。继续向下debug在

t[5] = e[p("0x3f", "Gtlg")](M[p("0x40", "rfzr")], !0) ? 1 : 0,

这行代码中的M 其实一个navigator对象,你需要把这个对象中的属性单独拿出来就好了,还有两个必要的参数在window对象中 那就是时间戳和一个MATH这两个参数的寻找方法和以上的方法是相同的。经过这些抽取的代码以及参数的准备,就可以进行下一步了,那就是将这些抽出来的代码以及参数串起来。

串起来的方法其实和js解密已经关系不是很大,重点考验的是你对js代码的熟悉程度,奈何本人对js特别菜,串了好久也没成功的串起来,最后还是在一位大神的代码下 成功的将自己抽取的代码串了起来生成了所需要的加密参数。这里就不放代码了。希望大家可以自行将代码串起来,欢迎大家留言交流~

注:本博客纯属技术交流,不带任何利益色彩。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值