情弱ログ

参考にならないので当てにしないでください

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
qiita.com