文章目录

  • 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 找到入口,只有两个位置统统打上断点。

2. 分析流程

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

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

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

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

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

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


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

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

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

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

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

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

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

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

​ 新的某乎增加环境检测,要打开知乎界面调用,不然会走错误流程。请求结果是403。
​ 掐头去尾留中间,删除这个匿名函数头部和尾部还有导出函数 exports,直接放到浏览器调用。

​ 新手按箭头数字提示操作,老手直接跳过。
​ 出意外的报错了,我们点击报错点跟过去注释它。

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

3. 算法还原

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

​ 除了知道一个 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

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

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

​ 我们直接搜算法开头的第一位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;

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

​ 加密第一位也是如此 我们再去看看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;

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

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

//  加密值字符串拼接 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,

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

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

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

解压之后 打开 日志6.log

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

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

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

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

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

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

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

md5 f1fa96c714c6752f28b162fda60ded03
Date.now 1661986251253
Math.random 0.08636862211354912

​ 接下来进行最后的算法还原,用 日志6.log 这个日志记录来分析还原算法。

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

// 之后通过slice取0-16位
var md5_charCodeAt_arr1 = md5_charCodeAt_arr.slice(0, 16)
// md5_charCodeAt_arr1 -> 10, 0, 102, 49, 102, 97, 57, 54, 99, 55, 49, 52, 99, 54, 55, 53// 固定值
var charCodeAt_arr_1 = [48, 53, 57, 48, 53, 51, 102, 55, 100, 49, 53, 101, 48, 49, 100, 55];var new_md5_charCodeAt_arr = [];
for (var key in md5_charCodeAt_arr1) {new_md5_charCodeAt_arr.push(md5_charCodeAt_arr1[key] ^ charCodeAt_arr_1[key] ^ 42);
}
// 因为是16位的数组,每个值都需要计算,所以相当于是分了16组,每组计算的结果都放入了一个新的数组中
// new_md5_charCodeAt_arr -> 16, 31, 117, 43, 121, 120, 117, 43, 45, 44, 46, 123, 121, 45, 121, 40

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

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

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

​ 第一组 第二组

​ 第三组 第四组

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

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

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

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_4var 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 -- > 18944console.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 57for (x of [0, 6, 12, 18]) {let a = param >>> xlet b = a & 63let c = salt.charAt(b)ret = ret + c}// console.log(ret)return ret
}

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

​ 完全成功 和日志6.log的一模一样

x_zse_96 2.0_BsdBVlB5vVIR=TbdMQh2skhsHK4scwNOWSamRia2YOaH+LCWTlURM4I/XKjQsHMc

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

最后推荐一下蔡老板的星球,好用不贵。

参考文章:

蔡老板 vip群 : ) 佬 的 demo

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

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

jsvmp-某乎 x-zes-96 算法还原相关推荐

  1. js逆向JSVMP篇新版某乎_x-zes-96算法还原

    提示!本文章仅供学习交流,严禁用于任何商业和非法用途,如有侵权,可联系本文作者删除! 前言   最近有人在jsvmp-某乎_x-zes-96参数算法还原(手把手教学)CSDN上的发布文章下说跟某乎的加 ...

  2. qq音乐sign算法还原源码放送及jsvmp全流程分析

    1.声明 本次分析过程仅限于学习使用,请勿用于非法用途,若读者用于非法用途其造成的一切后果与本人无关,若本文章侵犯了贵公司的权益请添加本人微信YotaGit联系删除 博客所写的所有算法还原均已开源在G ...

  3. app和web逆向算法还原案例源码分享

    1.前言 后续公众号将不再设置任何收费,只做算法还原源码分享并分享在github项目上 项目地址是:https://github.com/YotaGit/AlgorithmRestore 现已将之前公 ...

  4. 七麦数据analysis参数算法还原

    1. 前言 本次分析过程仅限于学习使用,请勿用于非法用途,若读者用于非法用途其造成的一切后果与本人无关 博客所写的所有算法还原均已开源在GitHub,地址 https://github.com/Yot ...

  5. 安卓逆向-马蜂窝zzzghostsigh算法还原--魔改的SHA-1

    模拟执行 unidbg调用的视频已出:unidbg调用马蜂窝zzzghostsigh算法 这篇文章主要讲解算法还原app version: 9.3.7加密值:muyang返回值:efa2ecf4644 ...

  6. 国家税务总局全国增值税发票查验平台网站js逆向分析及全逆向算法还原

    本文教程针对的是2021年7月2日时国税查验平台的js分析,其中版本号为V2.0.06_009.主要分析内容为key9和flwq39以及fplx这3个参数的算法,其中key9分为获取验证码阶段和查验阶 ...

  7. 巨量星图sign算法还原(头条系)

    1.郑重声明 本次分析过程仅限于学习使用,请勿用于非法用途,若读者用于非法用途其造成的一切后果与本人无关 博客所写的所有算法还原均已开源在GitHub,地址 https://github.com/Yo ...

  8. 瑞数vmp算法还原流程讲解

    提示!本文章仅供学习交流,严禁用于任何商业和非法用途,如有侵权,可联系本文作者删除! 前言 ​ 又是很久没写文章了,今天水一篇文章吧,鉴于之前有看到过别人的文章被警告的案例,所以这篇文章就仅写一下还原 ...

  9. 【JS逆向系列】某空气质量监测平台无限 debugger 与 python算法还原

    [JS逆向系列]某空气质量监测平台无限 debugger 与 python算法还原 1.前置阅读 2.过反调试 3.js分析 4.代码逻辑改写 1.前置阅读 样品地址:aHR0cHM6Ly93d3cu ...

最新文章

  1. 【Docker】registry部署docker私有镜像仓库
  2. 民营企业的项目,真的很难做
  3. elasticsearch 根据条件去除重复值_Excel工作表中的条件格式,不只是查找重复值,还有7种典型用法...
  4. HDU 3709 Balanced Number (数位DP)
  5. 1007. Maximum Subsequence Sum (25)
  6. 【HDU - 5988】Coding Contest(网络流费用流,改模板)
  7. Linux使用cpuset设置CPU独占
  8. 洛谷——P1219 [USACO1.5]八皇后 Checker Challenge
  9. 同比和与环比的计算公式
  10. Python学习笔记-2017.5.4thon学习笔记-2017.5.19
  11. MongoDB分组查询数据库sql
  12. 《策略投资》第1、2章读书分享
  13. 补天漏洞平台:让更多的白帽子脱离黑产
  14. PTA_数据结构与算法_7-38 寻找大富翁 (25分)
  15. #420 Div2 Problem B Okabe and Banana Trees (math 暴力枚举)
  16. 信息系统集成考试中pv,ev,ac相关概念及运算
  17. 如何防止Access数据库被下载- -
  18. 高德地图-添加一个或多个覆盖物
  19. 无线智能灌溉系统功能
  20. toad for mysql 彻底卸载_toad for mysql 下载

热门文章

  1. 全球与中国市场聚乙烯醇缩丁醛(PVB)树脂发展规模分析与前景战略研究报告2022年版
  2. 毕业一年感想~微思顾轻展望
  3. 天源财富:突破“极限”!我科学家发现迄今最高能量光子
  4. ::细细品味ASP.NET (二)::
  5. 【案例分享】沃尔沃FMX V4 Euro 6 燃油压力故障诊断
  6. 理解ALSA(二):概览
  7. GWO(灰狼优化)算法MATLAB源码逐行中文注解()
  8. Category 类别 -Objective-C
  9. 集成电路工艺基础介绍以及什么是Corner?
  10. java2d游戏代码_Java 2d游戏中的“JUMP”