2011-01-27 14 views
43

私は最近取り組んできた趣味の仮想マシン用のJITコンパイラを書いています。私はアセンブリのビットを知っています(私は主にCプログラマですが、私は理解していないオペコードのリファレンスを使ってほとんどのアセンブリを読むことができますし、いくつかの簡単なプログラムを書くことができます)。私がオンラインで見つけた自己修正コードの実行時http://asm.sourceforge.net/articles/smc.htmlx86アセンブリで自己修正コードを書く方法

設けられたプログラム例が明確に説明されているいずれも、約4の異なる修飾を行います

これはその一例です。 Linuxカーネルの割り込みは数回使用され、説明も詳細もされていません。 (著者は、割り込みを呼び出す前にデータをいくつかのレジスタに移動しましたが、引数を渡していたと仮定していますが、これらの引数は全く説明されていません。自己修正プログラムのコードで最も簡単な例です。私が見ることができ、x86アセンブリ内の自己修正コードがどのように記述されなければならないか、そしてどのように動作するかを理解するために使用するもの。あなたが私に指摘できるリソースがあるか、これを適切に実証できる例はありますか?

私はアセンブラとしてNASMを使用しています。

編集:このコードはLinuxでも実行しています。

+1

http://linux.die.net/man/2/mprotectは、mprotectの引数が何であるかを説明する必要があります。呼び出す関数IDはEAXで渡され、次の引数はEBX ECXとEDXで渡されます。 – KitsuneYMG

答えて

44

うわー、これは予想以上に痛いものでした痛みの100%は、プログラムが上書きされたりデータが実行されないようにするためのLinuxでした。

下記の2つのソリューション。そしてたくさんのグーグルが関わっていたので、いくらか単純な命令バイトを入れて実行すると、mprotectとページサイズの調整がgoogle検索で行われました。

自己修正コードは簡単です。プログラムを実行するか、少なくとも2つの単純な関数をコンパイルして逆アセンブルすると、それらの命令のオペコードが得られます。またはアセンブラのブロックをコンパイルするためにnasmを使用してください。これから、即座にeaxにロードして戻すオペコードを決定しました。

理想的には、これらのバイトをいくつかのラムに置き、そのラムを実行します。 linuxにこれをさせるためには、保護を変更する必要があります。つまり、mmapページにポインタを合わせて送る必要があります。そのため、必要以上に割り当てる必要があります。ページ境界にあるその割り当て内のアライメントされたアドレスを見つけて、そのアドレスからmprotectし、そのメモリを使用してオペコードを入れて実行します。

2番目の例では、プログラムにコンパイルされた既存の関数を使用しています。これは、単に保護メカニズムをポイントしてバイトを変更できないため、書き込みから保護を解除する必要があります。したがって、そのアドレスと修正されるコードをカバーするのに十分なバイトで、前のページ境界呼び出しmprotectにバックアップする必要があります。次に、あなたが望む任意の方法でその関数のバイト/オペコードを変更することができます(使用し続ける関数に溢れない限り)。この場合、fun()が機能していることが分かります。値を返すだけで値を変更し、再度呼び出すと変更されました。

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

unsigned char *testfun; 

unsigned int fun (unsigned int a) 
{ 
    return(a+13); 
} 

unsigned int fun2 (void) 
{ 
    return(13); 
} 

int main (void) 
{ 
    unsigned int ra; 
    unsigned int pagesize; 
    unsigned char *ptr; 
    unsigned int offset; 

    pagesize=getpagesize(); 
    testfun=malloc(1023+pagesize+1); 
    if(testfun==NULL) return(1); 
    //need to align the address on a page boundary 
    printf("%p\n",testfun); 
    testfun = (unsigned char *)(((long)testfun + pagesize-1) & ~(pagesize-1)); 
    printf("%p\n",testfun); 

    if(mprotect(testfun, 1024, PROT_READ|PROT_EXEC|PROT_WRITE)) 
    { 
     printf("mprotect failed\n"); 
     return(1); 
    } 

    //400687: b8 0d 00 00 00   mov $0xd,%eax 
    //40068d: c3      retq 

    testfun[ 0]=0xb8; 
    testfun[ 1]=0x0d; 
    testfun[ 2]=0x00; 
    testfun[ 3]=0x00; 
    testfun[ 4]=0x00; 
    testfun[ 5]=0xc3; 

    ra=((unsigned int (*)())testfun)(); 
    printf("0x%02X\n",ra); 


    testfun[ 0]=0xb8; 
    testfun[ 1]=0x20; 
    testfun[ 2]=0x00; 
    testfun[ 3]=0x00; 
    testfun[ 4]=0x00; 
    testfun[ 5]=0xc3; 

    ra=((unsigned int (*)())testfun)(); 
    printf("0x%02X\n",ra); 


    printf("%p\n",fun); 
    offset=(unsigned int)(((long)fun)&(pagesize-1)); 
    ptr=(unsigned char *)((long)fun&(~(pagesize-1))); 


    printf("%p 0x%X\n",ptr,offset); 

    if(mprotect(ptr, pagesize, PROT_READ|PROT_EXEC|PROT_WRITE)) 
    { 
     printf("mprotect failed\n"); 
     return(1); 
    } 

    //for(ra=0;ra&lt;20;ra++) printf("0x%02X,",ptr[offset+ra]); printf("\n"); 

    ra=4; 
    ra=fun(ra); 
    printf("0x%02X\n",ra); 

    ptr[offset+0]=0xb8; 
    ptr[offset+1]=0x22; 
    ptr[offset+2]=0x00; 
    ptr[offset+3]=0x00; 
    ptr[offset+4]=0x00; 
    ptr[offset+5]=0xc3; 

    ra=4; 
    ra=fun(ra); 
    printf("0x%02X\n",ra); 

    return(0); 
} 
+1

Linuxだけでなく最新のOSも書き込み可能なメモリの実行を保護します –

+0

これはWindowsで行うことができます。つまり、RAMのページを保護しない、または死のブルースクリーンに陥るでしょうか?私は自己修正暗号化システムを作成するためにこのメソッドを使いたいと思う。 – tentimes

+0

32ビットのArch Linuxではコードはうまく機能しましたが、64ビットのRHEL(64ビットELF、もちろん32ビットELFを使用している場合)で失敗しました。これがRHELなどで追加のメモリ保護と関係しているかどうかはわかりません。出力されました: '' ' 0x9a00008 0x9a01000 MPROTECTは ' '' – Alexander

3

GNU lightningのようなプロジェクトを見ることもできます。簡略化されたRISC型のマシンのコードを与え、正しいマシンを動的に生成します。

非常に現実的な問題は、外国の図書館とのインタフェイスです。おそらく、少なくともいくつかのシステムレベルの呼び出し/操作をサポートして、VMを有効にする必要があります。 Kitsuneのアドバイスは、システムレベルの呼び出しについて考えさせる良いスタートです。おそらくmprotectを使用して、変更したメモリが法的に実行可能になることを確認します。 (@KitsuneYMG)

Cで書かれた動的ライブラリへの呼び出しを許可するFFIでは、OS固有の詳細をたくさん隠すのに十分なはずです。これらの問題はすべてあなたのデザインにかなり影響を与える可能性がありますので、早期に考えることをお勧めします。

0

私は自己修正コードを書いたことはありませんが、私はそれがどのように動作するかについて基本的な理解があります。基本的には、実行したい命令をメモリに書き込んでそこにジャンプします。プロセッサはあなたが書いたバイトを解釈し、それらを実行しようとします(試みます)。例えば、ウイルスおよびアンチコピープログラムは、この技術を使用することができる。
システムコールに関しては正しいと思いますが、引数はレジスタ経由で渡されます。 linuxシステムコールとその引数の参照については、hereをチェックしてください。

8

あなたはJITコンパイラを書いているので、おそらくあなたは、実行時に実行可能コードを生成したい、自己修正コードを望んでいません。これらは2つの異なるものです。自己修正コードは、すでにの実行を開始した後に変更されたコードです。自己修正コードは現代のプロセッサで大きなパフォーマンス上のペナルティを持ち、したがってJITコンパイラにとって望ましくないでしょう。

ランタイムで実行可能コードを生成するには、PROT_EXECおよびPROT_WRITEパーミッションを持つメモリをmmap()するだけです。 dwelchが上で行ったように、あなた自身が割り当てたメモリでmprotect()を呼び出すこともできます。

+0

自己書き換えコードは、常に最新のプロセッサ上のパフォーマンスの低下を持っていません失敗しました。あなたは何を変更するか慎重にし、CPUキャッシュが同期していることとブランチ保護が変更されていないことを確認する必要があります。それらを変更すると、あなたのパフォーマンスが変わります。 – Beachhouse

+0

自己修正が比較的頻繁に起こらず、かつ/または現在実行されていないコードの一部で一時的なパフォーマンスが無視できる場合は、 –

3

上記の例に基づいて少し簡単な例です。 dwelchに感謝してくれました。

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

char buffer [0x2000]; 
void* bufferp; 

char* hola_mundo = "Hola mundo!"; 
void (*_printf)(const char*,...); 

void hola() 
{ 
    _printf(hola_mundo); 
} 

int main (void) 
{ 
    //Compute the start of the page 
    bufferp = (void*)(((unsigned long)buffer+0x1000) & 0xfffff000); 
    if(mprotect(bufferp, 1024, PROT_READ|PROT_EXEC|PROT_WRITE)) 
    { 
     printf("mprotect failed\n"); 
     return(1); 
    } 
    //The printf function has to be called by an exact address 
    _printf = printf; 

    //Copy the function hola into buffer 
    memcpy(bufferp,(void*)hola,60 //Arbitrary size); 


    ((void (*)())bufferp)(); 

    return(0); 
} 
+0

'hola()'に位置独立コードを生成しないと、これは大幅に失敗する可能性があります。 – CoffeeandCode

+0

これは動作していません!セグフルト! – ANTHONY

0

これはAT & Tアセンブリに書かれています。プログラムの実行からわかるように、自己修正コードのために出力が変化しました。

.globl f4 
.data  

f4: 
    pushl %ebp  #standard function start 
    movl %esp,%ebp 

f: 
    movl $1,%eax # moving one to %eax 
    movl $0,f+1 # overwriting operand in mov instuction over 
       # the new immediate value is now 0. f+1 is the place 
       # in the program for the first operand. 

    popl %ebp # standard end 
    ret 

C試験プログラム:

コンパイル:例では、32ビットマシン

Aessemblyに動作するためのgcc -m32 modify.s modify.c

-m32オプションが使用されています:

#include <stdio.h> 

// assembly function f4 
extern int f4(); 
int main(void) { 
int i; 
for(i=0;i<6;++i) { 
printf("%d\n",f4()); 
} 
return 0; 
} 

出力:

1 
0 
0 
0 
0 
0 
関連する問題