操作符混淆工具
- 2019 年 12 月 4 日
- 筆記
1 序
1.1 从一段神奇的JS代码说起
前段时间在公众号看到一段神奇的代码,它长这个样子:
(!(~+[])+{})[--[~[]][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]]*~+[]]
如果把这行代码扔到浏览器里面执行以下,就会输出sb
字符串。

这是什么鬼,还有这种操作?
1.2 走进科学时间
上面的代码由!()*+-[]{}~
这11种符号组成,其实这些符号都是JS的操作符
,而上面的代码在执行后转换成字符串则是因为:
- 当操作符作用的操作数类型不一致或者不是基本类型时,JS将自动完成类型转化;
- 不同的操作符具有不同的优先级
将上面的代码按照 操作符优先级 进行区域划分,大致可以分为以下的几个部分。

可以看到实际上就是应用JS的类型隐式转换生成字符串,然后从字符串里提取想要的字符。 至于为什么上图的叶节点为什么是这样生成的值,请参照 es5.github.io/ 9 Type Conversion and Testing
2 操作符代码混淆器
收到前文的启发,本人萌发了一种“操作符代码混淆器”的想法。也就是利用上文提及的原理,将JS代码混淆成全部由操作符组成的“让人看着头疼的代码”。
这意味着一些简单的字符层面上的代码注入防范工作完全无法对我们的代码生效
,因为我们的代码完全由“操作符”构成,根本就不包含敏感关键字。
"操作符代码混淆器"需要解决几个关键性的问题:
- 操作符生成其他字符
- 字符串组装成可执行代码
2.1 数字
生成数字实际上只要有一个数字0,我们完成可以通过自增操作符++
生成数字1-9,所以我们只需要
// '数字集合' $ = +[]; // 0 $ = { _: $++, // 0 __: $++, // 1 ___: $++, // 2 ____: $++, // 3 _____: $++, // 4 $_: $++, // 5 $__: $++, // 6 $___: $++, // 7 $____: $++, // 8 $_____: $++, // 9 }
2.2 英文字符
从前文我们可以知道其逻辑是根据不同的类型转换成字符串时可以生成不同的描述性字符串
{} + [] // '[object Object]' ![] +[] // 'false' !![] + [] // 'true' [][$._] + [] //'undefined'
从上面我们可以得到 a b c d e f i j l n o r s t u
这些字符。但是这很明显没办法包含所有英文字符,同时也没办法表示换行符等特殊字符。
2.3 通用字符
所以我们需要一个更加通用的方案来通过操作符生成其他字符。 基于我们现在已经得到的数字字符,我们可以使用八进制的表示方式来生成其他ASCII字符。
'\' + $.__ + $._____ + $.$__ + '\' // '147' 即 g '\' + $.__ + $.$ + $._ // '150' 即 h , 以此类推
但是这也随之带来一个问题,那就是我们为了使用八进制来表达ASCII字符,引入了'\'
这种常量字符串,这使得整个计划优点不完美,但是作者目前没有想到更好的实现方式。
// '字符集合' $$ = '\'; $$ = { _: $$ + $.__ + $._____ + $.__, // 141 a __: $$ + $.__ + $._____ + $.___, // 142 b ___: $$ + $.__ + $._____ + $.____, // 143 c ____: $$ + $.__ + $._____ + $._____, // 144 d _____: $$ + $.__ + $._____ + $.$_, // 145 e $_: $$ + $.__ + $._____ + $.$__, // 146 f $__: $$ + $.__ + $._____ + $.$___, // 147 g $___: $$ + $.__ + $.$_ + $._, // 150 h $____: $$ + $.__ + $.$_ + $.__, // 151 i $_____: $$ + $.__ + $.$_ + $.___, // 152 j $$_: $$ + $.__ + $.$_ + $.____, // 153 k $$__: $$ + $.__ + $.$_ + $._____, // 154 l $$___: $$ + $.__ + $.$_ + $.$_, // 155 m $$____: $$ + $.__ + $.$_ + $.$__, // 156 n $$_____: $$ + $.__ + $.$_ + $.$___, // 157 o $$$_: $$ + $.__ + $.$__ + $._, // 160 p $$$__: $$ + $.__ + $.$__ + $.__, // 161 q $$$___: $$ + $.__ + $.$__ + $.___, // 162 r $$$____: $$ + $.__ + $.$__ + $.____, // 163 s $$$_____: $$ + $.__ + $.$__ + $._____, // 164 t $$$$_: $$ + $.__ + $.$__ + $.$_, // 165 u $$$$__: $$ + $.__ + $.$__ + $.$__, // 166 v $$$$___: $$ + $.__ + $.$__ + $.$___, // 167 w $$$$____: $$ + $.__ + $.$___ + $._, // 170 x $$$$_____: $$ + $.__ + $.$___ + $.__, // 171 y $$$$$_: $$ + $.__ + $.$___ + $.___ // 172 z }
2.4 执行容器
前文我们只是将代码内容转换成了字符串的形式,这么一个字符串还需要能够跑起来。 好,我们的第一想法可能是eval
方法
eval() 函数可计算某个字符串,并执行其中的的 JavaScript代码。
假设我们已经将代码转换成了字符串,但是下面的用户调用方式未免显得太过没有逼格。
var codeStr = '混淆过的代码'; // 用户调用 eval(codeStr)
所以我们的目标是生成的代码用户可以直接扔到浏览器里面开始执行,即我们需要一个可以执行的函数容器:
// 字符串集合 $$$ = { _: {} + [], // '[object Object]' __: ![] + [], // 'false' ___: !![] + [], // 'true' ____: [][$._] + [] //'undefined' } // 替换字符集, 这里不替换的话无法根据constructor找到Function $$.___ = $$$._[$.$_]; // c $$._____ = $$$.___[$.____] // e $$.$$____ = $$$.____[$.__]; //n $$.$$_____ = $$$._[$.__]; // o $$.$$$___ = $$$.___[$.__]; // r $$.$$$____ = $$$.__[$.____]; // s $$.$$$_____ = $$$.___[$._]; // t $$.$$$$_ = $$$.____[$._]; // u $$$._____ = $$.___ + $$.$$_____ + $$.$$____ + $$.$$$____ + $$.$$$_____ + $$.$$$___ + $$.$$$$_ + $$.___ + $$.$$$_____ + $$.$$_____ + $$.$$$___; // 'constructor' $$$.$_ = $$.$$$___ + $$._____ + $$.$$$_____ + $$.$$$$_ + $$.$$$___ + $$.$$____; // 'return' $$$.$__ = ($._)[$$$$._][$$$$._] // Function // 执行容器调用方式 $$$.$__($$$.$__($$$.$_ + '"' + '这里是经过混淆后的代码' + '"'())(); // 实际上就是 // Function(Function()('return "' + '这里是经过混淆后的代码' + '"')())()
3 结论
通过以上实现,基本实现了一个简单代码混淆工具的逻辑,可以只使用操作符对代码进行混淆,但依旧遗留了一些问题
- 代码依赖字符串,生成的代码也会包含字符串常量,并不是完全的“操作符化”;
- 工具的代码本身很难阅读,使得维护和开发非常困难,这个工作可以依赖构建工具进行优化;
- 目前只包含了对ASCII字符的处理,对字符集以外字符的处理是有问题的;
- 本工具的应用场景具有局限性
4 相关资料
一段神奇的javascript代码 运算符优先级 Annotated ECMAScript 5.1 – 9 Type Conversion and Testing jjencode JS代码加密混淆工具 jjencode