從hello world看JavaScript隱藏的黑魔法

寫在最前

事情的起因是這段看起來不像代碼的代碼:

_20170825232240
_20170825232240

有興趣的同學可以自己先嚐試下!

([]+[][(![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])()(([]+{})[+[]])[+[]]+(!+[]+!![]+[])+(+!![]+[]))

作者對着這段代碼足足看了一下午,我只想說這不是什麼深奧的黑魔法。一點點看下來你就知道其中的原理了。最後會有一段作者自己封裝的代碼叫nb.js(源碼在這裏),它實現了輸入數字字母后自動生成這種玄學代碼片段。就像這樣:

_20170825233827
_20170825233827

歡迎關注我的博客,不定期更新中——

JavaScript小衆系列開始更新啦

——何時完結不確定,寫多少看我會多少!這是已經更新的地址:

這個系列旨在對一些人們不常用遇到的知識點,以及可能常用到但不曾深入瞭解的部分做一個重新梳理,雖然可能有些部分看起來沒有什麼用,因爲平時開發真的用不到!但個人認爲糟粕也好精華也罷裏面全部蘊藏着JS一些偏本質的東西或者說底層規範,如果能適當避開舒適區來看這些小細節,也許對自己也會有些幫助~文章更新在我的博客,歡迎不定期關注。

轉換思路

基礎思路:通過關鍵字來獲取字母

什麼意思?比如:f。看到f你會想到哪個關鍵字?同時這個關鍵字是要在類型轉換的機制下能夠被打印出來的。如果類型轉換你還不是很瞭解,可以先讀下這篇來理解一下:從[] == ![]看隱式強制轉換機制。我相信很多同學可以想到是false這個關鍵字。那麼我們的思路就有了也就是要讓代碼實現'false'[0]這件事,這個認識統一之後我相信下面的代碼一定不難理解了:

[[[] == []] + []][+![]][+![]]
//過程理解爲
[] == [] => false
[[] == []] => [false]
[[[] == []] + []] => ['false'], [+![]] => 0
[[[] == []] + []][+![]] => 'false'
[[[] == []] + []][+![]][+![]] => 'false'[0] => 'f'

其中大體形式可以理解爲:['false'][0][0] => 'f'

是不是瞬間覺得也不過如此?

可通過關鍵字獲取的字符

當你知道可以用上面的方式來獲取自己需要的字母之後,接下來要做的是思考一下你能從關鍵字中獲取哪些字母呢,作者總結了以下你可以通過關鍵字獲得的字母:

([][[]]+[]) => 'undefined'
+[1+[[][0]+[]][0][3]+400][0]+[] => 'Infinity'
[[[] == []] + []][+![]] => 'false'
[[[] != []] + []][+![]] => 'true'
([]+{}) => "[object Object]"

感興趣的同學自己打印下就明白爲什麼了。

接下來要說的是剩下的字母怎麼辦?當然了你仍然可以通過試圖尋找關鍵字的方式來獲取字母。但是如果標點我也想要呢?或者說26個字母我都想要怎麼辦?
具體點來說對於“hello world!”這段字符串來看,至少“w”,"!"的獲取方法通過關鍵字的形式我們是無從下手的。

unescape

unescape() 函數可對通過 escape() 編碼的字符串進行解碼。但是已經廢棄了

是的現在已經不建議如此使用了,但是瀏覽器下基本還是支持這個函數的。通過這個函數我們可以通過ascll碼來直接得到我們需要的字符:

unescape('%77') => 'w'

如此看來,除了我們可以快速得得到一些關鍵字字母外,用這個方法我們便可以實現任意字母的組合。而作者封裝的nb.js也是基於這兩者來實現輸出黑魔法字符串的。

那麼現在的問題是如果通過字符串來執行unescape('%77')這段代碼?

來看下hello world那段代碼是如何實現的:



在這裏也不繞彎子了,作者打印了很多次之後才發現是如此調用的:

[]['sort']['constructor']('return unescape')

因爲JS調用方法不光是“.”調用,通過[]也是可以調用的。同時通過return unescape,返回了一個匿名函數形成了閉包。故調用的時候採用如下方式:

[]['sort']['constructor']('return unescape')()('%77') => 'w'

至於爲什麼這段代碼寫出來如此長是因爲上面的每一個字母都是一點點拼出來的,也行好上面通過關鍵字的方式可以得到這些字母=。=不然的話——

封裝nb.js

所以經過上面的分析你會發現,除了字符串長度感人之外,這種通過拼接字符串可以返回函數並且執行的方式還真是蠻炫酷的。爲了達到裝逼的效果。作者決定封裝一個支持字母和數字的函數,當你傳入普通的字符串之後,會返回帶有黑魔法氣息的冗長字符串,盡情拿去裝x吧,不客氣~

封裝過程

維護基礎對象與ascll表對象

var baseAlibrary = {
    'a': '[[[] == []][0]+[]][0][1]',
    'b': null,
    'c': '[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]]+[]][0][3]',
    'd': '([][[]]+[])[+!![]+!![]]',
    'e': '([][[]]+[])[+!![]+!![]+!![]]',
    'f': '([][[]]+[])[+!![]+!![]+!![]+!![]]',
    'g': null,
    ...
    '0': '(+![])',
    '1': '(+!![])',
    '2': '(+!![]+!![])',
    ...
    ',': null,
    '!': null,
    }
    var ascll = { //ascll表可自行配置, 新添加後需要在上面對象中配置相同key,只是value爲null
        'A': '41',
        'B': '42',
        ...
    }

將簡單的字母轉換方式直接存儲下來,如果需要的字符無法從基礎對象獲取,就記爲null,並在ascll表中寫入相關轉碼方式。

封裝unesacpe

var result = ''
    var unescapeStr = '[][(![]((!![]+[])[+!![]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])'
    //將[]['sort']['constructor']('return unescape')的黑魔法形式存儲起來之後直接調用
    function changeAscll(ascllItem) {
        var ascllResult = ''
        var middleValue = ''
        ascll[ascllItem].split('').forEach(function(item) {
            if(isNaN(item)) { //ascll中遇到字母則需要再次進行unescape轉碼
                var str = ''
                ascll[item].split('').forEach(function(data) {
                    str += '+[' + baseAlibrary[data] + ']'
                })
                middleValue += '+' + unescapeStr + '()('+ baseAlibrary['%']+'+' +  str.slice(1) + ')'
            } else {
                middleValue += '+[' + baseAlibrary[item] + ']'
            }   
        })
        ascllResult += '+' + unescapeStr + '()('+ baseAlibrary['%']+'+' +  middleValue.slice(1) + ')'
        return ascllResult
    }
    function getUnEscape(str) {

    }
    strArr.forEach(function(item) {
        Object.keys(baseAlibrary).forEach(function(obj, i) {
            if(item.toLocaleLowerCase() === obj) {
                if(!baseAlibrary[item]) {
                    Object.keys(ascll).forEach(function(ascllItem) {
                        if(obj === ascllItem) {
                            var cbValue = changeAscll(ascllItem).slice(1)
                            result += '+' + cbValue
                        }
                    })
                } else {
                    result += '+' + baseAlibrary[obj]
                }
            }
        })
    })
    console.log(result.slice(1))

效果

也就是一開始大家看到的:


_20170825233827
_20170825233827

作者將函數綁定在了this上,通過this.reallyNb()即可得到你想要的~

PS:代碼請部署在服務器中再打開頁面,否則個別字母通過location方法會取不到,主要就是t,p。不過這個問題之後作者會將其以ascll表的方式存儲,就沒有環境限制了。只是作者嫌棄那樣做打印的字符串太長了~

最後

不定時更新中——
有問題歡迎在issues下交流。

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