本系列博客是對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裏面的一些字母與語句的由來,敬請期待