ここまでで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では左端の列にファイル先頭からのオフセット位置、中央にバイナリデータ、右端にはバイナリデータをアスキー文字に変換したものが表示されている。
カーソルは通常の文字反転カーソルの他に、十字カーソルも表示されている。これは本書のVM環境にインストールされたhexedit特有の追加パッチによるものだ。
ここでhexeditの使いかたについて、簡単に説明しておこう。
まずカーソルは矢印キーの上下左右で移動できる。
Tabキーを押すと、カーソル位置をバイナリデータ内からアスキー文字に交互に切替える。
検索は「/」を押すことで行える。カーソルがバイナリデータ内にある場合には16進数値の検索が行われる。アスキー文字内にある場合には文字列の検索が可能だ。
まずはこれくらいを知っておけば、とりあえずバイナリデータを参照するくらいは可能だろう。
さてここで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のようになり、検索するバイト列を聞いてくる。
ここで検索するバイトデータとして「08 b8 04 00 00 00 ff 15 50 67 0d 08」を入力してみよう。図2.39ではデータが長く折り返されているが、とくに気にする必要は無いようだ。
Enterを押すと検索が開始される。再度「/」→Enterのように押すと、次を検索することができる。
そして図2.40の状態では次を検索しても図2.41のように「not found」となるので、ヒットしたのは図2.40の一箇所のみのようだ。
さて図2.40がint $0x80の呼び出し箇所のようなのだが、具体的にはヒットした付近の「ff 15 50 67 0d 08」という6バイトが、int $0x80の処理の関数の呼び出しになる。図2.42のカーソル位置の部分だ。
この命令列を無効化したらどうなるであろうか。
x86では0x90が何もしない「nop」という命令になるので、0x90という値で上書きしてみよう。そしてhexeditでは、上書きするデータ列をそのまま入力することで、カーソル位置のデータが上書きされる。
この6バイトの命令列を、すべてnopで書き換えてみよう。図2.43でカーソル位置の前の6バイトがすべて0x90になっていることに注目してほしい。
書き換えたら実行ファイルを保存しよう。hexeditはCtrl+Xでファイルを保存して終了する(ファイルに変更が無い場合には、そのまま終了する)。
実際にCtrl+Xを押すと図2.44のように変更を保存していいかどうかを聞いてくるので、「y」を押すことで保存して終了する。
保存したらhello-nopを実行してみよう。
[user@localhost hello]$ ./hello-nop [user@localhost hello]$
今度はメッセージが出力されなくなった。
つまりint $0x80の呼び出しをnopで上書きしたため、システムコールが呼ばれなくなったわけだ。
このように注目している箇所の処理をnopで潰してしまい、プログラムの挙動が変化するかどうかを見ることで、その処理がプログラムの実行結果にどのように影響しているのかを確認することができる。
Copyright © ITmedia, Inc. All Rights Reserved.