FreeBSD/i386 な環境で perl のコードを実行する方法
今日からはじめての FreeBSD プログラミング、ということで、
まず最初に execve() を使って Hello, world! するプログラムを書きました。
8048080: 68 2d 65 00 00 push $0x652d 8048085: 89 e6 mov %esp,%esi 8048087: 68 6c 00 00 00 push $0x6c 804808c: 68 2f 70 65 72 push $0x7265702f 8048091: 68 2f 62 69 6e push $0x6e69622f 8048096: 68 2f 75 73 72 push $0x7273752f 804809b: 89 e7 mov %esp,%edi 804809d: 68 5c 6e 22 00 push $0x226e5c 80480a2: 68 72 6c 64 21 push $0x21646c72 80480a7: 68 2c 20 77 6f push $0x6f77202c 80480ac: 68 65 6c 6c 6f push $0x6f6c6c65 80480b1: 68 74 20 22 48 push $0x48222074 80480b6: 68 70 72 69 6e push $0x6e697270 80480bb: 89 e2 mov %esp,%edx 80480bd: 31 c0 xor %eax,%eax 80480bf: 50 push %eax 80480c0: 52 push %edx 80480c1: 56 push %esi 80480c2: 57 push %edi 80480c3: 89 e1 mov %esp,%ecx 80480c5: 50 push %eax 80480c6: 51 push %ecx 80480c7: 57 push %edi 80480c8: b0 3b mov $0x3b,%al 80480ca: 50 push %eax 80480cb: cd 80 int $0x80 80480cd: 31 db xor %ebx,%ebx 80480cf: 53 push %ebx 80480d0: 31 c0 xor %eax,%eax 80480d2: 40 inc %eax 80480d3: 50 push %eax 80480d4: cd 80 int $0x80
FreeBSD は syscall の呼び出し規約は Linux と違っていて、引数は全部スタック渡して int 80h するみたい。
FreeBSD Assembly Language Programming
kernel: int 80h ; Call kernel ret open: push dword mode push dword flags push dword path mov eax, 5 call kernel add esp, byte 12 ret
call 命令を使いたくない場合は、余分に1回ダミーでpushすれば大丈夫。
open: push dword mode push dword flags push dword path mov eax, 5 push eax ; Or any other dword int 80h add esp, byte 16
これに対して Linux は引数の一部にレジスタを使う(EBX, ECX, EDX …)。EAX の使い方はほぼ同じ。
open: mov eax, 5 mov ebx, path mov ecx, flags mov edx, mode int 80h
FreeBSD で定義されている syscall 番号を EAX レジスタに入れて、int 80h すればシステムコールが実行されます。
$ cat /usr/src/sys/kern/syscalls.master
$FreeBSD: src/sys/kern/syscalls.master,v 1.198.2.5 2006/10/10 13:19:47 rwatson Exp $ ; from: @(#)syscalls.master 8.2 (Berkeley) 1/13/94 ; ; System call name/number master file. ; Processed to created init_sysent.c, syscalls.c and syscall.h. ・・・(略)・・・ 0 AUE_NULL MSTD { int nosys(void); } syscall nosys_args int 1 AUE_EXIT MSTD { void sys_exit(int rval); } exit \ sys_exit_args void 2 AUE_FORK MSTD { int fork(void); } 3 AUE_NULL MSTD { ssize_t read(int fd, void *buf, \ size_t nbyte); } 4 AUE_NULL MSTD { ssize_t write(int fd, const void *buf, \ size_t nbyte); } ・・・(略)・・・ 59 AUE_EXECVE MSTD { int execve(char *fname, char **argv, \ char **envv); } ・・・(略)・・・
とりあえず、勢いで作ってみた NASM のソース貼っておきます
section .text global _start _start: push 0x00652d mov esi, esp push 0x006c push 0x7265702f push 0x6e69622f push 0x7273752f mov edi, esp push 0x00226e5c push 0x21646c72 push 0x6f77202c push 0x6f6c6c65 push 0x48222074 push 0x6e697270 mov edx, esp xor eax, eax push eax push edx push esi push edi mov ecx, esp push eax push ecx push edi mov al, 59 push eax int 0x80 xor ebx, ebx push ebx xor eax, eax inc eax push eax int 0x80
■ 実行結果
$ nasm -f elf e.asm && ld -s -o e e.o && ./e && echo ok Hello, world! ok $
FreeBSD の標準の as コマンドは gas みたいなのですが、どうも私は GAS の表記に慣れていなくて、ついつい NASM を使ってしまいます。。。
参考文献: Linux のアセンブラー: GAS と NASM を比較する
■ perl から任意の C ライブラリを呼び出す方法 - kazuhoのメモ置き場
id:kazuhooku さんの手法を応用すると、以下のように感じで Perl のプログラムから perl のコードを実行することが出来ます。
#!/usr/bin/perl use DynaLoader; my $s="h\x2de\x00\x00\x89\xe6hl\x00\x00\x00". "h\x2fperh\x2fbinh\x2fusr\x89\xe7". "h\x5cn\x22\x00hrld\x21h\x2c\x20wohelloht\x20\x22Hhprin". "\x89\xe21\xc0PRVW\x89\xe1PQW\xb0\x3bP\xcd\x80". "1\xdbS1\xc0\x40P\xcd\x80"; DynaLoader::dl_install_xsub("X",unpack("L",pack("P".length($s),$s)));X();
ここでは print "Hello, world!\n" の文字列が4byte毎にpush命令に分割されています。自分でコードをpushする際は、文字列の終端の \0 も忘れないように push しましょう。
FreeBSD FreeBSD 6.3-RELEASE #0: Wed Jan 16 04:18:52 UTC 2008 i386 で実行した結果は以下になりました。
Hello, world!
kazuhooku++
とりあえず、今週はここまで。