実行時コードの変更(実行時に自身のコードを変更するプログラム)の正当な(スマートな)使用法を思いつくことができますか?
最近のオペレーティングシステムは、この手法が検出を回避するためにウイルスによって使用されているため、これを実行するプログラムに眉をひそめているようです。
私が考えることができるのは、コンパイル時に知ることができない何かを実行時に知ることによって、いくつかのコードを削除または追加する、ある種の実行時最適化です。
実行時コードの変更(実行時に自身のコードを変更するプログラム)の正当な(スマートな)使用法を思いつくことができますか?
最近のオペレーティングシステムは、この手法が検出を回避するためにウイルスによって使用されているため、これを実行するプログラムに眉をひそめているようです。
私が考えることができるのは、コンパイル時に知ることができない何かを実行時に知ることによって、いくつかのコードを削除または追加する、ある種の実行時最適化です。
コード変更には多くの有効なケースがあります。実行時にコードを生成すると、次の場合に役立ちます。
コードが実行時にコードに変換される場合があります(これは動的バイナリ変換と呼ばれます)。
コード変更を使用して、命令セットの制限を回避できます。
コード変更のその他のケース:
これは、コンピューター グラフィックス、特に最適化目的のソフトウェア レンダラーで行われています。実行時に多くのパラメータの状態が調べられ、最適化されたバージョンのラスタライザ コードが生成され (多くの条件を削除する可能性があります)、三角形などのグラフィックス プリミティブをより高速にレンダリングできます。
正当な理由の 1 つは、asm 命令セットに、自分で作成できる必要な命令が不足しているためです。例: x86 では、レジスタ内の変数への割り込みを作成する方法はありません (たとえば、ax で割り込み番号を使用して割り込みを作成します)。オペコードにコード化された const 番号のみが許可されました。自己変更コードを使用すると、この動作をエミュレートできます。
一部のコンパイラは、静的変数の初期化にこれを使用して、後続のアクセスの条件付きのコストを回避していました。つまり、最初に実行されたときに操作なしでそのコードを上書きすることにより、「このコードを1回だけ実行する」を実装します。
多くの場合があります:
一部の OS のセキュリティ モデルは、ルート/管理者権限なしでは自己変更コードを実行できないことを意味するため、汎用的な使用には実用的ではありません。
ウィキペディアから:
厳密な W^X セキュリティを備えたオペレーティング システムで実行されているアプリケーション ソフトウェアは、書き込みが許可されているページで命令を実行できません。メモリへの命令の書き込みと、後でそれらの命令の実行の両方が許可されているのは、オペレーティング システム自体だけです。
このような OS では、Java VM のようなプログラムでさえ、JIT コードを実行するために root/admin 権限が必要です。(詳細については、 http://en.wikipedia.org/wiki/W%5EXを参照してください)
Synthesis OSは基本的に、API 呼び出しに関してプログラムを部分的に評価し、OS コードを結果に置き換えました。主な利点は、多くのエラー チェックが不要になったことです (プログラムが OS にばかげたことを要求しない場合は、チェックする必要がないため)。
はい、これはランタイム最適化の例です。
何年も前に、私は自己修正コードをデバッグしようと朝を過ごしました。ある命令が次の命令のターゲットアドレスを変更しました。つまり、分岐アドレスを計算していました。これはアセンブリ言語で書かれており、一度に 1 命令ずつプログラムを実行すると完全に機能しました。しかし、プログラムを実行すると失敗しました。最終的に、マシンがメモリから 2 つの命令をフェッチしていて、(命令がメモリに配置されていたため) 変更していた命令が既にフェッチされていたため、マシンは変更されていない (正しくない) バージョンの命令を実行していたことに気付きました。もちろん、私がデバッグしていたときは、一度に 1 つの命令しか実行していませんでした。
私のポイントは、自己変更コードはテスト/デバッグが非常に厄介である可能性があり、多くの場合、マシンの動作 (ハードウェアまたは仮想) に関する隠れた仮定があります。さらに、システムは、(現在の) マルチコア マシンで実行されているさまざまなスレッド/プロセス間でコード ページを共有することはできませんでした。これにより、仮想メモリなどの利点の多くが失われます。また、ハードウェア レベルで行われる分岐の最適化も無効になります。
(注 - 私は自己変更コードのカテゴリに JIT を含めていません。JIT はコードのある表現から別の表現に変換しており、コードを変更していません)
全体として、それはただの悪い考えです - 本当にきちんとしていて、本当にあいまいですが、本当に悪いです。
もちろん、8080 バイトと最大 512 バイトのメモリしかない場合は、そのような方法に頼る必要があるかもしれません。
オペレーティング システム カーネルから見ると、すべてのジャスト イン タイム コンパイラおよびリンカー ランタイムは、プログラム テキストの自己修正を実行します。顕著な例は、Google の V8 ECMA Script Interpreter です。
自己変更コード (実際には「自己生成」コード) のもう 1 つの理由は、パフォーマンスのためにジャストインタイム コンパイル メカニズムを実装することです。たとえば、代数式を読み取り、入力パラメーターの範囲でそれを計算するプログラムは、計算を記述する前にマシンコードで式を変換する場合があります。
ハードウェアとソフトウェアの間に論理的な違いがないことは古い栗毛を知っています... コードとデータの間に論理的な違いはないと言うこともできます。
自己変更コードとは何ですか? データではなくコマンドとして解釈できるように、値を実行ストリームに入れるコード。確かに、関数型言語には実際には違いがないという理論的な観点があります。私は、命令型言語とコンパイラ/インタープリタで、同等の地位を前提とせずに、これを簡単な方法で行うことができると言っています。
私が言及しているのは、データがプログラムの実行パスを変更できるという実際的な意味です (ある意味では、これは非常に明白です)。プログラムがコマンドからコマンドに移動するのと同じように、解析中にトラバースし、状態から状態に移動する (および他の変数を変更する) テーブル (データの配列) を作成するコンパイラ-コンパイラのようなものを考えています。 、プロセス内の変数を変更します。
したがって、コンパイラがコード空間を作成し、完全に分離されたデータ空間 (ヒープ) を参照する通常のインスタンスであっても、データを変更して実行パスを明示的に変更することができます。
進化を使って最適なアルゴリズムを作成するプログラムを実装しました。自己修正コードを使用して DNA 設計図を修正しました。
使用例の 1 つは、ウイルス対策プログラムをテストするための正規の DOS 実行可能 COM ファイルであるEICAR テスト ファイルです。
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
実行可能ファイルには [21h-60h, 7Bh-7Dh] の範囲の印刷可能/入力可能な ASCII 文字のみを含める必要があるため、自己コード変更を使用する必要があります。これにより、エンコード可能な命令の数が大幅に制限されます。
詳細はこちらで説明しています
DOSでの浮動小数点演算のディスパッチにも使用されます。
一部のコンパイラはCD xx
、x87 浮動小数点命令の代わりに 0x34 ~ 0x3B の範囲の xx を出力します。CD
は命令のオペコードであるため、int
割り込み 34h-3Bh にジャンプし、x87 コプロセッサが使用できない場合はソフトウェアでその命令をエミュレートします。それ以外の場合、割り込みハンドラーはこれらの 2 バイトを置き換えて9B Dx
、後の実行がエミュレーションなしで x87 によって直接処理されるようにします。
MS-DOS での x87 浮動小数点エミュレーションのプロトコルは何ですか?
別の使用法は、実行時にコードを最適化することです
たとえば、可変ビット シフトのない (または非常に遅い) アーキテクチャでは、制御前の命令でシフト カウントを含む即値フィールドを変更することにより、シフト カウントがかなり前からわかっている場合に、定数シフトのみを使用してエミュレートできます。その命令に到達し、実行のためにキャッシュがロードされる前に
また、異なる (マイクロ) アーキテクチャに複数のバージョンがある場合に、関数呼び出しを最適化されたバージョンに変更するためにも使用できます。たとえば、同じ関数がスカラー、SSE2、AVX、AVX-512 で記述されていて、現在の CPU に応じて最適なものを選択します。これは、コード ディスパッチャによって起動時に設定される関数ポインタを使用して簡単に実行できますが、CPU に悪影響を与える間接的なレベルがもう 1 つあります。一部のコンパイラは関数のマルチバージョン化をサポートしていますこれは自動的に異なるバージョンにコンパイルされ、ロード時にリンカーが関数アドレスを目的のアドレスに修正します。しかし、コンパイラとリンカのサポートがなく、間接化も望まない場合はどうでしょうか? 関数ポインターを変更する代わりに、起動時に呼び出し命令を自分で変更するだけです。現在、呼び出しはすべて静的であり、CPU によって正しく予測できます。
継続的に更新されるデータベースに対して統計分析を実行します。私の統計モデルは、利用可能になる新しいデータに対応するために、コードが実行されるたびに書き込まれ、書き直されます。
Linux カーネルには、まさにそれを行うロード可能なカーネル モジュールがあります。
Emacs にもこの機能があり、私はいつもそれを使用しています。
動的プラグイン アーキテクチャをサポートするものは、基本的に実行時にコードを変更することです。
これの最良のバージョンは Lisp マクロかもしれません。単なるプリプロセッサである C マクロとは異なり、Lisp では常にプログラミング言語全体にアクセスできます。これは Lisp で最も強力な機能であり、他の言語には存在しません。
私は決して専門家ではありませんが、Lisp の専門家の 1 人にそれについて話してもらいましょう。Lisp が最も強力な言語であると彼らが言うのには理由があります。