声明:本文只作学习研究,禁止用于非法用途,否则后果自负,如有侵权,请告知删除,谢谢!

前言:我技术可能不是很牛逼,但我很会偷懒,怎么简单怎么来,所以有更好的解密方法可以在评论区评论~

目标网站:这个网站很坑,那个验证字段搜不到,XHR断点也断不到,就很奇怪,不过竟然我敢写这篇文章,就代表我是过了的。

这是网站:

aHR0cHM6Ly90b29scy5taWt1LmFjL25vdmVsYWkv

关于JS逆向,无非就是:

        1.寻找参数

        2.定位参数生成的位置

        3.分析参数的加密逻辑

        4.扣代码调用

关于webpack,不了解的可以看下面这篇文章:

https://app.yinxiang.com/fx/970ae39c-9964-4aae-aa96-7e81fee4ef8f

         简单来说就是经过webpack打包之后所有资源都变成了模块,通过“加载器(分发器)”进行调用。

下面开始逆向过程:

打开目标网站,多点击目标网站的“开始生成”按钮,等待生成成功,比较几次请求可以发现请求参数都是一样的,而请求头中有个authsign每次都是不一样的,故判断该参数是验证参数(实际上确实是这个)。

JS逆向 webpack解密

第一步,老规矩先全局搜索一下,

JS逆向 webpack解密

似乎是没有赋值的地方,这就很奇怪,

第二步,定位参数位置。总之先看看发起请求的位置(在启动器里面找)(其实还有另一种方法:Js Hook,下面再讲)

JS逆向 webpack解密

 跳转到这里了,在这里打个断点。

JS逆向 webpack解密

 再次点击“开始生成”

JS逆向 webpack解密

 可以发现这里有个t,里面的headers不知道是什么(一般的headers都是请求头,所以这个很可疑,先记住这个t),总之按F10跳过函数继续执行。

JS逆向 webpack解密

e是个数组,好像在遍历这个数组 ,不管他,反正看不懂,接着摁F10跳过。

JS逆向 webpack解密

到这里之后可以发现这里请求参数,但是并没有我们想要的"authsign", 接着摁F10跳过直到看到我们需要的参数(Authsign)。

可以发现这里又出现了那个t,里面的headers依旧是那些值。在摁F10跳过函数

JS逆向 webpack解密

好,跳过之后可以发现这里这个t里面的headers里面多了个Authsign。所以这个Authsign的生成位置就在这里面。

JS逆向 webpack解密

我们在这里打上断点,取消其他断点,重新点击“开始生成”,

JS逆向 webpack解密

在这里断住之后,我们摁F9逐步执行(F10是跨函数执行),

JS逆向 webpack解密

逐步执行到了这里,打印一下,可以发现这里就是加密参数生成的地方,现在已经定位到了。

JS逆向 webpack解密

来讲讲另一种方法:JS Hook

 需要在浏览器安装拓展:油猴(Violentmonkey)

        打开插件新建脚本:该脚本的作用是在指定请求头被设置的时候进入debugger状态。

        更多脚本:(https://www.jb51.net/article/241736.htm)

// ==UserScript==
// @name        tools.miku
// @namespace   Violentmonkey Scripts
// @match       https://*/*
// @grant       none
// @version     1.0
// @author      -
// @description 2022/11/4 08:39:59
// ==/UserScript==
var code = function(){
var org = window.XMLHttpRequest.prototype.setRequestHeader;
window.XMLHttpRequest.prototype.setRequestHeader = function(key,value){
    if(key=='Authsign'){
        debugger;
    }
    return org.apply(this,arguments);
}
}
var script = document.createElement('script');
script.textContent = '(' + code + ')()';
(document.head||document.documentElement).appendChild(script);
script.parentNode.removeChild(script);

然后刷新页面然后启动该脚本。重新点击“开始生成”,可以发现会在这里断住,说明这个时候“Authsign被设置上了Header”。

JS逆向 webpack解密

 然后我们开始追栈,追栈的目的也是为了找到加密参数产生的位置。

JS逆向 webpack解密

调用堆栈是当前代码的调用者,以及调用者的调用者说明白点就是,程序执行到这里之前的所有操作,而在这里设置上了“Authsign”值,追栈就是从上往下追,最上面的是当前位置,下面的就是上一个调用位置。还不会的就只能自己去找追栈的教程看。

最后追到这个h.request这里,就和启动器进去的那个是一样的了,摁F10一步一步看,在哪里添加上了“Authsign”,就跟进去,也能定位到加密参数位置。

接着刚刚的讲,定位到加密参数之后。

JS逆向 webpack解密

通过观察可以发现这是一个webpack打包的。webpack打包的都很有特点,有个分发器。

f是JS逆向 webpack解密,而y在上面定义了,JS逆向 webpack解密 y = n.n(f),好。

下面我们开始扣代码了,先在y=n.n(f)这里打上断点,刷新页面(模块都是一开始就加载好的所以我们是刷新页面而不是点击“开始生成”)

断住之后跳到n里面去。可以鼠标浮在n上面然后跳入该方法,也可以直接在控制台打印n,然后双击跳入该方法。

JS逆向 webpack解密

 JS逆向 webpack解密

 可以看到这就是webpack所谓的加载器(分发器)

JS逆向 webpack解密

我们直接复制这整个js文件, 有的教程可能会说只复制加载器就可以了,实则不然,可以发现我们的y=n.n(f)是n.n的,而n是这个o函数,就相当于o.n(f),我们在这个js文件搜索一下,可以发现下面定义了o.n,所以我们需要全部复制而不是只复制加载器。

JS逆向 webpack解密

 新建js文件,粘贴到此。

JS逆向 webpack解密

定义一个全局变量lorder。把最后一个d()改成lorder=o,最一个d()是入口函数,我们用不到,这样我们就可以在外面使用lorder做o的事了。 然后我们把数组形式改为对象形式,自己建两个方法测试一下。

JS逆向 webpack解密JS逆向 webpack解密

 出异常了。window is not defined。window未定义,我们去开头定义一个var window={}; 

JS逆向 webpack解密

 然后再运行,成功输出,说明我们的加载器没有问题。

JS逆向 webpack解密

好,接下来去复制我们需要的方法,JS逆向 webpack解密f=n(267),y=n.n(f),也是一样,刷新页面跳进去,然后复制方法。我们可以看到n(267)并不是方法。

JS逆向 webpack解密

 这个时候我们跳进n,也就是加载器那,打上断点,重新刷新页面,然后在控制台打印下标为267的函数,e就是函数数组,也就是e[267]。

JS逆向 webpack解密

跳进去之后发现里面整个js模块和其他的不一样(这个是数组形式的),而且里面又调用了一堆函数,这个时候就不管他了,一个一个复制太累人了,当然你要是喜欢一个一个复制那也可以,不过要记得对象形式前面记得改成对应的下标,也就是这样

JS逆向 webpack解密

这里就不演示了,我们使用懒人方法,一键自吐。先定位到e[267]所在js的开头,以这种开头的都是webpack打包的模块文件。

JS逆向 webpack解密

我们也复制这个js的模块,把他放我们的加载器中(注意,我们的加载器js要重新去浏览器中复制下来,得是未更改的,就是没加lorder的)。

JS逆向 webpack解密

 然后需要用到渔滒大佬的ast逆向。这是大佬的项目地址。

文件 · master · 渔滒 / webpack_ast · GitCode

缺依赖补依赖,我这里在这个ast文件夹创建了一个a.js文件,然后将我们写的js(加载器以及数组模块)复制到a.js。

JS逆向 webpack解密

然后打开cmd进入到该ast文件夹,执行一下命令:

node webpack_mixer.js -l a.js -o webpack_out.js

 会报错的话缺依赖补依赖,然后运行成功之后就会多出一个webpack_out.js文件,这个文件就是变成对象形式的模块。

然后将该js重新复制回我们自己的js,而且大佬还贴心的帮我们生成了全局变量供我们调用(export_function = o;),这里再次给大佬点个赞。

JS逆向 webpack解密

 而代码中的n就相当于o函数,而o又赋值给了全局变量export_function,也就是说,n(267)=o(267)=export_function(267),267号方法有了,接着往下看,

JS逆向 webpack解密

我们也定义n(267),以及y,

JS逆向 webpack解密然后是m函数,在上面定义了,我们复制过来 ,总之就是缺什么补什么

JS逆向 webpack解密

运行之后又异常了,看到这个异常就知道是缺函数了,而我们又不知道缺哪个函数,所以我们在加载器里面加上代码console.log(c),这里的c就是下标,也可以说是对象名称,这样就知道他是在哪个函数报错了,也就知道缺的是哪个函数了, 

JS逆向 webpack解密

 emmm,51号函数

JS逆向 webpack解密

老规矩,进到装饰器中打上断点 

JS逆向 webpack解密

然后跳进51号方法并且将整个js的模块复制到加载器中,注意,我们的加载器js要重新去浏览器中复制下来,和上面那个一样,也是cmd进入到ast文件夹,执行一下命令:

 node webpack_mixer.js -l a.js -o webpack_out.js

 然后,将输出的js文件中的模块复制到我们自己的js加载器模块后面就可以了,

JS逆向 webpack解密

 方法之间记得打逗号隔开!JS逆向 webpack解密

 放好之后保存运行,又报错了。e没有定义, 

JS逆向 webpack解密

 我们来找找e从哪来的。往上找可以发现e在这,是方法的参数,

 JS逆向 webpack解密

 然后我们看看这个方法的参数是怎么来的,往后看可以发现是.call(this, n(262), n(51)) ,也就是说,e是n(262),o是n(51)。

JS逆向 webpack解密

好,然后我们也定义一个e=n(262),保存之后再运行,又报错了,r未定义,一样的,找r在哪定义的

JS逆向 webpack解密

 发现就在开头处,

JS逆向 webpack解密

我们也把r定义一下,r=n(56),保存运行报错,o未定义,o就是刚刚那个n(51),我们也定义一下,然后保存运行报错。。

JS逆向 webpack解密

在e = decodeURIComponent(l);这里报错了,URIError: URI malformed,这个就很奇怪了,看看是不是少了什么东西。好,可以看到这个m的上面确实是有一些参数的定义,我们没有,把他加上去。JS逆向 webpack解密

 好了,加上去之后依旧是保存运行报错,不过不是刚刚的错了,这次是d(...)(...)[m(...)] is not a function,看看d在哪定义,有没有做什么多余的操作。

JS逆向 webpack解密

然后可以看到,d定义之后有个操作,d.a.extend(_.a) ,而_ = n.n(m),m = n(568),

JS逆向 webpack解密

我们也加上这些定义,依旧是保存运行报错,document is not defined,

JS逆向 webpack解密

 这个异常和window不一样,在上面定义也不管用,总之我们先试一下在上面定义,var document={},可以看到f是打印出来了,但是还是有问题,提示未定义属性“words”,而words又是f的属性,

JS逆向 webpack解密

 总之打印然后和浏览器的对比一下吧,m("8", "fUV&")是domain,也就是域名,也就是说,document[domain]输出是undefined,

JS逆向 webpack解密

 看看浏览器的,

JS逆向 webpack解密

 所以说,由于我们的程序获取不到浏览器文档的域名,所以我们直接用死值就好了,改好之后保存运行,又报错了,加密模块有问题,

JS逆向 webpack解密

 我们打印看看是什么加密,可以看到是AES的加密方式,

JS逆向 webpack解密

参数已经有了,我们引用nodejs的加密库,

var CryptoJS = require("crypto-js");

crypto-js自行安装,然后下面的加密也改一下,保存运行 

JS逆向 webpack解密

 功夫不负有心人,终于是加密成功了!放到apipost上试一下,很奇怪,没有数据,难道是加密的数据有问题?

JS逆向 webpack解密

 经过不懈的努力,最后发现是JS逆向 webpack解密

 这个f的document[m("3", "jx9[")]有问题,之前说过我们程序访问不到网站的document,所以我们打印看看是需要什么值,

JS逆向 webpack解密

可以看到他也是domain域名,所以我们这个document[m("3", "jx9[")]也要替换成'tools.miku.ac',保存运行,

JS逆向 webpack解密

再次拿着加密的数据放到Apipost里,这次请求的比较久(大喜),

JS逆向 webpack解密

可以看到网站返回了base64形式的图片,终于是成功了!

我贴上核心解密代码,中间的模块就不贴了(太多了),

var CryptoJS = require("crypto-js");
var document = {}
var export_function;
!function (e) {
    //这里是加载器代码
}({
    //这里是模块代码
});
module.exports = export_function;
e = export_function(262)
o = export_function(51)
r = export_function(56)
f = export_function(267)
y = export_function.n(f)
h = (export_function(37), export_function(91), export_function(92), export_function(25), export_function(123), export_function(49))
d = export_function.n(h)
m = export_function(568)
_ = export_function.n(m)
d.a.extend(_.a)
var n, l, h = ["jsjiami.com.v6", "jsjSiamtirN.coEmh.Fulv6YWfbWzOB==", "bALCr8KoHEA=", "w7BQDcKBPMO7", "wqw8W8OFT1XDnw==", "TcKFbsOIw7oTwqnDrQ==", "wqM3w7c=", "w6TCqsKDw6PCsMOaRA==", "wq9Qw7rDpcKRZg==", "w7Msw5zDtsORCsOJIw==", "OlPDoQ==", "IsOIWMOowpvCtHdD"];
n = h,
    l = 448,
    function (e, t, o, r) {
        if ((t >>= 8) < e) {
            for (; --e;)
                r = n.shift(),
                    t === e ? (t = r,
                        o = n.shift()) : o.replace(/[StrNEhFulYWfbWzOB=]/g, "") === t && n.push(r);
            n.push(n.shift())
        }
    }(++l, 114688);
var m = function t(n, c) {
    n = ~~"0x".concat(n);
    var l = h[n];
    if (void 0 === t.RVAulw) {
        !function () {
            var t = "undefined" != typeof window ? window : "object" === (void 0 === e ? "undefined" : Object(r.a)(e)) && "object" === (void 0 === o ? "undefined" : Object(r.a)(o)) ? o : this;
            t.atob || (t.atob = function (e) {
                for (var t, n, o = String(e).replace(/=+$/, ""), r = 0, c = 0, l = ""; n = o.charAt(c++); ~n && (t = r % 4 ? 64 * t + n : n,
                    r++ % 4) ? l += String.fromCharCode(255 & t >> (-2 * r & 6)) : 0)
                    n = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(n);
                return l
            }
            )
        }();
        t.hRdDHj = function (e, t) {
            for (var n, o = [], r = 0, c = "", l = "", h = 0, d = (e = atob(e)).length; h < d; h++)
                l += "%" + ("00" + e.charCodeAt(h).toString(16)).slice(-2);
            e = decodeURIComponent(l);
            for (var m = 0; m < 256; m++)
                o[m] = m;
            for (m = 0; m < 256; m++)
                r = (r + o[m] + t.charCodeAt(m % t.length)) % 256,
                    n = o[m],
                    o[m] = o[r],
                    o[r] = n;
            m = 0,
                r = 0;
            for (var _ = 0; _ < e.length; _++)
                r = (r + o[m = (m + 1) % 256]) % 256,
                    n = o[m],
                    o[m] = o[r],
                    o[r] = n,
                    c += String.fromCharCode(e.charCodeAt(_) ^ o[(o[m] + o[r]) % 256]);
            return c
        }
            ,
            t.gjhasZ = {},
            t.RVAulw = !0
    }
    var d = t.gjhasZ[n];
    return void 0 === d ? (void 0 === t.PPSXqK && (t.PPSXqK = !0),
        l = t.hRdDHj(l, c),
        t.gjhasZ[n] = l) : l = d,
        l
}
_ = d()()[m("0", "ZLEd")]()[m("1", "1)V^")]()
f = y.a[m("2", "ise7")]("" + _ + 'tools.miku.ac');
authsign=f + "." + CryptoJS.AES.encrypt(_, 'tools.miku.ac')[m("9", "6lDQ")]()
console.log(authsign)

本篇文章是给自己做个记录,当然也欢迎大家学习交流。

发表回复