JSfuck原理解析一——基础原理

  本系列博客是对jsfuck代码的一些分析,暂定为三篇。第一篇为jsfuck基础原理,第二篇为官方案例分析,第三篇为源码解析。

  不出意外的话就是这三篇,我实在是比较懒,第一篇过了一年半才去写第二篇,但愿第三篇不会再拖了……

  如果你也有对知识的渴望,可以直接访问原作者的GitHub:https://github.com/aemkei/jsfuck 原作者其实已经解释很清楚了。不过学习嘛,自己分析才有乐趣。

  那下面开始正文:

  什么是jsfuck?

  想象一下自己第一次见到代码的样子,满屏的奇怪字符?WTF?而当我看到jsfuck的时候,再次体会到了这种感觉。请看下面的代码:

[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()

WTF???

  没错!这确实是一串js代码,你可以直接放到控制台去执行,最后执行的结果是alert(1)。JS就是这么一门神奇的语言。本着对知识的渴求与对未知事物的探索精神(雾),下面我们就一起来研究下jsfuck的原理。

   在讲jsfuck之前,先要说起一个js的经典面试题:[]==![]

  这道题是考验js基础的,利用的是js的一些基本原理——类型转换。类型转换大家都了解,比如简单的 ‘a’+1结果就是’a1’,在这个计算过程中1被隐式转换成string类型,这道面试题也是这个原理。Js语法中’==’两边如果不是同一类型则会进行类型转换。

  但是相信很多接触过前端的同事见到这个题目时候的第一反应,是这个表达式的结果是false,然而拿到控制台执行之后却发现是true,这让很多人十分不解。特别是当我们在控制台打印代码的时候,会出现一些令人费解的事情。如:

Boolean([])   //true

Boolean(![])  //false

  其实上述代码,我们进行了强制转换,在js中在把对象做布尔值转换的时候,会把所有对象都转换为true(包括创建的new Boolean(false)对象转换之后也是true,具体可参考犀牛书3.8.3)。而相等运算符却不是这么计算的,在相等运算符中,如果运算符两边的值有一个是布尔类型,则会把true转换成1,false则转换为0。而在这道题目中![]的结果肯定是布尔值false,则会转换为0。

[]==0

  这时候解释起来就简单了很多,相等运算符如果有一边是数字,而另一边是对象,则会把对象转换成数字。而空的数组对象转换成数字是0(犀牛书3.8)那么结果便是:

0==0  //true

  讲了这么多,终于可以进入正题了。之所以开篇讲这么一个题目,是为了更好理解jsfuck的原理。在官网上(直接百度jsfuck就行),我们能看到官方给出的jsfuck的Basics,下面我们来逐条分析。(水平有限,如果有发现错误可以指出,大家一起研究)

 

false => ![]  

//非运算符会把[]的布尔值取反


 

true => !![]  

//再取反则是true

 


undefined => [][[]] 

 

 

//这一个就比较复杂,在js中数组的索引可以为负数和非整数,而在这种情况下会将索引转换成字符串,作为对象的属性访问,我们知道[]转换为字符串为空字符串,则上面等价于:[][‘’]。然而数组对象并没有名为’’的属性,在js中访问一个对象并不存在的属性时,会给出undefined 

 

 

NaN => +[![]]  

//这里利用了+运算符的特性,这里+不是加号运算符(姑且这么叫)而是一元运算符的一种,一元加法,用于把操作数转换为数字或者NaN并返回这个数字。上式等价于+[false],而根据类型转换的规则我们知道,这样得到的结果为NaN

 

0 => +[]  

//根据类型转换规则,[]转换为数字为0

 

1 => +!+[]  

//本式为上面的变式,根据运算符的优先级,先执行+[]为0,再转换成布尔取反为true,true转换为数字为1

 

2 => !+[]+!+[] 

 //中间一个+为加法运算符优先级比较低,等价于true+true,值为2

 

10 => [+!+[]]+[+[]]   

//可拆分成[+!+[]]加[+[]],左边为[1],右边为[0],+作为连接字符串运算符,而根据数组转换成字符串我们可以知道上面等价于‘1’+‘0’,得到的是字符串‘10’

 

Array => []  

//[]就是数组对象的事例,用于获取字符串’Array’,用["constructor"]找到对应函数,再将其变成字符串,在最后一篇源码解析中会讲到。

 

Number => +[]  

//与上式相同,后面会详细讲解

 

String => []+[]  

//与上式相同,后面会详细讲解

 

Boolean => ![] 

 //与上式相同,后面会详细讲解

 

Function => []["filter"] 

 //与上式相同,后面会详细讲解

 

eval => []["filter"]["constructor"]( CODE )()  

//这里比较复杂,我们先来看前半部分[]["filter"]["constructor"],这一部分可以看到拿的是一个数组对象的‘filter’属性下面的‘constructor’属性,‘filter’属性是数组的一个方法,但在这里并没有用到该方法,你把它替换成别的方法也可以照常运行(例如‘map’),关键是下面这一步,filter的constructor属性,我们知道在js中任何方法都可以看做是Function对象的一个实例,而Function就作为所有函数的构造函数。这里拿到的就是函数的Function()构造函数。而Function()构造函数的最后一个参数会作为函数体执行。因此这段语句会执行‘CODE里的内容’。

 

window => []["filter"]["constructor"]("return this")() 

 //与上式相同,拿到的是函数的构造函数,第一个括号内便是函数体。并且Fuction()构造函数所创建的函数并不使用词法作用域(划重点!),他是直接执行在顶层函数中的,也就是全局作用域,因此this返回window。

 

说了这么多,却还是无法解释开篇那外星语言一般的代码,但是有了这篇博客的基础,接下来就方便了很多下一篇会详细分析jf里面的一些字母与语句的由来,敬请期待

 

 

 

你可能感兴趣的:(JavaScript基础,jiFuck,web前端)