ホームページ >バックエンド開発 >Python チュートリアル >「Hello world!」を難読化するPython で難読化する
文字列「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 _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
文字列リテラルは許可されていませんでしたが、楽しみのために他の制限を設定しました。組み込みの使用を最小限に抑え、整数リテラルを使用しない単一の式 (つまり print ステートメントなし) である必要がありました。
はじめに
print を使用できないため、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")
必要な式は 1 つなので、import():
を使用します。
__import__("os").write(1, "Hello world!\n")
write() を難読化できるようにしたいので、getattr():
をスローします。
getattr(__import__("os"), "write")(1, "Hello world!\n")
これが出発点です。今後はすべて、3 つの文字列と int が難読化されます。
紐を繋ぐ
「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'
ラムダで 1 行で書き換えます:
convert = lambda num: chr(num % 256) + convert(num // 256) if num else ""
次に、匿名再帰を使用してこれを 1 つの式に変換します。これにはコンビネータが必要です。まずはこれから始めましょう:
>>> 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'
ここで、2 つの定義を式に代入するだけで、関数が完成します。
>>> (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 も形成する必要がありますが、これらはこれら 8 つの「基本的な」数値の算術演算を使用して作成できます。 1 ~ 8 の選択は任意ですが、適切な中間点と思われます。
関数が受け取る引数の数は、コード オブジェクトを介して取得できます。
__import__("os").write(1, "Hello world!\n")
argcounts が 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]
前と同様に、これをラムダ形式に変換します。
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 in の値を代入し、* を使用して結果の整数リストを 8 つの個別の変数として渡すことができ、次のようになります。
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} 変数を 、_、、_ などに置き換えます。これは、で使用される変数と混乱を招くためです。私たちの内なる機能。スコープ ルールは適切なルールが使用されることを意味するため、これによって実際に問題が発生することはありません。これは、難読化された Convert() 関数の代わりに、_ が 1 を参照するように 256 をリファクタリングした理由の 1 つでもあります。長くなったので前半だけ貼ります:
(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 _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
あと 2 つだけ残っています。まずは簡単な方から始めましょう: 256. 256=28
なので、1 << として書き換えることができます。 8 (左ビットシフトを使用)、または _ << ________ 難読化された変数を使用します。
同じアイデアを 802616035175250124568770929992 にも使用します。単純な分割統治アルゴリズムを使用して、それを、一緒にシフトされた数値の合計である数値の合計に分割できます。たとえば、112 がある場合、それを 96 16、そして (3 << 5) (2 << 3) に分割できます。私はビットシフトを使用するのが好きです。 std::cout << を思い出します。 C の "foo"、または Python の print chevron (print >>)、どちらも他の I/O 方法を伴う危険な行為です。
数値はさまざまな方法で分解できます。どれが正しいという方法はありません (結局のところ、(1
import sys sys.stdout.write("Hello world!\n")
ここでの基本的な考え方は、基数とシフトという 2 つの数値が見つかるまで、特定の範囲内の数値のさまざまな組み合わせをテストして、基数 <
range() の引数、span は、検索スペースの幅を表します。これは大きすぎることはできません。そうしないと、ベースとして num を取得し、シフトとして 0 を取得することになります (diff が 0 であるため)。また、base は単一の変数として表すことができないため、無限に再帰的に繰り返されます。 。小さすぎると、上記の (1 スパン=⌈log1.5|数値|⌉ ⌊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")
読んでいただきありがとうございます!この投稿の人気には驚き続けています。
以上が「Hello world!」を難読化するPython で難読化するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。