web-dev-qa-db-ja.com

メモリ内でマシンコードを実行する

メモリに保存されているマシンコードを実行する方法を理解しようとしています。

私は次のコードを持っています:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    FILE* f = fopen(argv[1], "rb");

    fseek(f, 0, SEEK_END);
    unsigned int len = ftell(f);
    fseek(f, 0, SEEK_SET);

    char* bin = (char*)malloc(len);
    fread(bin, 1, len, f);

    fclose(f);

    return ((int (*)(int, char *)) bin)(argc-1, argv[1]);
}

上記のコードはGCCで正常にコンパイルされますが、次のようにコマンドラインからプログラムを実行しようとすると次のようになります。

./my_prog /bin/echo hello

プログラムのセグメンテーション違反。コメントアウトするとセグメンテーション違反が停止するため、問題は最後の行にあることがわかりました。

私はまだ関数ポインタに頭を悩ませているので、私はそれを完全に正しく行っているとは思いません。

問題はキャストの誤りですか、それとも他の何かですか?

28

ELFイメージをロードしてから、ELFヘッダーに直接ジャンプしようとしているように見えますか? http://en.wikipedia.org/wiki/Executable_and_Linkable_Format

別のバイナリを実行しようとしている場合は、使用しているプラ​​ットフォームのプロセス作成関数を使用してみませんか?

12
ta.speot.is

書き込み実行権限のあるページが必要です。 UNIXを使用している場合は、mmap(2)およびmprotect(2)を参照してください。 mallocを使用してそれを行うべきではありません。

また、他の人が言ったことを読んでください、あなたはあなたのローダーを使って生のマシンコードを実行することができるだけです。 ELFヘッダーを実行しようとすると、おそらくすべて同じようにセグメンテーション違反が発生します。

返信とダウンモッドの内容について:

1- OPは、マシンコードを実行しようとしていると言ったので、実行可能ファイルを実行するのではなく、それに応答しました。

2-malloc関数とmman関数を混在させない理由を確認してください。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>

int main()
{
    char *a=malloc(10);
    char *b=malloc(10);
    char *c=malloc(10);
    memset (a,'a',4095);
    memset (b,'b',4095);
    memset (c,'c',4095);
    puts (a);
    memset (c,0xc3,10); /* return */

    /* c is not alligned to page boundary so this is NOOP.
     Many implementations include a header to malloc'ed data so it's always NOOP. */
    mprotect(c,10,PROT_READ|PROT_EXEC);
    b[0]='H'; /* oops it is still writeable. If you provided an alligned
    address it would segfault */
    char *d=mmap(0,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);
    memset (d,0xc3,4096);
    ((void(*)(void))d)();
    ((void(*)(void))c)(); /* oops it isn't executable */
    return 0;
}

Linux x86_64でこの動作を正確に表示し、他の実装で必ず発生する他の醜い動作を表示します。

29
jbcreix

mallocの使用は正常に機能します。

これが私の最終的な答えです。元のポスターのコードを使用したことに注意してください。元のコードと同じように、このコードのコンパイル済みバージョンであるディスクからヒープ割り当て領域「bin」にロードしています(名前はargvを使用せずに修正され、値0x674はからです。

objdump -F -D foo|grep -i hoho
08048674 <hohoho> (File Offset: 0x674):

これは、実行時にBFD(Binary File Descriptor library)または他の何かで調べることができます。同じライブラリのセットに静的にリンクされている限り、他のバイナリ(自分だけでなく)を呼び出すことができます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

unsigned char *charp;
unsigned char *bin;

void hohoho()
{
   printf("merry mas\n");
   fflush(stdout);
}

int main(int argc, char **argv)
{
   int what;

   charp = malloc(10101);
   memset(charp, 0xc3, 10101);
   mprotect(charp, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   __asm__("leal charp, %eax");
   __asm__("call (%eax)" );

   printf("am I alive?\n");

   char *more = strdup("more heap operations");
   printf("%s\n", more);

   FILE* f = fopen("foo", "rb");

   fseek(f, 0, SEEK_END);
   unsigned int len = ftell(f);
   fseek(f, 0, SEEK_SET);

   bin = (char*)malloc(len);
   printf("read in %d\n", fread(bin, 1, len, f));
   printf("%p\n", bin);

   fclose(f);
   mprotect(&bin, 10101, PROT_EXEC | PROT_READ | PROT_WRITE);

   asm volatile ("movl %0, %%eax"::"g"(bin));
   __asm__("addl $0x674, %eax");
   __asm__("call %eax" );
   fflush(stdout);

   return 0;
}

ランニング...

co tmp # ./foo
am I alive?
more heap operations
read in 30180
0x804d910
merry mas

[〜#〜] upx [〜#〜] を使用して、ファイルのロード/変更/実行を管理できます。

P.S.以前の壊れたリンクをお詫びします:|

12

一般的な実行可能ファイルには次のものがあります。

  • ヘッダー
  • main(int, char **)の前に呼び出されるエントリコード

1つ目は、通常、ファイルのバイト0が実行可能であるとは期待できないことを意味します。ヘッダーの情報は、ファイルの残りの部分をメモリにロードする方法と、ファイルの実行を開始する場所を示しています。

2つ目は、エントリポイントを見つけたときに、引数をとるC関数のように扱うことは期待できないことを意味します(int, char **)。おそらく、パラメータを使用しない関数として使用できる可能性があります(したがって、呼び出す前に何もプッシュする必要はありません)。ただし、mainに渡されるコマンドライン文字列を作成するためにエントリコードによって使用される環境にデータを入力する必要があります。

特定のOSでこれを手作業で行うと、私を超えた深みになります。しかし、私はあなたがやろうとしていることをするもっと良い方法があると確信しています。外部ファイルをオンオフ操作として実行しようとしていますか、それとも外部バイナリをロードしてその機能をプログラムの一部として処理しようとしていますか?どちらもUnixのCライブラリによって提供されます。

4
Edmund

呼び出し自体ではなく、関数ポインターを介した呼び出しによってジャンプされるのは、セグメンテーション違反を引き起こしているコードである可能性が高くなります。投稿したコードから、binにロードされたコードが有効であると判断する方法はありません。最善の策は、デバッガーを使用し、アセンブラービューに切り替え、returnステートメントを中断し、ステップイン関数呼び出しを使用して、実行する予定のコードが実際に実行されていること、およびそれが有効であることを確認することです。 。

また、すべてを実行するには、コードが位置に依存しないであり、完全に解決されている必要があることにも注意してください。

さらに、プロセッサ/ OSがデータ実行防止を有効にしている場合、その試みはおそらく運命づけられています。いずれにせよ、それはせいぜいお勧めできません。コードのロードはOSの目的です。

3
Clifford

あなたがやろうとしていることは、通訳者がやっていることと似ています。インタプリタがPythonのようなインタープリタ言語で書かれたプログラムを読み取り、そのコードをその場でコンパイルし、実行可能コードをメモリに入れて実行することを除いて。

ジャストインタイムコンパイルについても詳しく読むことをお勧めします。

ジャストインタイムコンパイル
Java HotSpot JITランタイム

興味があれば、 GNU lightninglibJIT などのJITコード生成に使用できるライブラリがあります。ただし、ファイルから読み取ってコードを実行するだけでなく、さらに多くのことを行う必要があります。使用シナリオの例は次のとおりです。

  1. スクリプト言語(おそらくあなた自身の言語)で書かれたプログラムを読んでください。
  2. ソースを解析して、JITライブラリが理解できる中間言語にコンパイルします。
  3. JITライブラリを使用して、ターゲットプラットフォームのCPU用にこの中間表現のコードを生成します。
  4. JITで生成されたコードを実行します。

また、コードを実行するには、mmap()を使用して実行可能コードをプロセスのアドレス空間にマップし、そのページを実行可能としてマークし、そのメモリにジャンプするなどの手法を使用する必要があります。これよりも複雑ですが、Python、Rubyなど)などのスクリプト言語のすべてのインタープリターの下で何が起こっているのかを理解するための良いスタートです。

本の オンラインバージョン " リンカーとローダー "は、オブジェクトファイルの形式、プログラムの実行時に舞台裏で何が行われるか、の役割についての詳細を提供します。リンカーやローダーなど。とても良い読み物です。

2
Sudhanshu

プログラムのロードと実行には、オペレーティングシステムを使用します。

UNIXでは、 exec 呼び出しでこれを実行できます。

質問のスニペットは書き直すことができます。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    return execv(argv[1],argv+2);
}
1
Will

ファイルをdlopen()し、シンボル "main"を検索し、0、1、2、または3個の引数(すべてchar *型)を使用して、pointer-to-function-returning-int-taking-へのキャストを介して呼び出すことができます。 0,1,2、or3-char *

1
haavee

実行可能ファイルには、コードだけではありません。ヘッダー、コード、データ、その他のデータ、これらのものは、OSとそのライブラリによって分離され、メモリのさまざまな領域にロードされます。プログラムファイルをメモリの単一のチャンクにロードして、その最初のバイトにジャンプすることを期待することはできません。

独自の任意のコードを実行しようとしている場合は、ダイナミックライブラリを調べる必要があります。これは、まさにその目的であるためです。

0
Jimbo