请原谅我起了一个这样的标题博取你的眼球。与其说叫通杀,不如说按照一定的套路一定能纯算 jsvmp,我测试了部分网站 tx,x 红薯...都成功过掉了,其中最麻烦的当属某音的 a-bous,一个星期左右搞定。现在,24 年 5 月 2 日,作者也就是我,认为只要耐心一点可以纯算市面上所有的 jsvmp,以后,不好说,其实本文只是最初版本,我认为还有很大可扩展的空间,允许我先卖个关子。遗憾的是没遇到我想象中更恶心的 jsvmp,没有机会一展拳脚。以上,为了激发你的阅读兴趣,被迫装 B。
以下我将以某音的 a-bous 为例讲述如何去做,分为三个部分:流程概述,实操 a-bous,未来工作。
vmp 是什么,我就不再赘述了,相信在你动手做的时候会有自己的体会。鼓励大家动手做一遍。
一、流程概述
1.观察代码结构,观察至关重要,在第一步你需要看懂 jsvmp 的调用流程,找到“内存”数组、“指令集”数组,这些名词现在看不懂也没关系,在纯算 a-bous 时会举例说明。
2.代理 jsvmp 的“内存”数组,因为 jsvmp 是用数组模仿的汇编中的“内存”,所以找到 jsvmp 的主循环后,在“内存”数组定义下面,将其代理,然后替换网页代码,刷新载入代码。
3.copy 代理文本信息在文本编辑器中查看,找到需要逆向的密文,搜索第一次出现的位置。查看上面行代理日志初步观察生成的规律,特别要说明的是,你花费多少时间就取决于这个初步观察了,举个例子就是,有些密文的生成所带出的代理日志是有一定规律的,三个一组或四个一组;或者有些算法直接就出现特征值等等,要做的心中有数,如果只是盲目的进行第四步后续会浪费大量时间,因小失大。
4.根据代理日志的特征定位到生成密文的起点,修改代理代码,重新替换网页代码,对照代理日志,确定密文加密地点。
5.单步调试、跳出代理函数...“指令集”数组中参与运算的数据当作固定值,运算符、未知数据做好记录,对照 3 中的猜测,结合代理日志,得到密文初步生成规律,不必全部跟完,先自己构建一个算法试试能不能对上,对上就证明密文初步生成规律是正确的,反之则要多跟几步,看看哪里引入额外的运算或特殊的处理,得出算法。
6.逆向、逆向,逆着破解密文,在 5 的过程中记录的未知数据大概率是另一层加密算法,需要你结合之前的代理日志找到对应的数据,可能值已经变了但日志中对应的位置不会改变,长度也大概率不会变动。就这样“递归”下去,当所有的未知数据都逆向完毕,只剩下了些环境值、之前返回的参数、网页中的固定值、“指令集”数组中的值时,宣告纯算成功!
二、实操 a-bous
嗯,重头捋一遍某音的 a-bous 的生成流程,让我删掉本地替换,哎,好麻烦。
首先,先找一下数据接口。哦,对了,案例是评论接口,a-bous 长度为 164,没有登录。
直接搜索大法,找到,如下:
还有一件事,建议用谷歌浏览器,edge 可能会卡。
然后就是,最好找到进入 vmp 文件的最开始,以及 vmp 加密的结束,因为存在 vmp 的文件会被重复加密别的地方,这样简单筛选下,减少一些代理日志。
这个是 xhr 请求,二话不说,先来个 xhr 断点,作“尾”。什么,你问我为什么不跟栈,随你便。
发现 this 中已经有 a-bous 的值了,然后向上跟栈,最后确定进入 vmp 文件的位置,也就是“头”,如下:
但是你再去看 this 中的_url 时,发现什么鬼,这个作者绝对业余,这加密起点,所谓的“头”已经有了 a-bous,那还加密个集贸。稍安勿躁,听我解释,没有翻车,浏览器调试有个特性,跟栈显示的值会被你断点处的变量值覆盖。如果是不一样的变量名那没什么问题,如果一样,那跟栈的值就会显示成断点处的值,所以这里的 this 被之前 xhr 断点的 this 覆盖了。
好了解释完,这一点,再来说说该怎么找这种值,有个技巧,也就是“头”和“尾”,一句话就是:找两个点,“头”:加密开始前,“尾”:加密结束后,逼近加密的位置。这个需要刻意练习一下,有经验的高手,看到这种组包就会习惯性下个断点。靠,一不留神又跑题了。
言归正传,在这里下一个条件断点,因为这里是一个封装的发包函数,普通断点会一直断。
重新断下来发现,a-bous 已经没有了。
单步会进入存放 vmp 的文件了。
把代码全部拷贝下来,分析一下,这段代码的执行流程。
我记得我当时闲着无聊数了一下,它有多少带 vmp 的函数,好像是 29 个,忘记了。不过不用慌,加密只用到了其中的 4 个。
接下来就很简单了,无脑上套路!
先通过搜索定位一下最开始的 vmp:
进入流程概述-1:
i 是“指令集”数组,p 是“内存”数组,确定的方式有两种:一是分析这个函数,看主循环那个数组使用的多,怎么使用的,vmp 的解密函数解密出“指令集”数组:
二是在主循环里下断点,看看作用域种,各个数组的内容,高效,直观。
但某音特殊的地方在于,它每个 vmp 搞了两块内存,这里的 g 也是“内存”数组。
然后是流程概述-2:
代理的脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
|
封装了一下,过这些 vmp 站的时候,踩过不少坑,也逐渐完善了这个脚本,用着还可以,其实还有新的想法去进一步改进代理脚本,会在未来工作种会展示。
使用方法也很简单,下面将仔细介绍下:
前面一段扔到 vmp 文件最开始:
后面一段放在“内存”数组定义处:
因为有多个 vmp 嵌套,所以给“内存”数组搞了一个名字。然后替换代码重新载入。然后细节的地方来了,这个代理日志的存储是有一个开关的,这也就是为什么要找到“头”的一个重要原因。有了一个开关不会运行速度。来到头部打开开关。
然后流程概述-3:
来到“尾”,copy 代理日志:
第一个 vmp 很短只有 400 多行日志。
直接到网站的“尾”,找到 a-bous 的值,在代理日志中搜索:
url 编码应该不用我在提醒大家了。
找到第一项
我擦勒,啥子情况,突然出现??!是的,这种情况就预示着,这个 vmp 对于 a-bous 的加密来说,屁用没有,所以直接进入第四步了。
流程概述-4:
接下来我们要考虑的是如何定位到这一行,你可能到这还是不太懂,接下来要仔细看了。
搜索这一小段,发现只出现了两次,并且,a-bous 的长度固定为 164(长度为 164 可以直接在“头”调用进入函数测试,不是重点,这里就不在演示)。在 set 句柄中,写判断函数:
重新替换代码,再次载入,找到一个函数调用。
多次运行:
跟进去是一个新的 vmp
老规矩,重复定位:
挂上代理:
这时可以把第一个 vmp 的代理取消掉了。
重置一下 set 句柄:
重新获取代理日志:
很明显最后一行就是我们要的 a-bous,全局搜索一下:
我的发,什么鬼,又是突然出现!好吧,去 set 句柄里写定位。看到这里有一些老手,就要忍不住要拿上边的乱码、数组试一试了,你问我为什么不试试,因为我早就试过了,没这么简单。
找到第三个 vmp:
再次获取代理日志:
好家伙,这下多起来了,不过有搜索在,多少行都无所谓的。
小技巧之只搜索一部分:
这就是 a-bous 的第一层加密了,接下来我们逐渐删除加密字符串的长度,定位到开始生成的位置,为什么要这样做?这样可以让每一行的日志变短一点,方便我们观察,可以说这一步的观察是唯一体现技术的地方,敏锐的洞察,出色的判断,将决定你纯算 jsvmp 花费的时间与精力,老手与小白的差距也大概率在此拉开。不过,不用慌,什么也观察不出来,就按照套路走也可以做出来的,我保证。
OK,差不多定位到这里,可以开始观察了:这加密字符串是怎么一个个蹦出来的,几个是一轮回,遍历什么,取什么......大概心里有数。
下面我将以 a-bous 第一层加密为例,详细解释一下:
可疑点 1:
测试:
"Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe"这个东西,搜索一下,贯穿了整个日志,追溯一下第一项:
复制出来看一看:
看知道这种东西,大概率是一个固定值,先不管它,当一个未知值处理。也就是说,我们现在关心的就是 20 是哪来的,简单找一下。
这里代理日志每行都很短的好处就体现出来了,如果你的显示屏足够长的话,当我没说。
20 大概率是个什么运算,向上翻一翻:
好的,看不懂。好了,到这里我们的观察-1:“来源观察”就可以结束了,老手可能到这里又想试试这些乱码啊、数据啊,这次,我会告诉你这是在浪费时间。
然后让我们进入观察-2:“分组观察”,这个没法截图了,我大概描述下,你需要向下看一看代理日志,看看加密字符串每个字符的生成是不是大概都一样,可能每个字符的运算逻辑都一样,只不过换了数据;也可能四个一组,一组中的四个字符的运算逻辑完全不一样,但是每组的运算逻辑相同。反应到代理日志上最直观的就是代理日志的行数、每行的长度是否一样,这样大概估计一个分组,估计错了,没有影响,估计对了,大大简化纯算的流程。
当然,别的 jsvmp 可能还好,只有一个 vmp,没有几层加密,但像某音的 a-bous 这种,套了很多层 vmp,加密了很多次,每次都要观察,确实有点费眼睛,在未来工作中会给出解决办法,稍后再谈。
好了,不管你观察没观察出来它的分组,我这里都默认你没观察出来,没观察出来分组的话,就先按“每个字符的运算逻辑都一样,只不过换了数据”这种来,到时候在网站中看着改,现在再一次进入流程概述-4,找代理日志的特征,在写 set 句柄中写判断,当然,你乐意在 get 句柄中写也行。
这里有个小技巧:
我们的目的是 debugger 到一个字符开始生成之前,你当然可以看日志总结一下规律,但更方便的是找到一个字符刚生成完的地方,然后 debugger 进入就可以了。如下:
set 句柄中的判断,如下:
替换代码,重新载入。
进入流程概述-5:
在网站开始单步跟算法,记得“指令集”数组中的值如果参与运算了就当作固定值,未知的值要记录下来,每出现一个值做好笔记,否则很容易前功尽弃!指令集”数组中的值如果参与运算了就当作固定值,为什么呢?这个可以做一个思考题,自己想一想,其实你做的多了,就很容易想到了。
单步的时候小心一点,你不需要跟很多,一般来说,一层加密,跟一两个字符就能逆向了,稍后会演示,但你每一步,都要想一想它在做什么,切记不能无脑跟。
“指令集”数组算出值 258048:
现在可以去之前的代理日志中看看 258048 是不是总是有,开搜:
是不是很想说一句,臭小子让我逮到了,别急,点“查找下一个”的按钮,更惊喜的来了:
每加密四个字符出现一次,四个一组呗。别急,看看它用来做什么:
第一个运算:7835561 & 258048 = 229376
我知道你想问 7835561 是哪来的,呃,忘了截图了,7835561 是在 v 也就是第二个“内存”数组中取出来的的值,当未知值处理,先不用去搜索了,未知值做好笔记就可以了,固定值需要去以往的代理日志中搜索确定一下。
它又将结果存入 p“内存”数组中:
“指令集”数组算出值 12:
这个值估计一搜一大把,在笔记中做好记录就行了,看看 12 用来做什么,当然你也可以去代理日志中看看 258048 的下面是不是都跟着一个 12,来确定 12 是个固定且有用的值,不过我们现在已经有 80%机率确定第一层加密是四个一组,迫切的需要直到,它是怎么算出来的,所以就记录下 12,接着跟:
跟进来看一看:
第二个运算:229376 >> 12 = 56
在跟就到了这里:
哦吼,字符串变成'ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe'了,什么情况,实际上在我们全局搜索 258048 的时候,聪明的读者可能就已经看出端倪了,让我们看来看一下这张图:
注意左边的总览图,全局都在飘橘黄色,我们跟过去看看:
最开始的代理日志 258048 前面还真是'ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe'
到了后面才是"Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe"
我们跑到'ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe'最后生成的位置,看看它造了一个什么东西出来:
一顿搜索,发现生成了这么一个玩意,看看长度:
别晕,想想"Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe"是怎么来的,我们搜“尾”的 a-bous 的值逆过来的,而'ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe'呢,它是出现在代理日志的开始。好嘛,找错位置了!
其实这个长度为 148 的字符串在后续的加密中会用到,这里我们解的是第一层,就不再展开,算是留给你们的伏笔。
现在,让我们点击网站上的小三角,断到正确的位置,也就是说我们要改写一下断点的判断,有"Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe"的部分才是我们的目标。
观察代理日志:
在 get 句柄中写下判断:
此时,我们也基本可以确定,"Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe"就是固定值了。其实,在解密出的 o 中可以确定,道理与 i“指令集”数组相同:
重新断下来之后,继续单步跟。
“指令集”数组算出值 4032:
接下来到了:
又来!?
到了这里,我想我们应该去之前的代理日志中看看:
同 258048 一样,也是四个加密字符用一次,每个 4032 之后也都会有一个固定数 6,也同样会在 v(另一个“内存”数组)中取一个值。那么类比 258048,上图中的下一次运算应该是 256 >> 6 = 4,对上了!
我们找出所有的像 4032 与 6,这样的数:
也就是说 v 中取一个值是我们下一个目标,OK,现在让我们去代理日志中看看 v 中的值是从哪里来的,随便在代理日志中找一个 v 中取出来的值,全局搜索,找到这个数第一次生成的位置:
你可能会说:等等,你怎么知道那个值是从 v 中取出来的,如果你单步跟一跟的话就不会问这样的问题了。v 中取出来的值必然会和 258048 这样的值做运算,并且另一个重大发现是一组只用到了一个 v 中的值,等等等,让我们好好捋一捋,也就是说,a-bous 的第一层加密是:四个为一组,每组的运算流程相同,但每组需要一个不同的值来运算。
恭喜你想到这里,就已经破解了 a-bous 的第一层加密,接下来只要照葫芦画瓢,也就是流程概述-6,以纯算的王者之资通杀所有的 jsvmp!
因为本文的重点在于介绍纯算 jsvmp 的套路,而不是过掉 a-bous 这么一个小小的参数,所以花了很多篇幅与笔墨尽可能地去讲清这个流程的所有细节。与其说我带大家过一遍 a-bous 算法的全部逆向过程,不如大家自己动手,尝试一下。
没错,本文就写到这里了,因为实在很长了,相信能读到这里的人都会有点意犹未尽的感觉,我的建议是 have a try!
开始最好不要挑战 a-bous 这种很有难度的纯算,可以先试试 xx 音乐,等有了自己体会后,相信 jsvmp 是拦不住你的。
三、未来工作
1.其实代理脚本还可以加上“指令”也就是这里的 m:
一是在代理日志中可以有额外的信息用来筛选;二是:“指令”m 本身就蕴含着指令信息,可以帮助我们更好的分析,但我还没想好要怎么改,如果你有好的想法欢迎和我讨论。
2.相信大家实操一下就会觉得,在观察代理日志的时候会有些费眼睛,其实可以训练一个小型的神经网络来代替,输入一段加密字符串的日志,这个其实可以用但问题是用 transformer 训练的话,每一行都进行 tokenization,内存实在吃不消,其实也没必要这样做,有点杀鸡用牛刀的感觉。我现在有两个思路:一是取一些关键信息如:每行日志的长度、每个加密字符隔了多少行,get 还是 set 句柄,下标为多少,这样的边角信息去做,因为我们也是通过这些观察的;二是微调开源的 Bret,用人家已经训练好的模型抽特征,最后加一个分类头。
如果我有时间的话会继续玩一玩,或者出现更恶心的 jsvmp。