web-dev-qa-db-ja.com

Cで自己変更コードを書く方法は?

わずかな変更であっても、継続的に変化するコードを記述したいと考えています。

たとえば、多分


for i in 1 to  100, do 
begin
   x := 200
   for j in 200 downto 1, do
    begin
       do something
    end
end

最初の反復後に行x := 200を他の行x := 199に変更し、次の反復後にx := 198に変更する必要があると想定します。

そのようなコードを書くことは可能ですか?そのためにインラインアセンブリを使用する必要がありますか?

編集:私がCでそれをしたい理由はここにあります:

このプログラムは実験的なオペレーティングシステムで実行され、他の言語からコンパイルされたプログラムの使用方法がわかりません。このようなコードが必要な本当の理由は、このコードが仮想マシンのゲストオペレーティングシステムで実行されているためです。ハイパーバイザーは、コードのチャンクを変換するバイナリトランスレーターです。トランスレータはいくつかの最適化を行います。コードのチャンクを一度だけ変換します。次に同じチャンクがゲストで使用されるとき、翻訳者は以前に翻訳された結果を使用します。コードがその場で変更されると、翻訳者はそれに気づき、以前の翻訳を古いものとしてマークします。したがって、同じコードの再変換を強制します。これは私が達成したいことであり、翻訳者に多くの翻訳を強いることです。通常、これらのチャンクは、分岐命令(ジャンプ命令など)の間の命令です。自己変更コードはこれを実現する素晴らしい方法だと思います。

27
AnkurVj

独自の自己変更コードを作成できるCで仮想マシンを作成することを検討してください。

自己変更実行可能ファイルを作成する場合は、対象となるオペレーティングシステムに大きく依存します。インメモリプログラムイメージを変更することで、目的のソリューションにアプローチできます。そのためには、プログラムのコードバイトのメモリ内アドレスを取得します。次に、このメモリ範囲でオペレーティングシステムの保護を操作して、アクセス違反や '' 'SIG_SEGV' ''が発生することなくバイトを変更できるようにします。最後に、ポインター(おそらく '' 'unsigned char *' ''ポインター、おそらくRISCマシンのように '' 'unsigned long *' '')を使用して、コンパイルされたプログラムのオペコードを変更します。

重要な点は、ターゲットアーキテクチャのマシンコードを変更することです。実行中のCコードには標準形式はありません。Cはコンパイラーへのテキスト入力ファイルの仕様です。

12
Heath Hunnicutt

それは可能ですが、おそらく移植可能ではありません。実行中のコードやOSによって導入されたその他の障害のために、読み取り専用のメモリセグメントと競合する必要があるかもしれません。

9
Vatine

申し訳ありませんが、少し遅れて回答しましたが、あなたが探しているものを正確に見つけたと思います: https://shanetully.com/2013/12/writing-a-self-mutating-x86_64-c-program /

この記事では、アセンブリをスタックに注入することにより、定数の値を変更します。次に、スタック上の関数のメモリを変更してシェルコードを実行します。

以下は最初のコードです:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>

void foo(void);
int change_page_permissions_of_address(void *addr);

int main(void) {
    void *foo_addr = (void*)foo;

    // Change the permissions of the page that contains foo() to read, write, and execute
    // This assumes that foo() is fully contained by a single page
    if(change_page_permissions_of_address(foo_addr) == -1) {
        fprintf(stderr, "Error while changing page permissions of foo(): %s\n", strerror(errno));
        return 1;
    }

    // Call the unmodified foo()
    puts("Calling foo...");
    foo();

    // Change the immediate value in the addl instruction in foo() to 42
    unsigned char *instruction = (unsigned char*)foo_addr + 18;
    *instruction = 0x2A;

    // Call the modified foo()
    puts("Calling foo...");
    foo();

    return 0;
}

void foo(void) {
    int i=0;
    i++;
    printf("i: %d\n", i);
}

int change_page_permissions_of_address(void *addr) {
    // Move the pointer to the page boundary
    int page_size = getpagesize();
    addr -= (unsigned long)addr % page_size;

    if(mprotect(addr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
        return -1;
    }

    return 0;
}
7
Labo

これは良いスタートです。基本的にCのLISP機能:

http://nakkaya.com/2010/08/24/a-micro-manual-for-LISP-implemented-in-c/

5
CreativeJourney

必要な自由度によっては、関数ポインターを使用して目的を達成できる場合があります。ジャンプコードポイントとして疑似コードを使用して、ループインデックスxが変化したときに、その変数iをさまざまな方法で変更する場合を考えてみます。次のようなことができます。

#include <stdio.h>

void multiply_x (int * x, int multiplier)
{
    *x *= multiplier;
}

void add_to_x (int * x, int increment)
{
    *x += increment;
}

int main (void)
{
    int x = 0;
    int i;

    void (*fp)(int *, int);

    for (i = 1; i < 6; ++i) {
            fp = (i % 2) ? add_to_x : multiply_x;

            fp(&x, i);

            printf("%d\n", x);
    }

    return 0;
}

プログラムをコンパイルして実行すると、出力は次のようになります。

1
2
5
20
25

明らかに、これは、実行するたびにxを使用して実行する処理が有限の数である場合にのみ機能します。変更を永続的にするため(これは、「自己変更」で必要なものの一部です)、関数ポインター変数をグローバルまたは静的にする必要があります。この種のことを達成するためのより単純で明確な方法がしばしばあるため、私がこのアプローチを本当に推奨できるかどうかはわかりません。

5
Pillsy

CでLISPを実装し、それを使用することについての提案は、移植性の懸念のために堅実です。しかし、本当にそうしたいのであれば、プログラムのバイトコードをメモリにロードしてからそれに戻ることで、多くのシステムで別の方向に実装することもできます。

これを行うには、いくつかの方法があります。 1つの方法は、バッファオーバーフローエクスプロイトを使用することです。もう1つは、mprotect()を使用してコードセクションを書き込み可能にしてから、コンパイラーが作成した関数を変更することです。

このような手法はプログラミングの課題や難読化された競争にとっては楽しいものですが、Cが未定義の動作と見なしているものを悪用しているという事実と、コードがどれほど読みにくいかを考えると、本番環境では回避するのが最善です。

3
Daniel Papasian

(Cのようにハードコンパイルおよびリンクされていない)自己解釈言語の方が適している場合があります。 Perl、javascript、PHPには、目的に適した悪質なeval()関数があります。これにより、常に変更して実行する一連のコードを作成できますeval()経由。

2
Jonathan M

標準C11では、読み取り n157 )、自己変更コードを書き込むことはできません(少なくとも 未定義の動作なし) )。概念的には、少なくとも コードセグメント は読み取り専用です。

動的リンカー を使用して、プログラムのコードを plugins で拡張することを検討してください。これには、オペレーティングシステム固有の機能が必要です。 POSIXでは、 dlopen を使用します(新しくロードされた関数ポインターを取得するには、おそらく dlsym を使用します)。次に、関数ポインターを新しいポインターのアドレスで上書きできます。

おそらく、いくつかの JIT-compiling ライブラリ( libgccjit または asmjit など)を使用して目標を達成できます。新しい関数アドレスを取得して、関数ポインターに入れます。

Cコンパイラは、特定の関数呼び出しまたはジャンプのためにさまざまなサイズのコードを生成できるため、マシン固有の方法で上書きすることも脆弱であることを覚えておいてください。

私の友人と私は、コードを自己変更するゲームで作業中にこの問題に遭遇しました。ユーザーがx86アセンブリのコードスニペットを書き換えることを許可します。

これには、アセンブラーと逆アセンブラーの2つのライブラリーを利用する必要があるだけです。

FASMアセンブラ https://github.com/ZenLulz/Fasm.NET

UDIS86逆アセンブラー: https://github.com/vmt/udis86

逆アセンブラを使用して命令を読み取り、ユーザーがそれらを編集して、アセンブラで新しい命令をバイトに変換し、メモリに書き戻します。書き戻しでは、ウィンドウでVirtualProtectを使用してページの権限を変更し、コードを編集できるようにする必要があります。 Unixでは、代わりにmprotectを使用する必要があります。

私はそれをどのようにしたかについての記事をここに投稿しました:

https://medium.com/squallygame/how-we-wrote-a-self-hacking-game-in-c-d8b9f97bfa99

ここにサンプルコードと同様に:

https://github.com/Squalr/SelfHackingApp

これらの例はC++を使用するWindows上にありますが、クロスプラットフォームとCのみを作成するのは非常に簡単です。

0
Zachary Canann