x86_64環境でシステムコールを発行する
「1日待ってください。本物のシステムコールをお見せしますよ。」
と言ってしまったので、C言語からインラインアセンブラを使ってシステムコールを実行することになった時のメモです。
「write()やread()は通常の関数ではなくシステムコールである」という説明があり、そんなわけないだろと思って以下のプログラムをコンパイルしてみました。
#include <unistd.h> int main(void){ write(1,"hello\n",6); return 0; }
逆アセンブルすると以下のようになります。
0000000000400506 <main>: 400506: 55 push %rbp 400507: 48 89 e5 mov %rsp,%rbp 40050a: ba 06 00 00 00 mov $0x6,%edx 40050f: be b4 05 40 00 mov $0x4005b4,%esi 400514: bf 01 00 00 00 mov $0x1,%edi 400519: e8 c2 fe ff ff callq 4003e0 <write@plt> 40051e: b8 00 00 00 00 mov $0x0,%eax 400523: 5d pop %rbp 400524: c3 retq 400525: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 40052c: 00 00 00 40052f: 90 nop
x86_64の呼び出し規約はこちら
x86/x86_64関数呼び出しチートシートを書いた | d.sunnyone.org
第一引数を%ediに、第二引数を%esiに、第三引数を%edxに突っ込んでwrite@pltという関数を呼び出しています。
どう見てもソフトウェア割り込みなどは行っていません。
ちなみにwrite@pltは.textセクションではなく.pltセクションというところに設置されており、以下の様なプログラムになっていました。
00000000004003e0 <write@plt>: 4003e0: ff 25 0a 05 20 00 jmpq *0x20050a(%rip) # 6008f0 <_GLOBAL_OFFSET_TABLE_+0x18> 4003e6: 68 00 00 00 00 pushq $0x0 4003eb: e9 e0 ff ff ff jmpq 4003d0 <_init+0x28>
ELFフォーマットなどはよく知らないのですが、.pltセクションは共有オブジェクトの関数などを呼び出すために使うらしいです。よく分かっていません。
これでは本当にシステムコールを呼んだとは言えません。writeというライブラリ関数を呼び出したに過ぎません。
そこで、冒頭の「1日待ってください。本物のシステムコールをお見せしますよ。」に戻ってきます。
本物のシステムコールを実行するために、インラインアセンブラを利用します。
去年参加した熱血シェルコードではint 0x80を用いてシステムコールを発行しました。
今回は環境をx86_64に限定して、syscallを使ってみたいと思います。
コードは以下のようになります。
#include <sys/types.h> ssize_t my_write(int fd, const void *buf, size_t count){ ssize_t ret; // __NR_write = 1 asm volatile("movq $1, %%rax\n\t" "syscall\n\t" : "=a" (ret) // a=rax : "D" (fd), "S" (buf), "d" (count) // D=rdi, S=rsi, d=rdx : "memory", "cc", "rcx", "r11" ); return ret; } int main(void){ my_write(1,"hello\n",6); return 0; }
逆アセンブルします。
00000000004004b6 <my_write>: 4004b6: 55 push %rbp 4004b7: 48 89 e5 mov %rsp,%rbp 4004ba: 89 7d ec mov %edi,-0x14(%rbp) 4004bd: 48 89 75 e0 mov %rsi,-0x20(%rbp) 4004c1: 48 89 55 d8 mov %rdx,-0x28(%rbp) 4004c5: 8b 45 ec mov -0x14(%rbp),%eax 4004c8: 48 8b 75 e0 mov -0x20(%rbp),%rsi 4004cc: 48 8b 55 d8 mov -0x28(%rbp),%rdx 4004d0: 89 c7 mov %eax,%edi 4004d2: 48 c7 c0 01 00 00 00 mov $0x1,%rax 4004d9: 0f 05 syscall 4004db: 48 89 45 f8 mov %rax,-0x8(%rbp) 4004df: 48 8b 45 f8 mov -0x8(%rbp),%rax 4004e3: 5d pop %rbp 4004e4: c3 retq 00000000004004e5 <main>: 4004e5: 55 push %rbp 4004e6: 48 89 e5 mov %rsp,%rbp 4004e9: ba 06 00 00 00 mov $0x6,%edx 4004ee: be 94 05 40 00 mov $0x400594,%esi 4004f3: bf 01 00 00 00 mov $0x1,%edi 4004f8: e8 b9 ff ff ff callq 4004b6 <my_write> 4004fd: b8 00 00 00 00 mov $0x0,%eax 400502: 5d pop %rbp 400503: c3 retq 400504: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 40050b: 00 00 00 40050e: 66 90 xchg %ax,%ax
syscallを用いてシステムコールを発行していることが確認できます。
当然実行するとhelloという文字が出力されます。
車輪の再発明にも程が有りますが、本物のシステムコールをお見せすることができました。
ちなみに出力レジスタの"=a"を誤って"=A"と書いてしまい、%edxも破壊されてえらいことになったりしました。気をつけましょう。
参考にしたサイト
大体書き終わった後に見つけたのでしょぼくれてました。破壊されるレジスタも含めて書いてあるのはここくらいでしょうか。
stackoverflow.com
http://qiita.com/kure/items/5a1a114f9a37aeab255cqiita.com