OSのシステムコールの呼び出しとは&バイナリエディタの使い方main()関数の前には何があるのか(4)(2/3 ページ)

» 2017年06月08日 05時00分 公開
[坂井弘亮]

バイナリエディタを使ってみる

 ここまででwrite()が呼ばれていることは間違い無いようだが、果して本当に思った箇所で呼ばれ、そして文字列出力が行われているのだろうか。

 解析を行う場合、まるで教科書を読むように、確実な情報に沿って行えるということは少ない。自分が追いかけている場所は本当にこれで正しいのだろうか、見当外れなところを見ていたりするのではないだろうかと不安に思いながら行うことがほとんどだ。

 このため確証を得るための手段を様々に持っておくことが重要になる。ひとつの手段だけで確認するのではなく、複数の手段で多面的に確認して確実にするわけだ。

 ここではバイナリエディタの使いかたの練習も含めて、実行ファイルを変更したときの挙動の変化を見ることで確認するという方法を試しておこう。

バイナリエディタで実行ファイルを開く

 バイナリエディタを使ったことがある読者のかたは少ないかもしれない。

 しかしバイナリエディタはバイナリファイルを扱う際の必須ツールだ。フォーマットに応じたツールによって読み書きができる場合も多いが、いざというときはやはりバイナリエディタが万能のツールになる。

 本書ではhexeditというバイナリエディタを利用する。hexeditは本書のVM環境にもインストールされているため、そのまま使うことができる。

 まずはhexeditで、実行ファイルのhelloを開いてみよう。なお後でファイルの変更を行うので、helloをhello-nopにコピーしてそちらを開くようにする。

[user@localhost ~]$ cd hello
[user@localhost hello]$ cp hello hello-nop
[user@localhost hello]$ hexedit hello-nop

 図2.37はhexeditでhello-nopを開いた状態だ。

図2.37: hexeditで実行ファイルを開く 図2.37: hexeditで実行ファイルを開く

 図2.37では左端の列にファイル先頭からのオフセット位置、中央にバイナリデータ、右端にはバイナリデータをアスキー文字に変換したものが表示されている。

 カーソルは通常の文字反転カーソルの他に、十字カーソルも表示されている。これは本書のVM環境にインストールされたhexedit特有の追加パッチによるものだ。

 ここでhexeditの使いかたについて、簡単に説明しておこう。

 まずカーソルは矢印キーの上下左右で移動できる。

 Tabキーを押すと、カーソル位置をバイナリデータ内からアスキー文字に交互に切替える。

 検索は「/」を押すことで行える。カーソルがバイナリデータ内にある場合には16進数値の検索が行われる。アスキー文字内にある場合には文字列の検索が可能だ。

 まずはこれくらいを知っておけば、とりあえずバイナリデータを参照するくらいは可能だろう。

int $0x80の呼び出し部分を探す

 さてここでint $0x80の呼び出し部分を探してみよう。

 図2.31を見ると、write()の内部の0x8053d8cの位置からint $0x80を呼び出す処理が呼ばれていた。

 逆アセンブル結果からこのアドレスの部分を位置を探してみると、以下のようになっていた。

[user@localhost hello]$ objdump -d hello
...
  08053d70 <__libc_write>:
  8053d70:	65 83 3d 0c 00 00 00	cmpl	$0x0,%gs:0xc
  8053d77:	00
  8053d78:	75 25			jne	8053d9f <__write_nocancel+0x25>
 
08053d7a <__write_nocancel>:
  8053d7a:	53			push	%ebx
  8053d7b:	8b 54 24 10		mov	0x10(%esp),%edx
  8053d7f:	8b 4c 24 0c		mov	0xc(%esp),%ecx
  8053d83:	8b 5c 24 08		mov	0x8(%esp),%ebx
  8053d87:	b8 04 00 00 00		mov	$0x4,%eax
  8053d8c:	ff 15 50 67 0d 08	call	*0x80d6750
  8053d92:	5b			pop	%ebx
  8053d93:	3d 01 f0 ff ff		cmp	$0xfffff001,%eax
...

 機械語コードの命令列を見てみよう。call命令によるint $0x80の処理の呼び出し付近の機械語コードを集めると「08 b8 04 00 00 00 ff 15 50 67 0d 08」のようになっている。

 この機械語コードを、実行ファイル中から探してみよう。

 まず検索のために「/」を押してみよう。すると図2.38のようになり、検索するバイト列を聞いてくる。

図2.38: 検索のために「/」を押す 図2.38: 検索のために「/」を押す

 ここで検索するバイトデータとして「08 b8 04 00 00 00 ff 15 50 67 0d 08」を入力してみよう。図2.39ではデータが長く折り返されているが、とくに気にする必要は無いようだ。

図2.39: 検索データを入力する 図2.39: 検索データを入力する

 Enterを押すと検索が開始される。再度「/」→Enterのように押すと、次を検索することができる。

 そして図2.40の状態では次を検索しても図2.41のように「not found」となるので、ヒットしたのは図2.40の一箇所のみのようだ。

図2.40: Enterで検索が開始される 図2.40: Enterで検索が開始される
図2.41: 次を検索しても見つからない 図2.41: 次を検索しても見つからない

 さて図2.40がint $0x80の呼び出し箇所のようなのだが、具体的にはヒットした付近の「ff 15 50 67 0d 08」という6バイトが、int $0x80の処理の関数の呼び出しになる。図2.42のカーソル位置の部分だ。

図2.42: int $0x80の呼び出し部分 図2.42: int $0x80の呼び出し部分

実行ファイルを書き換えて確認する

 この命令列を無効化したらどうなるであろうか。

 x86では0x90が何もしない「nop」という命令になるので、0x90という値で上書きしてみよう。そしてhexeditでは、上書きするデータ列をそのまま入力することで、カーソル位置のデータが上書きされる。

 この6バイトの命令列を、すべてnopで書き換えてみよう。図2.43でカーソル位置の前の6バイトがすべて0x90になっていることに注目してほしい。

図2.43: nopで上書きする 図2.43: nopで上書きする

 書き換えたら実行ファイルを保存しよう。hexeditはCtrl+Xでファイルを保存して終了する(ファイルに変更が無い場合には、そのまま終了する)。

 実際にCtrl+Xを押すと図2.44のように変更を保存していいかどうかを聞いてくるので、「y」を押すことで保存して終了する。

図2.44: ファイルを保存する 図2.44: ファイルを保存する

 保存したらhello-nopを実行してみよう。

[user@localhost hello]$ ./hello-nop
[user@localhost hello]$

 今度はメッセージが出力されなくなった。

 つまりint $0x80の呼び出しをnopで上書きしたため、システムコールが呼ばれなくなったわけだ。

 このように注目している箇所の処理をnopで潰してしまい、プログラムの挙動が変化するかどうかを見ることで、その処理がプログラムの実行結果にどのように影響しているのかを確認することができる。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。