Cコード中にマシン語を埋め込んで実行する。

なんだろう、何百番煎じな気がする。もうお茶も出なくなってお湯ですよ、お湯。

フォロワーの @pasberth さんが、「JIT ってどういう仕掛けになってるの!」といっていたので、
メモリ上にバイトコードを置いて実行する方法ということでちょろっと書いたものです。

とりあえず、ピタゴラスの定理をアセンブラで書いて、
C でぺけぺけ。

ひとまず、私の作業環境である Windows での動作確認。

440411299

そいでもって、x86 なら、どのプラットフォームでもバイナリ部分は変えなくても走るよ! ってのを示すために、
Mac を立ち上げて実行。

440460787

 


ソースコードは下記のような感じです。@pasberth さん用に書いたので、Mac 向けソースになってます
Windows で動かすには、先頭の vm_protect を VirtualProtect の形式に書き換えれば動きます。

 

#include <mach/mach.h>
#include <stdio.h>

/************************************************
 とりあえず、中のデータは16進で記録してあって、

リトルエンディアンですから、
 0x01020304 は、メモリ上に 0x04,0x03,0x02,0x01 の順で格納されます。
要は逆順です。

また、命令のオーダーについては、必ずしも何バイトということはありませんが、
最長命令は 32bit アーキテクチャですんで 4 バイト ( 32 bit / 8 = 4 byte. )

見やすくするために、2バイトや1バイト命令に関しては、
0x90 (NOP) [NOP = なにもしない] を挿入して、アライメント(区切り)を合わせてあります。

まー、適当に見てもらえれば。

************************************************/

int main(){
	unsigned long d;
	int a = 0,b = 0,c = 0;
	unsigned long code[32] = {0};
	
	printf("vm_protect : %s\n\n",vm_protect(
		mach_task_self(),
		(vm_address_t)code,32 * sizeof(long),
		FALSE,
			VM_PROT_READ | 
			VM_PROT_WRITE | 
			VM_PROT_EXECUTE
		) == KERN_SUCCESS ? "[OK]" : "[FALSE]");

	printf("INPUT [a,b,c] : ");
	scanf("%d,%d,%d",&a,&b,&c);

	code[0]  = 0x0424448B;	// MOV EAX,DWORD PTR SS:[ESP+4]
	code[1]  = 0x08244C8B;	// MOV ECX,DWORD PTR SS:[ESP+8]
	code[2]  = 0x9090C13B;	// CMP EAX,ECX
	code[3]  = 0x9090047E;	// JLE SHORT ; EIP+4
	code[4]  = 0x9090C88B;	// MOV ECX,EAX
	code[5]  = 0x0C24548B;	// MOV EDX,DWORD PTR SS:[ESP+C]
	code[6]  = 0x9090CA3B;	// CMP ECX,EDX
	code[7]  = 0x9090147E;	// JLE SHORT ; EIP+C
	code[8]  = 0x90909056;	// PUSH ESI
	code[9]  = 0x9090F28B;	// MOV ESI,EDX
	code[10] = 0x9090D18B;	// MOV EDX,ECX
	code[11] = 0x9090CE8B;	// MOV ECX,ESI
	code[12] = 0x9090905E;	// POP ESI
	code[13] = 0x90C0AF0F;	// IMUL EAX,EAX
	code[14] = 0x90C9AF0F;	// IMUL ECX,ECX
	code[15] = 0x90D2AF0F;	// IMUL EDX,EDX
	code[16] = 0x9090C103;	// ADD EAX,ECX
	code[17] = 0x9090C22B;	// SUB EAX,EDX
	code[18] = 0x9090D8F7;	// NEG EAX
	code[19] = 0x9090C01B;	// SBB EAX,EAX
	code[20] = 0x90909040;	// INC EAX
	code[21] = 0x909090C3;	// RETN
	
	printf("\nRETURN : %s\n",
		((int(*)(int,int,int))code)(a,b,c) == 0 ? "[FALSE]" : "[OK]");
	
	return 0;
}

 

中のバイトコード部分は、ソースコード中のコメントを見てもらえれば。

本来、こういうページ領域上にバイトコードを置いて、それを呼び出そうとしても、
実行できないように設定されています。

(実行できるようになっていると、プロセスに脆弱性があったとき、悪意のあるコードを攻撃者から送信されて、実行されてしまう危険性が格段に上がる)

ですが、さすがに完全に実行できないと、Java VM をはじめとしたソフトウェアが稼働できなくなってしまうため、
OSから実行可能にフラグを書き換えるAPIが提供されています。

OS

関数

Windows VirtualProtect
MSDN : http://msdn.microsoft.com/ja-jp/library/cc430214.aspx
Mac OS X vm_protect
The GNU Mach Reference Manual :
http://www.gnu.org/software/hurd/gnumach-doc/Memory-Attributes.html
Darwin :
http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/vm_protect.html
Linux mprotect
Man page of MPROTECT :
http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/mprotect.2.html

実行可能なバイトコードは、x86 プロセッサ共通なので、同じプロセッサならどれでも動きますが、
各オペレーティングシステムに合わせて、上記に挙げた関数を利用して、実行可能に設定する必要があります。

 

実際の JIT コンパイラなどでは、各プラットフォームごとのライブラリや、
APIの差異の吸収が必要になって、結構面倒なことになりますが、

今回の例はきわめて単純な例を使って、 x86 プロセッサ上なら同じコードが動くということを示すのと同時に、
Java などでは、こういう感じでたぶんコードを実行しているということで。

(mono はこれ使ってる感濃厚。 ソースコードを眺めていた感じでは。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

This site uses Akismet to reduce spam. Learn how your comment data is processed.