JSFuck源码地址(GitHub):https://github.com/aemkei/jsfuck
JSFuck在OSC上的介绍页面:http://www.oschina.net/p/jsfuck
JSFuck可以将JavaScript代码进行转换,转换后的代码只使用6个字符([,],(,),!,+),实现的功能和转换前代码是一样的。出于好奇和学习的目的,我研究了一下JSFuck的源码。
在网站 http://www.jsfuck.com/ 中有一个例子,将JavaScript语句 alert(1) 转换为只由六种字符的版本:
将这段代码放到HTML文件的script标签下,就可以运行了:
<html> <head> <title>happy new year</title> </head> <body> <script> [][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])() </script> </body> </html>
不过,我想了解的是,为什么这段代码会起作用。研究后才发现,这段代码等价于另一段代码:
[]["filter"]["constructor"]("return eval")()("alert(1)")
在这段代码中,alert(1)是我们要转换的JavaScript源码,这段代码的意思是执行JavaScript代码alert(1)。这段代码是由四个字符串和字符[、]、(、)构成的。也就是说,我们只要能把字符串中的每一个字符,都用[,],(,),!,+这六个字符表示出来,那么我们就完全可以将任何一段JavaScript代码,找到仅用这六个字符表示的等价形式。
那么,我们来看一下各个字符的等价形式:(如果用console.log输出右边的部分,则会返回左边的字符,0-9这10个字符外面又套了一层[],这样做是为了保证在之后用+运算符进行拼接时不被系统识别为加法运算)
'0':'[+[]]' '1':'[+!+[]]' '2':'[!+[]+!+[]]' '3':'[!+[]+!+[]+!+[]]' '4':'[!+[]+!+[]+!+[]+!+[]]' '5':'[!+[]+!+[]+!+[]+!+[]+!+[]]' '6':'[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]' '7':'[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]' '8':'[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]' '9':'[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]' 'a':'(false+"")[1]' 'b':'(Function("return{}")()+"")[2]' 'c':'([]["filter"]+"")[3]' 'd':'(undefined+"")[2]' 'e':'(true+"")[3]' 'f':'(false+"")[0]' 'g':'(false+[0]+String)[20]' 'h':'(+(101))["toString"](21)[1]' 'i':'([false]+undefined)[10]' 'j':'(Function("return{}")()+"")[10]' 'k':'(+(20))["toString"](21)' 'l':'(false+"")[2]' 'm':'(Number+"")[11]' 'n':'(undefined+"")[1]' 'o':'(true+[]["filter"])[10]' 'p':'(+(211))["toString"](31)[1]' 'q':'(+(212))["toString"](31)[1]' 'r':'(true+"")[1]' 's':'(false+"")[3]' 't':'(true+"")[0]' 'u':'(undefined+"")[0]' 'v':'(+(31))["toString"](32)' 'w':'(+(32))["toString"](33)' 'x':'(+(101))["toString"](34)[1]' 'y':'(NaN+[Infinity])[10]' 'z':'(+(35))["toString"](36)' 'A':'(+[]+Array)[10]' 'B':'(+[]+Boolean)[10]' 'C':'Function("return escape")()(("")["italics"]())[2]' 'D':'Function("return escape")()([]["filter"])["slice"]("-1")' 'E':'(RegExp+"")[12]' 'F':'(+[]+Function)[10]' 'G':'(false+Function("return Date")()())[30]' 'H':'Function("return unescape")()("%"+(48)+"")' 'I':'(Infinity+"")[0]' 'J':'Function("return unescape")()("%"+(4)+"a")' 'K':'Function("return unescape")()("%"+(4)+"b")' 'L':'Function("return unescape")()("%"+(4)+"c")' 'M':'(true+Function("return Date")()())[30]' 'N':'(NaN+"")[0]' 'O':'(NaN+Function("return{}")())[11]' 'P':'Function("return unescape")()("%"+(50)+"")' 'Q':'Function("return unescape")()("%"+(51)+"")' 'R':'(+[]+RegExp)[10]' 'S':'(+[]+String)[10]' 'T':'(NaN+Function("return Date")()())[30]' 'U':'(NaN+Function("return{}")()["toString"]["call"]())[11]' 'V':'Function("return unescape")()("%"+(56)+"")' 'W':'Function("return unescape")()("%"+(57)+"")' 'X':'Function("return unescape")()("%"+(58)+"")' 'Y':'Function("return unescape")()("%"+(59)+"")' 'Z':'Function("return unescape")()("%"+(5)+"a")' ' ':'(NaN+[]["filter"])[11]' '!':'Function("return unescape")()("%"+(21)+"")' '"':'("")["fontcolor"]()[12]' '#':'Function("return unescape")()("%"+(23)+"")' '$':'Function("return unescape")()("%"+(24)+"")' '%':'Function("return escape")()([]["filter"])[20]' '&':'("")["link"](0+")[10]' ''':'Function("return unescape")()("%"+(27)+"")' '(':'(false+[]["filter"])[20]' ')':'(true+[]["filter"])[20]' '*':'Function("return unescape")()("%"+(2)+"a")' '+':'(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[2]' ',':'([]["slice"]["call"](false+"")+"")[1]' '-':'(+(.+[0000000001])+"")[2]' '.':'(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]' '/':'(false+[0])["italics"]()[10]' ':':'(RegExp()+"")[3]' ';':'("")["link"](")[14]' '<':'("")["italics"]()[0]' '=':'("")["fontcolor"]()[11]' '>':'("")["italics"]()[2]' '?':'(RegExp()+"")[2]' '@':'Function("return unescape")()("%"+(40)+"")' '[':'(Function("return{}")()+"")[0]' '\':'Function("return unescape")()("%"+(5)+"c")' ']':'(Function("return{}")()+"")["slice"]("-1")' '^':'Function("return unescape")()("%"+(5)+"e")' '_':'Function("return unescape")()("%"+(5)+"f")' '`':'Function("return unescape")()("%"+(60)+"")' '{':'(NaN+[]["filter"])[21]' '|':'Function("return unescape")()("%"+(7)+"c")' '}':'([]["filter"]+"")["slice"]("-1")' '~':'Function("return unescape")()("%"+(7)+"e")'
要将上面的字符转换成六种字符的等价形式,还需要用到下面的几个等价形式带入解决:
var SIMPLE = { 'false': '![]', 'true': '!![]', 'undefined': '[][[]]', 'NaN': '+[![]]', 'Infinity': '+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])' // +"1e1000" }; var CONSTRUCTORS = { 'Array': '[]', 'Number': '(+[])', 'String': '([]+[])', 'Boolean': '(![])', 'Function': '[]["filter"]', 'RegExp': 'Function("return/"+false+"/")()' };
使用SIMPLE和CONSTRUCTORS内的等价形式,即可推算出上面那些字符的内容。比如a的等价形式是(false+"")[1],将false替换为![],将""替换为[],将1替换为+!+[],就可以推算出a使用六个字符表示的等价形式为(![]+[])[+!+[]]。(PS:从中不难发现作者aemkei真是用心良苦)
这样看来,以alert(1)为例,每个字符的等价形式如下:
'a':'(![]+[])[+!+[]]' 'l':'(![]+[])[!+[]+!+[]]' 'e':'(!![]+[])[!+[]+!+[]+!+[]]' 'r':'(!![]+[])[+!+[]]' 't':'(!![]+[])[+[]]' '(':'(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]' '1':'[+!+[]]' ')':'(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]'
将它们用符号“+”连接起来,就可以获取"alert(1)"的等价形式了!同理我们获得了"filter"、"constuctor"、"return eval"的等价形式,再回头看看这段代码:
[]["filter"]["constructor"]("return eval")()("alert(1)")
将字符串替换后,放入HTML中的script标签里面,或是放到一个js文件里面,就可以用浏览器打开HTML文件查看效果啦!
(图中浏览器版本为:Google Chrome 46.0.2490.80 m)
-- 2016年2月8日 再补充一点:
如果JSFuck找不到一个字符的等价形式(比如输入中含有汉字),那么JSFuck会通过下面的方式找到该字符,以字符“呵”为例:
([]+[])["constructor"]["fromCharCode"]("呵".charCodeAt(0))
其中表达式
"呵".charCodeAt(0)
可以通过计算得出,它的值是21621,那么上面的表达式就可以替换为
([]+[])["constructor"]["fromCharCode"](21621)
将这个表达式中的字符串和数字运用之前介绍的规则进行等价替换,就可以完美地将非ASCII码字符转换为六种JSFuck符号的问题了!
END