〜rootkitを検出するために〜

連載:インシデントレスポンスはじめの一歩

第3回 侵入者の不利な情報を隠すLKM rootkitの仕組み

新町久幸
ラック
2003/5/29

 LKMのrootkitへの応用

 LKM rootkitが登場するまでは、「侵入者にとって不利となる情報を隠す」という目的を達成するために「バイナリを置き換える」という方法が用いられていた。前回までサンプルとして紹介していたものは、実はこの方法を用いたものであった。

 バイナリを置き換えて出力される情報を制限するという方法は実現が容易であり、侵入者にとって有効な手段となり得るのであるが、1つ大きな問題があった。それは、システム上のバイナリを置き換えた場合、ファイルのデータ自体が変化するため、それが管理者に発見され自分の存在が露見する可能性が高くなってしまうという点である。そこで侵入者にとって不利となる情報を隠すためにLKMの仕組みを利用する方法が考え出された。これがLKM rootkitである。LKM rootkitとは、LKMの仕組みを利用したプログラムが含まれたrootkitのことである。

●LKM rootkitの仕組み

 カーネルはコンピュータシステムの基本機能を提供し、システムの制御を行っていることは説明してきた。では、このカーネルの機能を変更することができるとしたらどうであろうか。LKM rootkitはカーネルに組み込まれ、特権モードとして動作するためにそれが可能となるのである。つまり、LKM rootkitはLKMを利用してカーネルそのものを変更することで、システムの挙動を変化させるのだ。LKM rootkitにもさまざまな種類のものが存在しているが、主に侵入者にとって不利となる情報が出力されるのを抑制するために利用されている。

 つまり、システムに“うそをつかせる”ということだ。どのようなうそをつかせるかは組み込むカーネルモジュール次第であるが、ユーザーがログインしているにもかかわらずだれもいないといわせたり、ファイルがあるのにもかかわらずファイルは存在しないといわせたりすることも可能である。LKM rootkitはシステムコールを「フック」することにより、これを実現している。では、その仕組みについて解説していこう。

●システムコールのフック

 ユーザーからの要求や割り込み発生時などに、カーネルへの要求が行われることをシステムコールといい、その際にカーネルとのやりとりを行うインターフェイスとなるものがカーネル関数である。カーネル関数はあらかじめカーネル内部に用意されており、ユーザープログラムはこの関数を介してOSの機能を利用している。カーネル関数へのアクセスはカーネルが持つsys_call_table[ ]という名前のテーブル(配列)を参照して行われている。

図3 sys_call_table[ ]を参照して行われる関数の呼び出し

 非特権モードで動作するユーザープログラムから、カーネル内部にあるsys_call_table[ ]にアクセスすることはできないが、カーネルと同様の特権モードで動作するLKMからであればこのテーブルにアクセスすることが可能である。従って、LKMとしてカーネルに組み込むモジュールを利用して、sys_call_table[ ]に格納されているカーネル関数の位置を指し示した値を変更することにより、システムコールが行われる際に本来参照されるべきカーネル関数とは異なる関数が呼び出されるようになるのである。これをシステムコールのフックという。

図4 変更されたsys_call_talbe[ ]により呼び出される関数が変化する

 システムコールをフックすることで呼び出される関数の中で、任意の処理を行わせることによってシステム本来の動作を変更させるのである。また、この関数内から本来のカーネル関数を呼び出すことも可能である。従って、システムには正規の処理を行わせ、その結果として返された情報に手を加えた後に、ユーザープログラムに返すという処理を行うことも可能なのだ。LKM rootkitはこの仕組みを利用して、侵入者にとって不利な情報を隠すのである。

●LKM rookitのタネとなるサンプルプログラム

 では、実際にLKM rootkitがどのようにしてシステムの動作を変更しているか、サンプルプログラムを用いて紹介しよう。次のプログラムは、システム情報を出力するsys_uname( )というカーネル関数をフックしてunameコマンドの結果を変更するLKMである。

----- hook_uname.c -----

1: #define MODULE
2: #define __KERNEL__
3:
4: #include <linux/module.h>
5: #include <linux/utsname.h>
6: #include <linux/string.h>
7: #include <sys/syscall.h>
8:
9: extern void *sys_call_table[];
10: int (*original_sys_uname)(struct old_utsname *buf);
11:
12: int hook_uname(struct old_utsname *buf)
13: {
14: int ret;
15: char str[] = "Windows";
16:
17: ret = original_sys_uname(buf);
18: strncpy(buf->sysname, str, sizeof(str));
19:
20: return ret;
21: }
22:
23: int init_module(void)
24: {
25: original_sys_uname = sys_call_table[SYS_uname];
26: sys_call_table[SYS_uname] = hook_uname;
27:
28: return 0;
29: }
30:
31: void cleanup_module(void)
32: {
33: sys_call_table[SYS_uname] = original_sys_uname;
34: }

 init_module( )関数はLKMがカーネルに組み込まれる際に実行される関数であり、cleanup_module( )関数はカーネルからモジュールを取り外す際に実行される関数である。この2つの関数はLKMを作成する場合には必ずなくてはならない。

 26行目でsys_call_table[ ]テーブル内のsys_uname( )関数を指し示す値が格納された場所(sys_call_table[SYS_uname])に、ユーザーが定義した関数を指し示す値を代入している。また、カーネルからこのモジュールを取り外した後に変更したテーブルを元の状態に戻すため25行目で変更前の値を保持し、33行目で保存しておいた値を代入している。17行目で本来の sys_uname( )関数の呼び出しで行われるはずであった処理をシステムに行わせて、18行目でその結果得られた値に手を加えている。

 このモジュールが行う処理をモデル化したものが次の図である。

1.
2.
3.
4.
sys_call_table[ ]テーブルの値を変更する。
フックした関数から、本来のカーネル関数を呼び出し、結果を取得する。
この結果から得られた値を変更する。
変更した情報をユーザープログラムに返す。
図5 sys_uname( )関数をフックしてunameコマンドの結果を変更するモデル

 このLKMをカーネルに組み込んだ場合のシステムの挙動は以下のとおりである。

# /bin/uname -a
Linux localhost.localdomain 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown

# gcc -c -O3 hook_uname.c
# insmod hook_uname.o

# /bin/uname -a
Windows localhost.localdomain 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknow

 カーネルにこのモジュールを組み込む前後でunameコマンドの出力結果が変化したことがお分かりいただけるであろうか。出力された情報は変化したが、unameのバイナリには一切手を加えていないという点に着目していただきたい。

 ここではsys_uname( )関数を用いて、システムコールがフックされシステムの動作が変化する様子を紹介したが、同様の方法でユーザーからファイルを見えなくしたり、psコマンドからプロセスを隠すことも可能である。現在、インターネット上にはさまざまな種類のLKM rootkitが存在しているが、その仕組みもこれと同様である。つまり、悪意のあるカーネルモジュールを事前にカーネルに組み込んでおき、システムコールをフックすることでシステムの挙動を変更するのである。

 このようなモジュールがカーネルに組み込まれた場合、システムから出力された情報が正しいものであるかどうかを従来のようにファイルを比較するだけで判断することはできない。そのうえ、そもそもその情報を出力するシステム自体がうそをついている可能性も考えられる。そのため、侵入者の存在を確認することが非常に困難なのである。このようにLKM rootkitはLKMの特性をうまく利用して目的を実現するのである。

 最後にLKM rootkitがバイナリ置き換え型のrootkitと異なる点を挙げる。重要なポイントなので、ぜひ押さえておきたい。

●システムを再起動させる必要がなく、動的にカーネルに組み込むことができる
●カーネルに組み込まれたLKM rootkitは特権モードとして動作し、システムの挙動を変更することが可能である
●ユーザープロセスからカーネルの動作が変更されたことを判断することが困難であるため、侵入者の活動を特定することがバイナリ置き換え型のrootkitよりも難しい

 次回は代表的なLKM rootkitをいくつか紹介し、それらが実際に用いている手法について技術的側面から解説を行いたい。

「第4回」

index
第3回 侵入者の不利な情報を隠すLKM rootkitの仕組み
LKM(Loadable Kernel Module)とは何か?
LKMのrootkitへの応用

関連記事
連載:Webアプリケーションに潜むセキュリティホール
特集:クロスサイトスクリプティング対策の基本
連載:不正侵入の手口と対策

「連載 インシデントレスポンスはじめの一歩」


Security&Trust フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Security & Trust 記事ランキング

本日 月間