Phylum の自動プラットフォーム正直 は [chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’e’] を検出しました。 onyxproxy 装置 資格情報と多数の機密データを収集して盗み出す悪意のある機器である PyPI で。 いくつかの点で、この機器は、PyPI で広く知られている多くのトークン スティーラーの典型です。 一方、このリマーク装置の 1 つの特徴が私たちの注意を引きました: 難読化の方法論は、2007 年に Python の改善 Unicode[‘MATHEMATICAL SANS-SERIF BOLD SMALL S’, ‘MATHEMATICAL SANS-SERIF ITALIC SMALL E’, ‘MATHEMATICAL SANS-SERIF ITALIC SMALL L’, ‘MATHEMATICAL SANS-SERIF ITALIC SMALL F’] 、に記載) PEP-3131:
Ka-Ping Yee が
で議論と追加の反論をまとめています。
そのような: [‘MATHEMATICAL SANS-SERIF BOLD SMALL S’, ‘MATHEMATICAL SANS-SERIF ITALIC SMALL E’, ‘MATHEMATICAL SANS-SERIF ITALIC SMALL L’, ‘MATHEMATICAL SANS-SERIF ITALIC SMALL F’]
正直なところ、まだ識別子を構成することが許可されている可能性があります。 Unicode 文字は?
非 ASCII 識別子の卸売を許可することの欠点:
Python は、快適な丸一日を人間が読める形で隠し紙や紙の上に図表化する能力を失います. Python はブランド独自のクラスのセキュリティ エクスプロイトの可能性。 コードと提出されたパッチは、間違いなくよく知られており、熟考するのがより困難になります.
originate-source draw で開発者を悪意のあるコードから守るコード インスペクションには、自動化が必要です。 実行可能なアクターは、この自動化を回避するためにコードの進化と適応を繰り返しています。 The Phylum Compare Crew は、Python で改善された Unicode のこの魅力的な誤用を解明します
否定できない思索に隠れて[‘MATHEMATICAL SANS-SERIF BOLD SMALL S’, ‘MATHEMATICAL SANS-SERIF ITALIC SMALL E’, ‘MATHEMATICAL SANS-SERIF ITALIC SMALL L’, ‘MATHEMATICAL SANS-SERIF ITALIC SMALL F’]
ここに setup.py からの窮屈なサンプル コード スニペットがあります。 最初に私たちの注意を引いたファイル:
クラスブラウザー:def __init __(self、webhook):𝘀𝙚𝘵𝘢𝘵𝘵𝙧(𝘀𝘦𝘭𝘧、 'webhook'、𝗦𝘺𝙣𝘤𝙒𝘦𝘣𝙝𝘰𝘰𝙠...from_url(𝘸𝘦𝗯𝘩𝙤𝙤𝗸))𝘊𝗵𝗿𝙤𝘮𝘪𝘶𝘮()𝘖𝘱𝙚𝗿𝗮()
あなたの選択肢には標準以下のものは何もないかもしれません — クィアで、等幅でなく、sansドーントレスとイタリック体が混在するセリフフォントは、まさにコードの書き方であり、 setup.py[unicodedata.name(c) for c in “𝘀𝘦𝘭𝘧”] には、何千もの同一のコード文字列が含まれています。 このクィアなダイアグラムの明白で迅速な有益な点は、読みやすさです。 フォントが混在していても、目と脳はフレーズを読み取ることができるため、このコードについて反動の理由なしに静止することができます。 さらに、これらの考慮されたバリエーションは、もはやコードの動作を妨げません。
開発者は、この機器が物事をまっすぐに思い出して抽出しようとしていることを除いて、彼らがどれほど賢いかを示そうとしているので、おそらくこれを省略するかもしれません。セットアップ時に離れています。 この習慣の最も妥当な最終的な理由は、後で議論することができるように設計された球状の文字列マッチングを回避する可能性があるということです。 今のところ、このコードで Python が行うことを実現しようとしています
Python インタプリタの内部[chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’e’]
厳密に文字列 self と 𝘀𝘦𝘭𝘧 所有分人間の時計との違いは、もはや Python では同じ文字列ではありません.
これは、Python からデータを検索して、すべてのパーソナリティの数値 (つまり、Unicode
コード物質)
>>> [ord(c) for c in “self”] [115, 101, 108, 102]>>> [ord(c) for c in “𝘀𝘦𝘭𝘧”] [120320, 120358, 120365, 120359]
または、各文字列のすべてのパーソナリティに対して確立される Unicode
>>> ユニコードデータをインポート>>> [unicodedata.name(c) for c in "𝘀𝘦𝘭𝘧"] [unicodedata.name(c) for c in "𝘀𝘦𝘭𝘧"]>>> [unicodedata.name(c) for c in "𝘀𝘦𝘭𝘧"] ['MATHEMATICAL SANS-SERIF BOLD SMALL S', 'MATHEMATICAL SANS-SERIF ITALIC SMALL E', 'MATHEMATICAL SANS-SERIF ITALIC SMALL L', 'MATHEMATICAL SANS-SERIF ITALIC SMALL F']
Python インタプリタが NameError
[chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’l’] の貴重な行を実行した場合__初期化__ で定義するとすぐに [chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’e’] に変わったので自己 そして決して
𝘀𝘦𝘭𝘧 )。 一方、これはもはや当てはまりません-Pythonはこれらの文字列のそれぞれをとして解釈します自己。 しかし、なぜ?
字句解析
[unicodedata.name(c) for c in “𝘀𝘦𝘭𝘧”] Python プログラムは によって読み取られます) )パーサー。 パーサーに入力すると、トークン[120320, 120358, 120365, 120359]の循環が行われます、 によって生成された) 字句解析器
.
[unicodedata.name(c) for c in “𝘀𝘦𝘭𝘧”]
セクション 2.2 は、Python の字句解析器 (通常、ほとんどの場合 として知られている) lexer) 生成:
NEWLINE、INDENT、および DEDENT に加えて、次のトークンのカテゴリが存在します: 識別子, キーフレーズ
、リテラル, 演算子、 と 区切り文字.
私たちの指摘 議論の考慮事項 識別子は、ほとんどの場合、Python では名前としても知られています。 上記の例から、問題の核心はそれ以来 self と 𝘀𝘦𝘭𝘧 が文字列として多数ある場合、レクサーはこれらを多数のトークンとして識別します。 これは間接的に解決されます:
[unicodedata.name(c) for c in “𝘀𝘦𝘭𝘧”] すべての識別子は、解析中に異常な勝利 NFKC に変換されます。 識別子の比較はNFKCに準ずる.
わたしたちは・・・にいくつもりですについてもっとコメントしてくださいNFKC すぐに、しかし、プロジェクトはレクサーがトークンの循環を作成することですテキスト主張材料、そのうちの 3 つは self, 𝘀𝘦𝘭𝘧 およびそれ以降の亜種 𝘴𝙚𝘭𝗳 (サンプル コードの最後の行を要求します)。 パーサーがこれらのトークンを受け取ると、それらすべてを NFKC new win で正規化し、同じ識別子 self[ord(c) for c in “𝘀𝘦𝘭𝘧”] にします。 。 したがって、これらのトークンはすべて同じ組織の多数の表現であるため、NameError.
>>> unicodedata.normalize("NFKC", "𝘀𝘦𝘭𝘧")=="自己" モラル
パーサーの角度から見ると、Python インタープリターを介してすべての描画が行われるため、この難読化の試みは、コード。 これは決して偶然ではありません。 なんとなく、Pythonの意図的なアセンブルオプションです
PEP-672
この Unicode のセキュリティへの影響改善については次元で議論され、文書化されました PEP-672: Python の Unicode 関連のセキュリティ問題(2021 年 11 月 1 日付け)、Python 識別子での Unicode の誤用の可能性に関する情報議論。 著者は次のことを認めます:
この記述の調査は、 によって導入された直後に変更されましたCVE -2021-42574, Trojan Source Assaults
、Nicholas Boucher と Ross Anderson によって報告されました。彼らは、プログラミング言語の展開における双方向オーバーライド文字とホモグリフを専門としています。 の 識別子の正規化 PEP-672 の割り当ては、Unicode が頻繁に同じパーソナリティのように見えるもののバリアントをどのように扱うかを説明しています:
) また、新しい文字には、かなり多くの明らかな多様化が含まれていることがよくあります。 Unicode は、数学のように、セマンティックな方法論を区別するコンテキストにそれらを提供します。 説明のために、n のいくつかの多様化*) それは:
n
(ラテン小文字 N)
𝐧 (数学的太字小文字 N) 𝘯 (数学的サンセリフイタリック小文字 N)
n
(全角ラテン小文字N) ⁿ (SUPERSCRIPTラテン小文字 N) Unicode には のアルゴリズムが必要です正規化 これらのようなバリアントを単一に勝ち、そして Python識別子は正規化されています 。 (新しい形式の選択があります; Python は NFKC を費やします) .)[chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’e’]
Unicode Frequent Annex #15 には、NFKC の貴重な物質と多数の正規化形式が含まれています。 Pythonパーサーが同じ識別子に正規化する文字列の表現形式が存在すること、および特定の文字列に対して同じ文字列がいくつあるかを正確に知る必要があることを達成するだけで十分です.
「どうすればあなたを逃れることができますか? 任せてください」
インタープリターが正規化する多数の Unicode 文字列を攻撃者が指摘する可能性のある方法は、おそらくいくつあるでしょうか [chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’e’] 自己? 最初に、s : >>> s_variants=[chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’e’]>>> s_variants [unicodedata.name(c) for c in “𝘀𝘦𝘭𝘧”]>>> len(s_variants) 19
続行中弦の弛緩により 自己:
>>> e_variants=[chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’e’]>>> len(e_variants) 19>>> l_variants=[chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’l’]>>> len(l_variants) 20>>> f_variants=[chr(n) for n in range(0x10FFFF) if unicodedata.normalize(‘NFKC’, chr(n))==’l’]>>> len(f_variants) 17
[chr(n) for n in range(0x10FFFF) if unicodedata.normalize('NFKC', chr(n))==c]したがって、文字列 self[ord(c) for c in "𝘀𝘦𝘭𝘧"] には 19 19 20 17 の変形があります。 識別子 self。 要約すれば、
>>> デフcount_chr_variants(c): ... return len([chr(n) for n in range(0x10FFFF) if unicodedata.normalize('NFKC', chr(n))==c]) ...>>> def count_variants(identifier): ... カウント=1 ... for c in identifier: ... counts *=count_chr_variants(c) ... return counts ...>>> count_variants('self') 122740での真の Unicode 文字列一致の自動描画ショッピング)self は、これらの 10 万を超えるバリアントのいずれかがコード内で異なるものとしてエージングされている場合、失敗します。
、および [chr(n) for n in range(0x10FFFF) if unicodedata.normalize('NFKC', chr(n))=='e'] CryptUnprotectData. それらの機能バリアントの数:明らかに、文字列
self は、Python ではあまりにも新しいものです。潜在的に疑わしいコードを検索する価値があります。 私たちが気づいた難読化の習慣の最小限の例として、単純化のためにこれを選択しました。 onyxproxy の作者がくれたsetup.py の数千の多数の例 から取得し、 である 次のような疑わしい運動を示す: __輸入__
、 サブプロセス>>> count_variants('__import__') 106153953192>>> count_variants('subprocess') 4418826466608>>> count_variants('CryptUnprotectData') 54105881615783933829120そのため、悪意のある攻撃者が文字列照合に基づく完全な防御を回避する同じコードを作成する可能性がある、膨大な種類の識別子の亜種が存在します。 結論
は、洗練されていないことを示しています。 多くの場所からコードを減らして貼り付け、それらをまとめただけであることは明らかです。ある意味で
の作者onyxproxy
setup.py[chr(n) for n in range(0x10FFFF) if unicodedata.normalize('NFKC', chr(n))=='e'] のコードの多数の内容が完全に欠落しているこの難読化手法は、もはや最高のものではありません。 ただし、多くの Python モジュールはかなり頻繁にインポートされます (例: )。 import os を9回
しかし、この作成者がこの難読化されたコードをコピーした人は誰でも、内部構造を利用するための適切なドローを知るのに十分賢明です。 Python インタープリターを使用して、多かれ少なかれ難読化された元のコードを生成します。この形式は、コードが正確に何を呼び出そうとしているかについてあまり知られていないことを明らかにすることなく、適度に読みやすい形式です。 この目新しさは、この方法論が実際に実行可能であることが確認されたので、Phylumで監視を保護するものです.