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裏面的一些字母與語句的由來,敬請期待

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章