前一篇文章介绍的aaencode更多的是因为趣味性而关注,而这篇文章介绍的JSF*ck则是因其明显的特征。
01
JSF*ck混淆
1.1
构造
日本安全研究人员长谷川阳介
制作了名为JSF*ck
的编码器,能够将所有JS代码用6个字符(分别是[
、]
、(
、)
、!
、+
)来表示(称之为用6个字符来混淆JavaScript)
构造数字
数字0
使用+[]
来构造,[]
代表空数组、而+
是一元加运算符
数字1
使用+!![]
或+!+[]
来构造,其中!![]
和!+[]
代表布尔值true
,前置一元加运算符将真值true
转换为1
数字2
通过真值的相加获得,true+true
这一表达式在JS中输出结果为2
,而true
可以写作!![]
或!+[]
,所以2
可以表示为!![]+!![]
或!+[]+!+[]
数字3
到9
方法相同
多位数字通过字符串串接得到,如10
,不采用10个true
相加,而是先得到字符串"10"
;字符串"10"
可以表达为两个数组的串接形式[1]+[0]
,而将其中的数字替换为JSF*ck,就有了[+!+[]]+[+[]]
;有了字符串"10"
后,在JS中将数字型字符串转换为数值,只需在字符串括在"括号"或"方括号"中,并加上一个+
运算符,最终10
的JSF*ck表达为+([+!+[]]+[+[]])
构造字母
像[]+[]+[![]] = "false"
能够得到字符串"false"
一样,通过下标"false"[0]
就能够得到字母"f"
,将下标的数字0
换成JSF*ck,那么"f"
就能表示成([]+[]+[![]])[+![]]
除了![] = false
外,内置的可以利用的字符串还有!![] = true
、+[![]] = NaN
、[][[]] = undefined
,以及+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]]) = Infinity
↑ 上面的
+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])
是将字符串"1e1000"
转换成数字得到的
将上面的内置显示单词转换为字符串,再用索引提取,能够构造部分字母
构造函数
"alert(1)"
,却发现它并不能像普通的JS代码那样执行——因为这里的"alert(1)"
是字符串,而不是语句[]["filter"]["constructor"]("alert(1)")()
等价于语句alert(1)
"filter"
、"constructor"
和"alert"
,将"alert()"
从字符串变为执行语句alert()
解释 语句 alert(1)
等价于Function("alert(1)")()
,Function
是类似其它语言中的eval
函数,用于触发执行包含在字符串中的JavaScript代码而 Function
是JS中任何常用函数的constructor
属性,这里的常用函数是指[]["filter"]
(即Array.prototype.filter)之类的函数);访问一个空数组的filter
属性下的constructor
属性,就能够构造出一个Function
这也就是为什么语句 alert(1)
能被表示成[]["filter"]["constructor"]("alert(1)")()
的原因
构造字母(2)
false
、true
、NaN
、undefined
、Infinity
,除了这几个比较容易得到的字符串之外,还有其它"冷门"的方法[]["find"] + []
会得到字符串"function find() { [native code] }"
原理是空数组[]
拥有许多方法和属性,像访问Python的字典元素那样访问[]
的find
方法,虽然不能直接执行,但加上一个[]
将find
强制转换为字符串,就会显示出数组find()
方法的一些信息
"find"
能够从"undefined"
中提取,执行[]["find"] + []
后就多出了几个字符,如o
、{
等
[]["entries"]() + []
会得到字符串"[object Array Iterator]"
能够得到新的字符j
、A
等
......
1.2
JSF*ck评价
JSF*ck的实现得益于JS本身的坑爹性,如:
[]
前置一元加运算符+
变成0
,前置一元布尔运算符!
变成false
;如!![] = true
true
或false
前置一元加运算符+
能变成对应的1
或0
;结合上一条,就有+!![] = 1
[1,2]+[3] = "1,23"
,你没看错,连分隔符,
都在;还可以利用[]+[] = ""
得到空字符串,然后用空字符串与布尔值false
相加,得到"false"
你也就能理解为什么长谷川阳介
先生将它命名为JSF*ck而不是JSGood了
回到前面,我们的 alert("Aurora")
可以改写成 []["filter"]["constructor"]("alert('Aurora')")()
,语句转换成字符串后,我们可以借助JSF*ck的思路来进一步混淆代码
例如,字符 a
可以取自布尔值 false
中,所以先构造出字符串 "false"
:[]+[]+[![]]
,再构造出下标 1
:+!![]
,两者结合,得到 ([]+[]+[![]])[+!![]]
将上面这串
([]+[]+[![]])[+!![]]
扔入浏览器的Console中,是可以看到为字符串"a"
的
然后把代码 []["filter"]["constructor"]("alert('Aurora')")()
中的 "a"
用这串字符串代替掉一个,就有了:
[]["filter"]["constructor"]((([]+[]+[![]])[+!![]]) + "lert('Aurora')")()
可以想象,未接触过混淆的人见到代码中的这串 ([]+[]+[![]])[+!![]]
是很懵的,然而执行它却能输出 Aurora
把代码中的所有字符都这样替代,就完成了混淆
以上就是JSF*ck的大致实现思路,了解学习一下混淆的概念
正是因为混淆,JSF*ck代码能够被浏览器直接运行,相当于等价的JavaScript代码
如果想深入了解JSF*ck的全部实现过程,可以去作者的Github上看,有十分完整的过程,作者的jsF*ck.js文件就罗列出了所有字符的表示法
【本部分参考】
JSF*ck Wiki https://zh.wikipedia.org/wiki/JSFuck
JSF*ck作者Github https://github.com/aemkei/jsFuck
02
decode
2.1
.toString()
将混淆后的代码最后表示函数调用的 ()
更改为 .toString()
,可以知道其代码内容:
2.2
hook function constructor
Function.prototype.__defineGetter__('constructor', function() {
return function(...args) {
console.log('code:', ...args);
return Function(...args);
};
});
运行上面的JS代码,修改较底层的一些设置,然后再执行混淆过的JS代码,能够输出其内容:
2.3
在线帮助
大部分JS混淆都已经广为人知,有许多人找寻对应的decode方法,比如:
考验你的信息检索能力了
以及上篇提到的手工计算
03
混淆的意义
正面的
JavaScript的代码由于需要执行,会直接呈现在前端中,公开透明,为了避免被恶意获取、修改,对其进行混淆可以加大阅读代码的难度
当理解代码逻辑的成本超过了一定的限度,就能劝退许多人
负面的
代码量增多
非加密
混淆不是加密,完全可以在仅知道混淆后代码的情况下,还原出原代码
有可能失效
经过混淆后的JS代码有可能无法使用,这样的原因有以下几种:
if
、for
语句没有 {}
;代码没有以 ;
结束等相关链接:JavaScript代码混淆(上)-aaencode篇
作者:examine2
编辑:June
深大信息安全协会
Aurora
长按二维码关注