吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 234|回复: 1
上一主题 下一主题
收起左侧

[Web逆向] JS 盾(JS DUN PROTECT)加固小记

[复制链接]
跳转到指定楼层
楼主
rsds0duck 发表于 2026-6-23 19:44 回帖奖励

JS 盾(JS DUN PROTECT)加固小记

JS 盾是猿人学自主开发的 JSVMP 加固方案,国内 JSVMP 难度算 T0 级。今天下午闲来无事看了看,感觉还挺有意思的,在它身上能看见历代先贤的影子。也有一些非常创新的设计,比如后面会提到的动态代码构造(可惜最终还是没搞到)

我做的事情非常简单,就把下面这段代码丢进去加固了一下:

function myScript(){
  console.log("把要加固的代码粘贴到框框里,剩下的就交给我们吧")
};
myScript();

膨胀出来的文件巨大,字节码很多,调用链相当杂乱。不过由于源码简单,加固产物不会难上天际,正好拿来摸清整体结构。


工具和初步观感

扣了一个小时后发现,这个加固不能按之前阿卡迈那种细节扣法来搞。得先通读一遍,理解整体结构,然后才能逐块分析。

工具方面,VSCode 直接报废——括号全乱了,格式化也不可信,甚至会报错。最后用 Vim 手动看的。搞完 AKM 以后有个经验:对于混淆严重的代码,一定要先看括号闭合状态,看的是结构而不是变量名。


整体结构

整个文件是一个 eval 包裹的立即执行函数:

eval(function r(n, Z) {
    var r = "JS DUN PROTECT", e, k, y, t, f, ...;
    return (((((...) && (...) || (...)) && ...));
}(...))

eval 里面的函数 r 接收两个参数 n(字节码)和 Z(始终为 undefined,用于短路判断)。整个函数体就是一条 return 语句,用 && / || 短路运算串成的超长表达式链。

这条链按执行顺序做了三件事:

  1. 初始化阶段:声明变量,缓存原生方法引用(Math.sinJSON.stringifyString.fromCharCode 等)
  2. 字节码解码 + 第一轮 VM 执行:解码一个巨大的编码数组,传入 VM 解释器 p(即函数 X)执行
  3. 第二轮 VM 执行:重置计数器,再次调用 p,这次传入代码生成器相关的 handler 函数

第二轮和第三轮之间的衔接点长这样:

前面是超长字节码数据 "H77"]))) || 1) && (T = 0) && 0 || (V = p(Q, 后面跟N个函数))

拆开看:

  • ]))) 关闭字节码数组和第一轮 p(...) 调用
  • || 1) 无论第一轮返回什么都为 truthy
  • && (T = 0) && 0 重置计数器 T,然后故意让左侧变 falsy
  • || (V = p(Q, ...)) 触发第二轮 VM 调用,结果赋给 V

这种 && 0 || expr 的写法在整个文件里到处都是,本质就是「先执行副作用,再跳到下一个表达式」,相当于把顺序语句伪装成了一条表达式。


VM 解释器

VM 主体是函数 X(在代码里别名为 p),它是一个标准的 while + switch 字节码解释器,不过 switch 被 if-else 链和短路运算替代了,混淆得几乎没法直接看。

VM 维护的核心状态:

  • 操作数栈 y:所有运算通过栈操作完成
  • 字节码数组 r + 程序计数器 n:逐条读取并执行
  • 作用域链 e:嵌套数组,模拟 JS 的词法作用域

指令集大约 50+ 条 opcode(0~54),覆盖了算术运算、比较、位运算、属性访问、函数调用、异常处理等。举几个例子:

opcode 操作
2 加载立即数(从字节码读取常量压栈)
6 加法(弹出两个值相加,结果压栈)
5 减法
0 属性访问(obj[key]
15 创建函数(闭包)
19 函数调用(支持 call / apply
20 返回(终止 VM 循环)

核心设计:X.$ 虚拟方法表

这是我觉得最有意思的部分,也是我目前没见别家用过的手法。

X.$ = [
    X.$ = "1.1",
    X.apply[X.$] = X.call[X.$] = X.call,
    X.apply,
    [].push,
    [].pop,
    [].concat,
    [].slice,
    X.bind,
    function (r, n, Z, e, k) {
        return 7 == r ?
            X.$[1][X.$[0]](X.$[3], n, Z, e, k) : 2 == r ?
            X.$[1][X.$[0]](X.$[r], n, Z, e) :
            X.$[1][X.$[0]](X.$[r], n, Z)
    }
]

这三行代码构造了一个数组,充当 VM 的操作原语表(vtable):

index 含义
0 "1.1" 属性名 key
1 Function.prototype.call call 间接引用
2 Function.prototype.apply apply
3 Array.prototype.push 压栈
4 Array.prototype.pop 弹栈
5 Array.prototype.concat 数组合并
6 Array.prototype.slice 数组截取
7 Function.prototype.bind 绑定
8 dispatcher 函数 统一调度入口

第一层:函数对象属性污染

数组构造过程中有个前置副作用:

X.$ = "1.1"  // 先赋值字符串
X.call["1.1"] = X.call  // 给 Function.prototype.call 挂了个 "1.1" 属性,指向自身
X.apply["1.1"] = X.call // 同理

JS 函数本质是对象,可以挂任意属性。混淆利用这一点,用字符串 key 隐藏真实的函数引用。

第二层:双层 call 跳板

调度器里的核心表达式:

X.$[1][X.$[0]](func, thisArg, ...args)

展开执行过程:

X.$[1]          → Function.prototype.call
X.$[0]          → "1.1"
X.$[1]["1.1"]   → Function.prototype.call(刚才挂上去的属性)

调用 call,this 指向 call 本身
→ call.call(func, thisArg, ...args)
→ func.call(thisArg, ...args)

两层 .call 绕了一圈,本质就是 func.call(thisArg, ...args)。但静态分析工具看到的是 X.$[1][X.$[0]](...),完全没法推断出实际调用了什么。

第三层:opcode 化分发

调度器通过参数 r 选择目标函数:

X.$[8](3, arr, val)       → push.call(arr, val)       // 压栈
X.$[8](4, arr)            → pop.call(arr)             // 弹栈
X.$[8](5, arr1, arr2)     → concat.call(arr1, arr2)   // 合并
X.$[8](6, arr, n)         → slice.call(arr, n)        // 截取
X.$[8](7, arr, a, b, c)   → push.call(arr, a, b, c)  // 多值压栈
X.$[8](2, fn, ctx, args)  → fn.apply(ctx, args)       // 函数调用

r 不是普通参数,而是函数 opcode。整个 VM 的栈操作和函数调用全部收敛到 X.$[8] 这一个入口。

这种设计巧妙在哪

破坏 AST 可读性:没有任何直接函数调用,全部变成数组索引访问加间接 call,AST 分析工具基本废了。

统一执行入口:所有底层操作走一个调度器,方便做 hook 检测。如果有人 hook 了 pushcall,调度器层面可以感知到。

opcode 化:JS 的原生方法被编号成指令,跟 VM 的字节码指令集在同一个抽象层级,整个执行流都在 VM 的控制之下。


后续

目前只摸清了 VM 的整体结构和 vtable 机制,代码生成那块还没深入。尝试了两个小时解混淆(把 && / || 链还原成正常语句),发现自己还是太菜了,短路混淆的自动化处理不太好写,但思路是有的,不至于死路一条。

值得庆幸的是 JS 盾目前还没大规模铺开。不确定购买定制化以后会不会加上类似 5s 那样的风控检测,不过对猿人学来说这应该不是什么难事。

等追到代码生成的完整链路再接着写,现在卡在代码格式化后被检测了,明天再看看怎么说吧,对比 Android 感觉还是简单许多,最起码不至于读超级碎片化的汇编

也欢迎各位来报猿人学的 JS 逆向课程,广告还是留给平哥吧,我就不打了
附件上不去,没招了,一直重试但是没用

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
buluo533 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
Infini 发表于 2026-6-23 23:02
前来参观
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-6-24 05:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表