当前位置: 首页 > news >正文

jsvmp-某乎 x-zes-96 算法还原

文章目录

        • 1. 找到关键入口
        • 2. 分析流程
        • 3. 算法还原

前言

​ 仅作学习交流,非商业用途,如侵删。

​ 第一次写篇幅这么长的文章,在完成算法还原的情况下整理这篇帖子也花费几乎一天的时间,记录一次手撕vmp的过程。

网站链接

aHR0cHM6Ly93d3cuemhpaHUuY29tL3NlYXJjaD9xPXB5dGhvbiZ0eXBlPWNvbnRlbnQ=

1. 找到关键入口

​ 我们选择直接使用粗暴的搜索方法,要解密的 x-zes-96 在这个url header 里面。

// 隐藏域名 防止帖子暴毙
https://www.xxx.com/api/v4/search_v3?gk_version=gz-gaokao&t=general&q=python&correction=1&offset=0&limit=20&filter_fields=&lc_idx=0&show_all_topics=0&search_source=Normal

​ 直接搜 x-zse-96 找到入口,只有两个位置统统打上断点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PVMD3owe-1662087958898)(./_pic/image-20220902050912358.png)]

2. 分析流程

​ 多打印两次看看 s 和 f()(s) 的值是不是值是否是不是动态的,防止走入误区。

​ 经过验证发现这是个标准md5可以使用标准库。那我们就没必要在这个上面浪费时间,我们主要分析x-zst-96所以跳过这个。

在这里插入图片描述

​ 然后再来多打印两次看看 (0,F®.encrypt)(f()(s)) 的值是不是值是否是不是动态的,防止走入误区。

​ 是个动态的,我们先给它留意下,继续往下走。

​ F11 跟进去 F函数 就是这个 F®.encrypt)(f()(s) 所以直接 分析这个 encrypt: u.a

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rEHe1HA2-1662087958901)(./_pic/image-20220902051935525.png)]

​ 打印 u.a 直接点进去 看到这个绿色的光 这就是导出方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FIWYR5lP-1662087958902)(./_pic/image-20220902052446287.png)]

打上断点执行到这里,那这个就是生成加密的方法了,我们再重复运行两次试试验证猜想。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KQxrewAH-1662087958902)(./_pic/image-20220902052821114.png)]

​ 我们继续跟试试看,这里new了一个 I 对象,我们进入这个I方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V7OzsbBo-1662087958903)(./_pic/image-20220902053211353.png)]

​ 这里会循环几万甚至几十万次,加入了大量的无用代码和逻辑。并且如果不熟悉ast的coder不要轻易尝试。

​ 接下来通过Debugger断点调试jsvmp就不太可行了,套用下渔哥的解释,本文末尾有参考链接。

​ jsvmp就是将js源代码首先编译为字节码,得到的这种字节码就变成只有操作码(opcode)和操作数(Operands),这是其中一个前端代码的保护技术。

​ 整体架构流程是服务器端通过对JavaScript代码词法分析 -> 语法分析 -> 语法树->生成AST->生成私有指令->生成对应私有解释器,将私有指令加密与私有解释器发送给浏览器,就开始一边解释,一边执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EEYiSsxt-1662087958903)(./_pic/image-20220902053355451.png)]

​ 接下来就不继续通过Debugger调试了,既然可以在控制台通过调用 F®.encrypt)(f()(s) 来实现调用,这是一个webpack打包的项目那么我们找到它的加载器就可以调用,或者在这个模块内部没有如果通过加载器引用其它模块的话,把它直接变成全局的也是可以运行的。

​ 本次为了考虑到文章主要是算法还原,直接改成全局调用,有兴趣的可以通过加载器方法,不在此增加工作量了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ey3JoRVo-1662087958904)(./_pic/image-20220902055256425.png)]

​ 新的某乎增加环境检测,要打开知乎界面调用,不然会走错误流程。请求结果是403。

​ 掐头去尾留中间,删除这个匿名函数头部和尾部还有导出函数 exports,直接放到浏览器调用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-81QBtjbR-1662087958904)(./_pic/image-20220902060253402.png)]

​ 新手按箭头数字提示操作,老手直接跳过。

​ 出意外的报错了,我们点击报错点跟过去注释它。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6EqAPWts-1662087958905)(./_pic/image-20220902060844001.png)]

​ 运行成功,至此分析流程结束开始进入算法还原。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rBZ1JQDK-1662087958905)(./_pic/image-20220902061008400.png)]

3. 算法还原

​ 前面分析过得,l.prototype.O = function (A, C, s) { 在这个方法的大循环内生成算法,所以代码逻辑也肯定在这个里面,我们给它插桩打印日志看看。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-letQkpIJ-1662087958906)(./_pic/image-20220902063630797.png)]

​ 除了知道一个 S 看起来像时间戳之外,密密麻麻的基本没有有用信息。不过通过展开的case分支发现,频繁的出现了一个变量 this.C 和 this.C[this.c] 我们来打印它试试看。记得先清空之前的日志,在调用加密参数的前面加上 console.clear() 过滤掉生成算法之前走的其它初始化或无用逻辑。

​ 插桩如下代码到循环处

console.log(`索引--> case ${this.T}: this.C-->${this.C}, this.C[this.c]-->${this.C[this.c]}`)

​ 好消息我们看到了,this.C[this.c] 返回的结果和最后生成结果一致,证明我们的猜想是对的。全部复制到本地文本中分析。

​ 坏消息是密密麻麻的信息28000多行日志,我们从后往前来慢慢
在这里插入图片描述​ 可以看到加密在这里不一样。

Mh0gk2Mj76=d0Ccqp0v/F+0QSfx+V0SPzin6ig1fmzpTi8uQiDMrAQ81oT4FclX q
Mh0gk2Mj76=d0Ccqp0v/F+0QSfx+V0SPzin6ig1fmzpTi8uQiDMrAQ81oT4FclXq

​ 明显有个算法拼接的过程 ,很难让人想到不是 + 拼接的,为了印证在网上继续找不过是拿着下面的去搜

Mh0gk2Mj76=d0Ccqp0v/F+0QSfx+V0SPzin6ig1fmzpTi8uQiDMrAQ81oT4FclX

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iWahyJVs-1662087958907)(./_pic/image-20220902065320125.png)]

​ 排除常规的标准算法,我们事先通过老版本也得知这是一个自定义算法,不用去猜想是不是aes des 这种。

​ 和猜想的一样的就是通过慢慢拼接起来的,这种一般按照经验都是循环生成的。我们可以看到字符串长度是48,开发程序员不可能一个一个去拼接,肯定是遵循某一种规律或者特定条件循环的,所以我们不忙着扣代码,直接往上找。同时先记住这个case168 两次生成最后两位都是同一个case 分支 改变的。我们继续网上找,找到加密拼接的头部

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mqTvBNXB-1662087958907)(./_pic/image-20220902065625016.png)]

​ 我们直接搜算法开头的第一位M全匹配,可以看到case 168 和 465 都有可能是重点位置,为什么不是case 352呢?

​ 还记得开始的插桩

console.log(`索引--> case ${this.T}: this.C-->${this.C}, this.C[this.c]-->${this.C[this.c]}`)

​ 它是在 switch (this.T) 上面, this.C 的值 是上一次给 有效代码运算过后给 this.C 的有效赋值。我们去 case 168 下插桩试试看

                case 168:
                    console.log(`case ${this.T}: this.C[${this.c}] = ${this.C[this.I]} + ${this.C[this.F]} --> ${this.C[this.I] + this.C[this.F]}`)
                    this.C[this.c] = this.C[this.I] + this.C[this.F];
                    this.T = 2 * this.T + 16;
                    break;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NL87iDCZ-1662087958908)(./_pic/image-20220902070604165.png)]

​ 和猜想的一模一样,是通过拼接完成的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FrddOXvx-1662087958908)(_pic/image-20220902072717982.png)]

​ 加密第一位也是如此 我们再去看看465是否也是如此。

                case 465:
                    this.C[3] = this.C[this.W][Q](G[+[]]);
                    console.log(`case ${this.T}: this.C[3] = ${this.C[this.W]}.${Q}(${G[+[]]}) --> ${this.C[3]}`)
                    this.T -= 13 * G.length + 100;
                    break;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9UT09qCB-1662087958908)(_pic/image-20220902072955253.png)]

​ case 465 果然也是如此, 那我们依法炮制。按照上面这种方法搜索然后在生成出插桩

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bjfXR4el-1662087958909)(_pic/image-20220902073538632.png)]

​ 重复步骤不在截图。依次顺序是

//  加密值字符串拼接 BsdBVlB5vVIR=TbdMQh2skhsHK4scwNOWSamRia2YOaH+LCWTlURM4I/XKjQsHM + c
168,
//  生成拼接的加密值 6fpLRqJO8M/c3jnYxFkUVC4ZIG12SiH=5v0mXDazWBTsuw7QetbKdoPyAl+hN9rgE.charAt(11) --> c
465,
//  生成 11 & 63 --> 11  ========> 63 定值
78,
//  生成 2922411 >>> 18 --> 11  ========> 0 6 12 18 定值
57,
//  生成 38827 | 2883584 --> 2922411
50,
//  生成参数2 44 << 16 --> 2883584   ========> 44 是数组的值 16 定值
64,

​ 完成了以上步骤之后就可以从下往上拿到一个很长的数组,但是中间无用代码太多。

​ 我们从上往下看大概可以猜到这个数组是通过运算得到的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pMmqScN3-1662087958909)(_pic/image-20220902080043559.png)]

​ 但是依然无法清晰的看到整个加密过程,因为还有很多插桩没有完整。我们给它整个补完,又是一个漫长的过程,注释掉无用的逻辑比如 352 368 这种。拿到一份完全逻辑清晰可见的日志。

解压之后 打开 日志6.log

​ 可以看到完整的环境检测,运算逻辑。

​ case 368那里做了一个校验,只在debugger住并且时间超过500毫秒才会生效,估计是走向错误分支,我们只需要固定住就可以。但是我们都是分析日志所以并没有触发这个条件。

       case 368:
                    // this.T -= 500 < S - this.a ? 24 : 8;
                    this.T -= 8;
                    break;

​ 接着我们研究研究加密为什么会变得问题。通过两次运行比对日志发现了,算法为何会变得原因。

​ 生成了一个随机数,然后取整把他unshift到了数组的开头参与了运算。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ahuXJJGu-1662087958911)(_pic/image-20220902090633326.png)]

​ 有意思的是时间戳并没有参与运算,为了方便调试我们还是写死。

​ 如果想用我这份环境调试可以固定 md5 和时间戳 随机数即可用我这份代码日志分析比对,每一行都会相同。

md5 f1fa96c714c6752f28b162fda60ded03
Date.now 1661986251253
Math.random 0.08636862211354912

​ 接下来进行最后的算法还原。

1 - 142 之前都是环境检测 我们直接跳过。

143 - 271 取md5字符串长度 循环每一位 charCodeAt(i) 压入数组

// 定义一个空数组
var md5_charCodeAt_arr = []
// md5 转 charCodeAt 存放到数组
for (let i = 0; i < md5_str.length; i++) {
    md5_charCodeAt_arr.push(md5_str.charCodeAt(i))
}

272 - 281 继续压入 通过两次对比发现时间戳不影响最后的计算结果

// 向数组开头添加一个新的元素 0
md5_charCodeAt_arr.unshift(0)
// 向数组开头添加一个新的元素 17,也就是上面计算出来的 可随机可固定
md5_charCodeAt_arr.unshift(17)

283 - 310 通过两次对比发现 固定值循环14次.push(14)

// 往数组中放14个14,此时数组的长度为48
for (let i = 0; i < 14; i++) {
	md5_charCodeAt_arr.push(14)
}

311 - 393

// 改变md5 通过两次对比发现 分析发现 this.C[1] 是一个固定数组 48,53,57,48,53,51,102,55,100,49,53,101,48,49,100,55

// 这里取md5数组的 slice(0, 16) 然后用 this.C[0] 和 this.C[1] 数组每一位进行运算之后 ^ 42

394 - 401

// 我们给上面的结果定义一个变量不然下面容易看的人头晕 
// 283 - 310 md5_charCodeAt_arr 
// 311 - 393 new_md5_charCodeAt_arr
// 调用 __g.r 方法
var __g_r_res = __g.r(new_md5_charCodeAt_arr)
var md5_charCodeAt_arr2 = md5_charCodeAt_arr.slice(16, 48)
var __g_x_res = __g.x(md5_charCodeAt_arr2, __g_r_res);
// 拿到结果 下面会用这个长度48的数组进行大量的运算 这里不就是我们需要的那个数组
var in_calculation = __g_r_res.concat(__g_x_res)

404 - 407

// 通过两次对比发现 这是一个固定值可以写死 他是通过在变量中取出的值 拼接起来的,发现是固定值之后我没继续跟参数了
case 168: this.C[3] = 6fpLR + qJO8M/c3j --> 6fpLRqJO8M/c3j
case 168: this.C[3] = 6fpLRqJO8M/c3j + nYxFkUV --> 6fpLRqJO8M/c3jnYxFkUV
case 168: this.C[3] = 6fpLRqJO8M/c3jnYxFkUV + C4ZIG12SiH=5v0mXDazWB --> 6fpLRqJO8M/c3jnYxFkUVC4ZIG12SiH=5v0mXDazWB
case 168: this.C[3] = 6fpLRqJO8M/c3jnYxFkUVC4ZIG12SiH=5v0mXDazWB + Tsuw7QetbKdoPyAl+hN9rgE --> 6fpLRqJO8M/c3jnYxFkUVC4ZIG12SiH=5v0mXDazWBTsuw7QetbKdoPyAl+hN9rgE

​ 最后一步,还记得刚开始猜的循环吗,这里开始了。

​ 开始咯,下面就是又臭又长的分析流程,依然是通过两次对比流程发现其中的规律

​ 第一组 第二组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bDe2F1NH-1662087958911)(_pic/image-20220902044028834.png)]

​ 第三组 第四组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ezHohA7W-1662087958912)(_pic/image-20220902044127885.png)]

​ 大量的对比发现以下规律这是一个循环

​ 每次生成4个字符串,前面拿到的那个大数组会参与运算,从数组 pop() 一个出来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eO2rW7uQ-1662087958912)(_pic/image-20220902100155719.png)]

​ 通过这张整理过后的对比可以得出循环,已第一组数据为例

var i = 0; var pop = 211;
var c_3_1 = i % 4;
var c_3_2 = 8 * i;
var c_3_3 = 58 >>> c_3_2;
var c_3_4 = c_3_3 & 255;
var c_3_5 = pop ^ c_3_4

var a = c_3_5 // 这个值要参与运算 先保留起来
console.log("c_3_5", c_3_5);

i = 1; var pop = 74;
c_3_1 = i % 4;
c_3_2 = 8 * i;
c_3_3 = 58 >>> c_3_2;
c_3_4 = c_3_3 & 255;
c_3_5 = pop ^ c_3_4
var b1 = c_3_5 << 8 // 74 << 8 -- > 18944

console.log("c_3_5", c_3_5);
console.log("b1", b1);
var a1 = a | b1 // 233 | 18944 -- > 19177
console.log("a1", a1);


i = 2; var pop = 167;
c_3_1 = i % 4;
c_3_2 = 8 * i;
c_3_3 = 58 >>> c_3_2;
c_3_4 = c_3_3 & 255;
c_3_5 = pop ^ c_3_4
console.log("c_3_5", c_3_5);


var c = c_3_5 << 16 // 10944512
console.log("c", c);
var d = a1 | c; // 10963689
console.log("d", d);

console.log(encode(d));
// 10963689 = > BsdB
function encode(param) {
    var salt = '6fpLRqJO8M/c3jnYxFkUVC4ZIG12SiH=5v0mXDazWBTsuw7QetbKdoPyAl+hN9rgE'
    let ret = ''
    // 这里对应点在 case 57
    for (x of [0, 6, 12, 18]) {
        let a = param >>> x
        let b = a & 63
        let c = salt.charAt(b)
        ret = ret + c
    }
    // console.log(ret)
    return ret
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RU8Qsijk-1662087958912)(_pic/image-20220902102930503.png)]

然后我们把他封装成一个函数 请求一下试试看

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EfrntQG8-1662087958913)(_pic/image-20220902103324652.png)]

​ 完全成功。(__)

​ 最后闲谈:讲道理还原纯算是比较浪费时间成本的,像这样已经做过一次知道流程的情况下,整理笔记资料和截图,完全搞清楚每个分支干什么了,写完这篇文章差不多花了10个小时。这种方案对抗vmp显然是不太划算的,相比较而言补环境应该是一个不错的选择。可能大佬都喜欢手撕vmp的快感吧,不过作为学习技术本身就是为了吃懂吃透,为了更好的对抗vmp,实际生产业务肯定还是以最快完成为主。一个练手demo还原纯算的话分析加上还原还是参考其他文章都需要花费1-2天,可能还是太菜了,

最后推荐一下蔡老板的星球,好用不贵。
在这里插入图片描述

参考文章:

蔡老板 vip群 : ) 佬 的 demo

渔滒 - 【JS逆向系列】某乎x96参数与jsvmp初体验

时光依旧不在 - js逆向JSVMP篇新版某乎_x-zes-96算法还原

相关文章:

  • 迅速了解JDK线程池以及Spring线程池
  • 前缀和与查分(一维前缀和,二维前缀和(子矩阵的和)一维差分、二维差分(差分矩阵))
  • 2022年是SEO行业凋谢的一年
  • CDR插件开发之Addon插件006 - 初体验:通过C#代码用外挂方式操作CDR中的对象
  • 【2020.09.01】 新学期,新气象
  • 基于云计算与深度学习的常见作物害虫识别系统的设计与实现
  • Flask 学习-22.可插拨视图MethodView类
  • 微信公众号如何获取查题搜题功能接口
  • 聚醋酸乙烯酯接枝聚苯乙烯PVAc-g-PSt微球/接枝-聚甲基丙烯酸甲酯表面(PS-acyl-Cl)的研究
  • 百度网盘的音乐怎么分享到qq音乐里?
  • Q_PLUGIN_METADATA
  • 【Java初阶】面向对象三大特性之继承
  • 标签传播算法(LPA)
  • ElasticSearch(版本7.8.1)中类型Long精度缺失
  • “两利四率” 、“两增一控三提高” 央企考核指标解读
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • es6要点
  • REST架构的思考
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • Vue实战(四)登录/注册页的实现
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 高性能JavaScript阅读简记(三)
  • 机器学习 vs. 深度学习
  • 批量截取pdf文件
  • 适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr 屏幕尺寸及安全区域
  • 算法之不定期更新(一)(2018-04-12)
  • 想写好前端,先练好内功
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • # 数据结构
  • #AngularJS#$sce.trustAsResourceUrl
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • $ is not function   和JQUERY 命名 冲突的解说 Jquer问题 (
  • (js)循环条件满足时终止循环
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (附源码)计算机毕业设计大学生兼职系统
  • (十八)SpringBoot之发送QQ邮件
  • (五)Python 垃圾回收机制
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .NET/C# 中设置当发生某个特定异常时进入断点(不借助 Visual Studio 的纯代码实现)
  • .net快速开发框架源码分享
  • .net生成的类,跨工程调用显示注释
  • 。Net下Windows服务程序开发疑惑
  • ??javascript里的变量问题
  • @Conditional注解详解
  • @converter 只能用mysql吗_python-MySQLConverter对象没有mysql-connector属性’...
  • @德人合科技——天锐绿盾 | 图纸加密软件有哪些功能呢?
  • [ C++ ] STL---仿函数与priority_queue
  • [Asp.net MVC]Asp.net MVC5系列——Razor语法
  • [BUUCTF NewStarCTF 2023 公开赛道] week4 crypto/pwn
  • [Codeforces] number theory (R1600) Part.11
  • [DAX] MAX函数 | MAXX函数