120

実行時コードの変更(実行時に自身のコードを変更するプログラム)の正当な(スマートな)使用法を思いつくことができますか?

最近のオペレーティングシステムは、この手法が検出を回避するためにウイルスによって使用されているため、これを実行するプログラムに眉をひそめているようです。

私が考えることができるのは、コンパイル時に知ることができない何かを実行時に知ることによって、いくつかのコードを削除または追加する、ある種の実行時最適化です。

4

16 に答える 16

118

コード変更には多くの有効なケースがあります。実行時にコードを生成すると、次の場合に役立ちます。

コードが実行時にコードに変換される場合があります(これは動的バイナリ変換と呼ばれます)。

  • AppleのRosettaのようなエミュレーターは、この手法を使用してエミュレーションを高速化します。もう1つの例は、Transmetaのコードモーフィングソフトウェアです。
  • ValgrindPinのような洗練されたデバッガーやプロファイラーは、実行中にコードをインストルメント化するためにそれを使用します。
  • x86命令セットに拡張が行われる前は、 VMWareなどの仮想化ソフトウェアは仮想マシン内で特権x86コードを直接実行できませんでした。代わりに、問題のある命令をその場でより適切なカスタムコードに変換する必要がありました。

コード変更を使用して、命令セットの制限を回避できます。

  • コンピュータにサブルーチンから戻る命令や間接的にメモリをアドレス指定する命令がなかった時代がありました(ずっと前に、私は知っています)。自己変更コードは、サブルーチン、ポインター、および配列を実装する唯一の方法でした。

コード変更のその他のケース:

  • 多くのデバッガーは、ブレークポイントを実装するために命令を置き換えます。
  • 一部のダイナミックリンカは、実行時にコードを変更します。この記事では、Windows DLLの実行時の再配置に関する背景情報を提供します。これは、事実上、コード変更の形式です。
于 2011-04-04T09:09:01.470 に答える
35

これは、コンピューター グラフィックス、特に最適化目的のソフトウェア レンダラーで行われています。実行時に多くのパラメータの状態が調べられ、最適化されたバージョンのラスタライザ コードが生成され (多くの条件を削除する可能性があります)、三角形などのグラフィックス プリミティブをより高速にレンダリングできます。

于 2011-04-04T07:22:22.453 に答える
23

正当な理由の 1 つは、asm 命令セットに、自分で作成できる必要な命令が不足しているためです。例: x86 では、レジスタ内の変数への割り込みを作成する方法はありません (たとえば、ax で割り込み番号を使用して割り込みを作成します)。オペコードにコード化された const 番号のみが許可されました。自己変更コードを使用すると、この動作をエミュレートできます。

于 2011-04-04T07:31:51.060 に答える
17

一部のコンパイラは、静的変数の初期化にこれを使用して、後続のアクセスの条件付きのコストを回避していました。つまり、最初に実行されたときに操作なしでそのコードを上書きすることにより、「このコードを1回だけ実行する」を実装します。

于 2011-04-04T07:47:15.610 に答える
17

多くの場合があります:

  • ウイルスは通常、実行前にコードを「難読化解除」するために自己変更コードを使用しますが、その手法は、リバース エンジニアリング、クラッキング、および不要なハッキングを苛立たせるのにも役立ちます。
  • 場合によっては、ランタイム中の特定の時点 (構成ファイルを読み取った直後など) で、プロセスの残りの存続期間中、特定のブランチが不必要ではなく常に実行されるか、まったく実行されないことがわかっている場合があります。変数をチェックして分岐する方法を決定すると、それに応じて分岐命令自体を変更できます
    • たとえば、仮想ディスパッチを特定の呼び出しに置き換えることができるように、可能な派生型の 1 つだけが処理されることが判明する場合があります。
    • 利用可能なハードウェアを検出すると、一致するコードの使用がハードコーディングされる場合があります
  • 不要なコードは、no-op 命令またはその上へのジャンプに置き換えるか、コードの次のビットを直接所定の位置にシフトすることができます (位置に依存しないオペコードを使用する場合はより簡単です)。
  • 独自のデバッグを容易にするために記述されたコードは、戦略的な場所でデバッガーによって予期されるトラップ/シグナル/割り込み命令を挿入する可能性があります。
  • ユーザー入力に基づく一部の述語式は、ライブラリによってネイティブ コードにコンパイルされる場合があります。
  • 実行時まで表示されないいくつかの単純な操作をインライン化する (たとえば、動的にロードされたライブラリから)...
  • 自己インストルメンテーション/プロファイリング ステップを条件付きで追加する
  • クラックは、それらをロードするコードを変更するライブラリとして実装できます (正確には「自己」変更ではありませんが、同じテクニックとパーミッションが必要です)。
  • ...

一部の OS のセキュリティ モデルは、ルート/管理者権限なしでは自己変更コードを実行できないことを意味するため、汎用的な使用には実用的ではありません。

ウィキペディアから:

厳密な W^X セキュリティを備えたオペレーティング システムで実行されているアプリケーション ソフトウェアは、書き込みが許可されているページで命令を実行できません。メモリへの命令の書き込みと、後でそれらの命令の実行の両方が許可されているのは、オペレーティング システム自体だけです。

このような OS では、Java VM のようなプログラムでさえ、JIT コードを実行するために root/admin 権限が必要です。(詳細については、 http://en.wikipedia.org/wiki/W%5EXを参照してください)

于 2011-04-04T07:29:43.977 に答える
16

Synthesis OSは基本的に、API 呼び出しに関してプログラムを部分的に評価し、OS コードを結果に置き換えました。主な利点は、多くのエラー チェックが不要になったことです (プログラムが OS にばかげたことを要求しない場合は、チェックする必要がないため)。

はい、これはランタイム最適化の例です。

于 2011-04-04T07:21:40.977 に答える
9

何年も前に、私は自己修正コードをデバッグしようと朝を過ごしました。ある命令が次の命令のターゲットアドレスを変更しました。つまり、分岐アドレスを計算していました。これはアセンブリ言語で書かれており、一度に 1 命令ずつプログラムを実行すると完全に機能しました。しかし、プログラムを実行すると失敗しました。最終的に、マシンがメモリから 2 つの命令をフェッチしていて、(命令がメモリに配置されていたため) 変更していた命令が既にフェッチされていたため、マシンは変更されていない (正しくない) バージョンの命令を実行していたことに気付きました。もちろん、私がデバッグしていたときは、一度に 1 つの命令しか実行していませんでした。

私のポイントは、自己変更コードはテスト/デバッグが非常に厄介である可能性があり、多くの場合、マシンの動作 (ハードウェアまたは仮想) に関する隠れた仮定があります。さらに、システムは、(現在の) マルチコア マシンで実行されているさまざまなスレッド/プロセス間でコード ページを共有することはできませんでした。これにより、仮想メモリなどの利点の多くが失われます。また、ハードウェア レベルで行われる分岐の最適化も無効になります。

(注 - 私は自己変更コードのカテゴリに JIT を含めていません。JIT はコードのある表現から別の表現に変換しており、コードを変更していません)

全体として、それはただの悪い考えです - 本当にきちんとしていて、本当にあいまいですが、本当に悪いです。

もちろん、8080 バイトと最大 512 バイトのメモリしかない場合は、そのような方法に頼る必要があるかもしれません。

于 2011-04-04T20:56:29.080 に答える
7

オペレーティング システム カーネルから見ると、すべてのジャスト イン タイム コンパイラおよびリンカー ランタイムは、プログラム テキストの自己修正を実行します。顕著な例は、Google の V8 ECMA Script Interpreter です。

于 2011-04-04T08:27:50.600 に答える
5

自己変更コード (実際には「自己生成」コード) のもう 1 つの理由は、パフォーマンスのためにジャストインタイム コンパイル メカニズムを実装することです。たとえば、代数式を読み取り、入力パラメーターの範囲でそれを計算するプログラムは、計算を記述する前にマシンコードで式を変換する場合があります。

于 2011-04-04T08:43:39.357 に答える
5

ハードウェアとソフトウェアの間に論理的な違いがないことは古い栗毛を知っています... コードとデータの間に論理的な違いはないと言うこともできます。

自己変更コードとは何ですか? データではなくコマンドとして解釈できるように、値を実行ストリームに入れるコード。確かに、関数型言語には実際には違いがないという理論的な観点があります。私は、命令型言語とコンパイラ/インタープリタで、同等の地位を前提とせずに、これを簡単な方法で行うことができると言っています。

私が言及しているのは、データがプログラムの実行パスを変更できるという実際的な意味です (ある意味では、これは非常に明白です)。プログラムがコマンドからコマンドに移動するのと同じように、解析中にトラバースし、状態から状態に移動する (および他の変数を変更する) テーブル (データの配列) を作成するコンパイラ-コンパイラのようなものを考えています。 、プロセス内の変数を変更します。

したがって、コンパイラがコード空間を作成し、完全に分離されたデータ空間 (ヒープ) を参照する通常のインスタンスであっても、データを変更して実行パスを明示的に変更することができます。

于 2011-04-04T14:24:40.537 に答える
4

進化を使って最適なアルゴリズムを作成するプログラムを実装しました。自己修正コードを使用して DNA 設計図を修正しました。

于 2011-04-04T13:01:46.177 に答える
2

使用例の 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 によって正しく予測できます。

于 2013-11-14T02:46:50.757 に答える
1

継続的に更新されるデータベースに対して統計分析を実行します。私の統計モデルは、利用可能になる新しいデータに対応するために、コードが実行されるたびに書き込まれ、書き直されます。

于 2011-04-05T00:35:35.480 に答える
0

Linux カーネルには、まさにそれを行うロード可能なカーネル モジュールがあります。

Emacs にもこの機能があり、私はいつもそれを使用しています。

動的プラグイン アーキテクチャをサポートするものは、基本的に実行時にコードを変更することです。

于 2011-04-04T17:10:33.133 に答える
-1

これの最良のバージョンは Lisp マクロかもしれません。単なるプリプロセッサである C マクロとは異なり、Lisp では常にプログラミング言語全体にアクセスできます。これは Lisp で最も強力な機能であり、他の言語には存在しません。

私は決して専門家ではありませんが、Lisp の専門家の 1 人にそれについて話してもらいましょう。Lisp が最も強力な言語であると彼らが言うのには理由があります。

于 2011-04-04T14:39:50.667 に答える