OSのシステムコールの呼び出しとは&バイナリエディタの使い方:main()関数の前には何があるのか(4)(2/3 ページ)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、printf()内のポインタ経由での関数呼び出しが行き着く先にあるシステムコールの呼び出しとバイナリエディタの使い方について。
バイナリエディタを使ってみる
ここまでで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の呼び出し部分を探す
さてここで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.
関連記事
- プログラミング言語Cについて知ろう
プログラミング言語の基本となる「C」。正しい文法や作法を身に付けよう。Cには確かに学ぶだけの価値がある(編集部) - シェルコード解析に必携の「5つ道具」
コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部) - 【 od 】コマンド――ファイルを8進数や16進数でダンプする
本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、「od」コマンドです。