創建最奇怪的混淆程序,列印字串「Hello world!」。我決定寫一篇解釋它到底是如何運作的。所以,這是 Python 2.7 中的條目:
(lambda _, __, ___, ____, _____, ______, _______, ________: getattr( __import__(True.__class__.__name__[_] + [].__class__.__name__[__]), ().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________] )( _, (lambda _, __, ___: _(_, __, ___))( lambda _, __, ___: chr(___ % __) + _(_, __, ___ // __) if ___ else (lambda: _).func_code.co_lnotab, _ << ________, (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __) - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______ << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) << ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) << __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______ << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) + _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ << _))) + (_____ << ______) + (_ << ___) ) ) )( *(lambda _, __, ___: _(_, __, ___))( (lambda _, __, ___: [__(___[(lambda: _).func_code.co_nlocals])] + _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else [] ), lambda _: _.func_code.co_argcount, ( lambda _: _, lambda _, __: _, lambda _, __, ___: _, lambda _, __, ___, ____: _, lambda _, __, ___, ____, _____: _, lambda _, __, ___, ____, _____, ______: _, lambda _, __, ___, ____, _____, ______, _______: _, lambda _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
不允許使用字串文字,但我為了好玩設定了一些其他限制:它必須是單一表達式(因此沒有列印語句),具有最少的內建用法,並且沒有整數文字。
開始使用
由於我們無法使用列印,我們可以寫入 stdout 檔案物件:
import sys sys.stdout.write("Hello world!\n")
但是讓我們使用較低等級的東西:os.write()。我們需要 stdout 的檔案描述符,它是 1(可以使用 print sys.stdout.fileno() 檢查)。
import os os.write(1, "Hello world!\n")
我們想要一個表達式,所以我們將使用 import():
__import__("os").write(1, "Hello world!\n")
我們也希望能夠混淆 write(),因此我們將引入 getattr():
getattr(__import__("os"), "write")(1, "Hello world!\n")
這是起點。從現在開始,一切都將混淆三個字串和整數。
將字串串在一起
「os」和「write」相當簡單,因此我們將透過連接各個內建類別的部分名稱來建立它們。有很多不同的方法可以做到這一點,但我選擇了以下方法:
"o" from the second letter of bool: True.__class__.__name__[1] "s" from the third letter of list: [].__class__.__name__[2] "wr" from the first two letters of wrapper_descriptor, an implementation detail in CPython found as the type of some builtin classes’ methods (more on that here): ().__class__.__eq__.__class__.__name__[:2] "ite" from the sixth through eighth letters of tupleiterator, the type of object returned by calling iter() on a tuple: ().__iter__().__class__.__name__[5:8]
我們開始取得一些進展!
getattr( __import__(True.__class__.__name__[1] + [].__class__.__name__[2]), ().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8] )(1, "Hello world!\n")
「Hello world!n」更複雜。我們將把它編碼為一個大整數,它由每個字元的 ASCII 代碼乘以 256 的字元在字串中的索引次方組成。換句話說,以下總和:
Σn=0L−1cn(256n)
哪裡L
是字串的長度,cn 是 n
個字元。建立號碼:
>>> codes = [ord(c) for c in "Hello world!\n"] >>> num = sum(codes[i] * 256 ** i for i in xrange(len(codes))) >>> print num 802616035175250124568770929992
現在我們需要程式碼將此數字轉換回字串。我們使用一個簡單的遞歸演算法:
>>> def convert(num): ... if num: ... return chr(num % 256) + convert(num // 256) ... else: ... return "" ... >>> convert(802616035175250124568770929992) 'Hello world!\n'
用 lambda 重寫一行:
convert = lambda num: chr(num % 256) + convert(num // 256) if num else ""
現在我們使用匿名遞歸將其轉換為單一表達式。這需要一個組合器。從這個開始:
>>> comb = lambda f, n: f(f, n) >>> convert = lambda f, n: chr(n % 256) + f(f, n // 256) if n else "" >>> comb(convert, 802616035175250124568770929992) 'Hello world!\n'
現在我們只需將這兩個定義代入表達式中,我們就得到了我們的函數:
>>> (lambda f, n: f(f, n))( ... lambda f, n: chr(n % 256) + f(f, n // 256) if n else "", ... 802616035175250124568770929992) 'Hello world!\n'
現在我們可以將其貼到先前的程式碼中,一路替換一些變數名稱 (f → , n → _):
getattr( __import__(True.__class__.__name__[1] + [].__class__.__name__[2]), ().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8] )( 1, (lambda _, __: _(_, __))( lambda _, __: chr(__ % 256) + _(_, __ // 256) if __ else "", 802616035175250124568770929992 ) )
函數內部
我們在轉換函數的主體中留下了一個「」(記住:沒有字串文字!),以及我們必須以某種方式隱藏的大量數字。讓我們從空字串開始。我們可以透過檢查某個隨機函數的內部結構來即時製作一個:
(lambda _, __, ___, ____, _____, ______, _______, ________: getattr( __import__(True.__class__.__name__[_] + [].__class__.__name__[__]), ().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________] )( _, (lambda _, __, ___: _(_, __, ___))( lambda _, __, ___: chr(___ % __) + _(_, __, ___ // __) if ___ else (lambda: _).func_code.co_lnotab, _ << ________, (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __) - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______ << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) << ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) << __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______ << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) + _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ << _))) + (_____ << ______) + (_ << ___) ) ) )( *(lambda _, __, ___: _(_, __, ___))( (lambda _, __, ___: [__(___[(lambda: _).func_code.co_nlocals])] + _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else [] ), lambda _: _.func_code.co_argcount, ( lambda _: _, lambda _, __: _, lambda _, __, ___: _, lambda _, __, ___, ____: _, lambda _, __, ___, ____, _____: _, lambda _, __, ___, ____, _____, ______: _, lambda _, __, ___, ____, _____, ______, _______: _, lambda _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
我們在這裡真正要做的是查看函數中包含的程式碼物件的行號表。由於它是匿名的,因此沒有行號,因此字串為空。將 0 替換為 _ 以使其更加混亂(這並不重要,因為該函數沒有被呼叫),然後將其插入。我們也將把 256 重構為一個參數,該參數傳遞給我們混淆的 Convert()連同號碼。這需要為組合器添加一個參數:
import sys sys.stdout.write("Hello world!\n")
繞道
讓我們暫時解決一個不同的問題。我們想要一種方法來混淆程式碼中的數字,但是每次使用它們時重新建立它們會很麻煩(而且不是特別有趣)。如果我們可以實作range(1, 9) == [1, 2, 3, 4, 5, 6, 7, 8],那麼我們可以將目前的工作包裝在一個函數中,該函數接受包含以下數字的變數1 到8,並用這些變數取代正文中出現的整數文字:
import os os.write(1, "Hello world!\n")
即使我們還需要形成 256 和 802616035175250124568770929992,它們也可以透過對這八個「基本」數字進行算術運算來創建。 1-8 的選擇是任意的,但似乎是一個很好的中間立場。
我們可以透過函數的程式碼物件來取得函數接受的參數數量:
__import__("os").write(1, "Hello world!\n")
建構參數計數在 1 到 8 之間的函數元組:
getattr(__import__("os"), "write")(1, "Hello world!\n")
使用遞歸演算法,我們可以將其轉換為 range(1, 9) 的輸出:
"o" from the second letter of bool: True.__class__.__name__[1] "s" from the third letter of list: [].__class__.__name__[2] "wr" from the first two letters of wrapper_descriptor, an implementation detail in CPython found as the type of some builtin classes’ methods (more on that here): ().__class__.__eq__.__class__.__name__[:2] "ite" from the sixth through eighth letters of tupleiterator, the type of object returned by calling iter() on a tuple: ().__iter__().__class__.__name__[5:8]
和之前一樣,我們將其轉換為 lambda 形式:
getattr( __import__(True.__class__.__name__[1] + [].__class__.__name__[2]), ().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8] )(1, "Hello world!\n")
然後,進入匿名遞歸形式:
>>> codes = [ord(c) for c in "Hello world!\n"] >>> num = sum(codes[i] * 256 ** i for i in xrange(len(codes))) >>> print num 802616035175250124568770929992
為了好玩,我們將 argcount 操作分解為一個附加函數參數,並混淆一些變數名稱:
>>> def convert(num): ... if num: ... return chr(num % 256) + convert(num // 256) ... else: ... return "" ... >>> convert(802616035175250124568770929992) 'Hello world!\n'
現在有一個新問題:我們仍然需要一種隱藏 0 和 1 的方法。我們可以透過檢查任意函數中局部變數的數量來獲得這些:
convert = lambda num: chr(num % 256) + convert(num // 256) if num else ""
儘管函數體看起來相同,但第一個函數中的 _ 不是參數,也不是在函數中定義的,因此 Python 將其解釋為全域變數:
>>> comb = lambda f, n: f(f, n) >>> convert = lambda f, n: chr(n % 256) + f(f, n // 256) if n else "" >>> comb(convert, 802616035175250124568770929992) 'Hello world!\n'
無論 _ 是否實際在全域範圍內定義,都會發生這種情況。
將其付諸實踐:
>>> (lambda f, n: f(f, n))( ... lambda f, n: chr(n % 256) + f(f, n // 256) if n else "", ... 802616035175250124568770929992) 'Hello world!\n'
現在我們可以取代 funcs 的值,然後使用 * 將結果整數列表作為八個單獨的變數傳遞,我們得到:
getattr( __import__(True.__class__.__name__[1] + [].__class__.__name__[2]), ().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8] )( 1, (lambda _, __: _(_, __))( lambda _, __: chr(__ % 256) + _(_, __ // 256) if __ else "", 802616035175250124568770929992 ) )
移位
快到了!我們將用、_、、_ 等來取代n{1..8} 變量,因為它會與中使用的變數產生混淆我們的內在功能。這不會造成實際問題,因為範圍規則意味著將使用正確的規則。這也是我們將 256 重構為 _ 指涉 1 而不是我們混淆的 Convert() 函數的原因之一。有點長了,就只貼前半部了:
(lambda _, __, ___, ____, _____, ______, _______, ________: getattr( __import__(True.__class__.__name__[_] + [].__class__.__name__[__]), ().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________] )( _, (lambda _, __, ___: _(_, __, ___))( lambda _, __, ___: chr(___ % __) + _(_, __, ___ // __) if ___ else (lambda: _).func_code.co_lnotab, _ << ________, (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __) - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______ << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) << ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) << __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______ << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) + _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ << _))) + (_____ << ______) + (_ << ___) ) ) )( *(lambda _, __, ___: _(_, __, ___))( (lambda _, __, ___: [__(___[(lambda: _).func_code.co_nlocals])] + _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else [] ), lambda _: _.func_code.co_argcount, ( lambda _: _, lambda _, __: _, lambda _, __, ___: _, lambda _, __, ___, ____: _, lambda _, __, ___, ____, _____: _, lambda _, __, ___, ____, _____, ______: _, lambda _, __, ___, ____, _____, ______, _______: _, lambda _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
只剩下兩件事了。我們從簡單的開始:256. 256=28
,所以我們可以將其改寫為 1
我們將對 802616035175250124568770929992 使用相同的想法。一個簡單的分而治之演算法可以將其分解為數字總和,這些數字本身就是移位在一起的數字總和,依此類推。例如,如果我們有 112,我們可以將其分解為 96 16,然後是 (3 >),這兩者都是涉及其他I/O 方式的轉移注意力的內容。
數字可以用多種方式分解;沒有一種方法是正確的(畢竟,我們可以將其分解為 (1
import sys sys.stdout.write("Hello world!\n")
這裡的基本思想是,我們測試一定範圍內的數字的各種組合,直到得出兩個數字,基數和移位,使得基數
range() 的參數 span 表示搜尋空間的寬度。這不能太大,否則我們最終將得到 num 作為我們的基數和 0 作為我們的移位(因為 diff 為零),並且由於基數不能表示為單個變量,所以它會重複,無限遞歸。如果它太小,我們最終會得到類似於上面提到的 (1 span=⌈log1.5|num|⌉ ⌊24−深度⌋
將偽代碼翻譯成 Python 並進行一些調整(支援深度參數,以及一些涉及負數的警告),我們得到:
(lambda _, __, ___, ____, _____, ______, _______, ________: getattr( __import__(True.__class__.__name__[_] + [].__class__.__name__[__]), ().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________] )( _, (lambda _, __, ___: _(_, __, ___))( lambda _, __, ___: chr(___ % __) + _(_, __, ___ // __) if ___ else (lambda: _).func_code.co_lnotab, _ << ________, (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __) - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______ << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) << ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) << __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______ << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) + _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ << _))) + (_____ << ______) + (_ << ___) ) ) )( *(lambda _, __, ___: _(_, __, ___))( (lambda _, __, ___: [__(___[(lambda: _).func_code.co_nlocals])] + _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else [] ), lambda _: _.func_code.co_argcount, ( lambda _: _, lambda _, __: _, lambda _, __, ___: _, lambda _, __, ___, ____: _, lambda _, __, ___, ____, _____: _, lambda _, __, ___, ____, _____, ______: _, lambda _, __, ___, ____, _____, ______, _______: _, lambda _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
現在,當我們呼叫convert(802616035175250124568770929992)時,我們得到了一個很好的分解:
import sys sys.stdout.write("Hello world!\n")
將其作為 802616035175250124568770929992 的替代品,並將所有部件放在一起:
import os os.write(1, "Hello world!\n")
這就是你的。
附錄:Python 3 支持
自從寫這篇文章以來,有幾個人詢問了 Python 3 支援的問題。我當時沒有想到這一點,但隨著 Python 3 不斷獲得關注(感謝您!),這篇文章顯然早就該更新了。
幸運的是,Python 3(截至撰寫本文時為 3.6)不需要我們進行太多更改:
__import__("os").write(1, "Hello world!\n")
這是完整的 Python 3 版本:
getattr(__import__("os"), "write")(1, "Hello world!\n")
感謝您的閱讀!我仍然對這篇文章的受歡迎程度感到驚訝。
以上是混淆'世界你好!” Python 上的混淆的詳細內容。更多資訊請關注PHP中文網其他相關文章!