前段时间在公众号看到一段神奇的代码,它长这个样子:
(!(~+[])+{})[--[~[]][+[]]*[~+[]]+~~!+[]]+({}+[])[[~!+[]]*~+[]]
如果把这行代码扔到浏览器里面执行以下,就会输出sb
字符串。
这是什么鬼,还有这种操作?
上面的代码由!()*+-[]{}~
这11种符号组成,其实这些符号都是JS的操作符
,而上面的代码在执行后转换成字符串则是因为:
将上面的代码按照 操作符优先级 进行区域划分,大致可以分为以下的几个部分。
可以看到实际上就是应用JS的类型隐式转换生成字符串,然后从字符串里提取想要的字符。 至于为什么上图的叶节点为什么是这样生成的值,请参照 es5.github.io/ 9 Type Conversion and Testing
收到前文的启发,本人萌发了一种“操作符代码混淆器”的想法。也就是利用上文提及的原理,将JS代码混淆成全部由操作符组成的“让人看着头疼的代码”。
这意味着一些简单的字符层面上的代码注入防范工作完全无法对我们的代码生效
,因为我们的代码完全由“操作符”构成,根本就不包含敏感关键字。
"操作符代码混淆器"需要解决几个关键性的问题:
生成数字实际上只要有一个数字0,我们完成可以通过自增操作符++
生成数字1-9,所以我们只需要
// '数字集合'
$ = +[]; // 0
$ = {
_: $++, // 0
__: $++, // 1
___: $++, // 2
____: $++, // 3
_____: $++, // 4
$_: $++, // 5
$__: $++, // 6
$___: $++, // 7
$____: $++, // 8
$_____: $++, // 9
}
从前文我们可以知道其逻辑是根据不同的类型转换成字符串时可以生成不同的描述性字符串
{} + [] // '[object Object]'
![] +[] // 'false'
!![] + [] // 'true'
[][$._] + [] //'undefined'
从上面我们可以得到 a b c d e f i j l n o r s t u
这些字符。但是这很明显没办法包含所有英文字符,同时也没办法表示换行符等特殊字符。
所以我们需要一个更加通用的方案来通过操作符生成其他字符。 基于我们现在已经得到的数字字符,我们可以使用八进制的表示方式来生成其他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
}
前文我们只是将代码内容转换成了字符串的形式,这么一个字符串还需要能够跑起来。
好,我们的第一想法可能是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 \"' + '这里是经过混淆后的代码' + '\"')())()
通过以上实现,基本实现了一个简单代码混淆工具的逻辑,可以只使用操作符对代码进行混淆,但依旧遗留了一些问题
一段神奇的javascript代码 运算符优先级 Annotated ECMAScript 5.1 - 9 Type Conversion and Testing jjencode JS代码加密混淆工具 jjencode