「ユーザーランドのpwnは一通り勉強したけど、カーネルからは難しそうで手が出ない」という方は多いでしょう。しかし、実はカーネルexploitでは、場合によってはとても攻撃が簡単です。
この節では、ユーザーランドexploitとカーネルexploitとの違い、環境構築などについて説明します。
カーネルexploitの特徴
まず始めにユーザーランドと比べてカーネル空間の脆弱性にはどんな特徴があるかを知っておきましょう。
攻撃対象
ユーザーランドexploitとカーネルexploitの最も大きな違いはその目的にあります。
これまで説明したユーザーランドexploitでは多くの場合「任意コマンド実行」という目標に向けてexploitを書きました。一方で、カーネルexploitでは一般的に「権限昇格」に向けてexploitを書きます。何らかの手段で攻撃対象のマシンに侵入できた仮定で、攻撃者はカーネルexploitを利用してさらにroot権限を取得します[1]。このように、ローカルマシンでの権限昇格をLPE(Local Privilege Escalation)と呼びます。
もちろん、ユーザーランドで権限昇格できる脆弱性もありますが、それは攻撃対象のプログラムが特権ユーザーで動作しているからです。カーネルexploitの場合、攻撃対象は主に次の2つがあります。
- Linuxカーネル
- カーネルモジュール
Linuxカーネル中のコード(システムコールやファイルシステム)はroot権限で動作しているため、カーネルそのものにバグがある場合LPEに繋がる可能性があります。
もう1つはデバイスドライバなどのカーネルモジュールに含まれる脆弱性です。デバイスドライバは、ユーザー空間から、主に外部機器(プリンタなど)とのやりとりを簡単にするためのインタフェースです。デバイスドライバも必ずroot権限で動作している[2]ので、バグがある場合LPEに繋がります。
攻撃方法
ユーザーランドexploitの場合、通常は攻撃対象のサービスに入力を与えることでexploitしました。そのため、Pythonなどの言語でexploitを書くのが主流です。
一方で、カーネルexploitの場合は対象がOSやドライバです。これらの操作は低レイヤのため、C言語などでexploitを書くのが主流です。もちろんPythonなどでも書けますが、そもそも攻撃対象のマシン(特にCTFや実験環境で用意されるような小さいLinux)の上にPythonが存在することが少ないため、動かない可能性が高いです。
このサイトでもexploitをC言語で記述します。詳しい話は別の節で出てきますが、musl-gccというコンパイラを使います。
リソースの共有
カーネルexploitのもう1つの特徴は、リソースが共有されているという点です。
ユーザーランドでは通常、攻撃対象のプロセスが1つ存在し、そのプロセスをexploitすることで、シェルを取るなどの攻撃をしました。一方で、Linuxカーネルやデバイスドライバといったプログラムは、OSを利用するすべてのプロセスに共有されています。システムコールは誰でも自由なタイミングで使うことが可能ですし、デバイスドライバも誰がいつ操作するかわかりません。つまり、カーネル空間で動くコードを書く際は、常にマルチスレッドのつもりでプログラミングしなければ簡単に脆弱性を埋め込んでしまいます。
グローバル変数のように競合する可能性のあるデータを使うときはロックを取らないといけないってことだね。
カーネル空間のプログラミングって大変だ〜。
ヒープ領域の共有
さらに、カーネルのヒープ領域は全ドライバやカーネルで共有されているという大きな特徴があります。
これまでユーザーランドのexploitでは、プログラムごとにヒープがあるため、そのプログラムでHeap Overflowが起きたからといってexploitableかはプログラム依存でした。しかし、例えばデバイスドライバで1回ヒープオーバーフローが起きると、他のデバイスドライバやLinuxカーネルがヒープに確保した周辺のデータまで汚染されてしまいます。
攻撃者の観点からすると、この特徴には長所と短所があります。長所は、ヒープ周りの小さな脆弱性でもLPEに繋がる可能性が非常に高いという点です。例えば関数ポインタを持つオブジェクトはLinuxカーネルにたくさん存在するので、そのどれかを利用してRIPが簡単に取れるわけです。短所は、全プログラムの影響を受けるためヒープの状態が予測できないという点です。ユーザーランドのプログラムは簡単なものであれば、入力に対してヒープの状態が決定的だったので、複雑なヒープexploit(俗に言うHouse of XXXなど)が可能でした。一方で、カーネルではHeap Overflowが発生するチャンクの後ろにどんなデータが存在するかや、Use-after-Freeでfreeされた後に誰がそのアドレスを使うかはわかりません。
ということは、カーネルexploitではHeap Sprayが重要なんだね。
脆弱性自体はユーザーランドのものと大きく変わりません。Stack OverflowやUse-after-Freeなどはカーネルランドにも存在し得ます。また、デバイスドライバのスタックにもセキュリティ機構としてStack Canaryを置くことができます。ただし、カーネル空間特有の脆弱性というのもあるので、それは後の節で登場します。
qemuの利用
Linuxカーネルexploitを書くときは、デバッグのためにエミュレータの上でカーネルを動かします。VMなら何でも構いませんが、qemuが一般的なのでこのサイトでもqemuを利用します。
読者の方は自分の環境に合わせてqemu-systemをインストールしておいてください。
1 | # apt install qemu-system |
ディスクイメージ
qemuでマシンを起動する際、Linuxカーネルとは別にルートディレクトリとしてマウントされるディスクイメージが必要です。
ディスクイメージは一般的にext等のファイルシステムの生バイナリか、cpioと呼ばれる形式で作成・配布されます。
ファイルシステムの場合はmountコマンドでマウントすれば中のファイルを編集できます。
1 | # mkdir root |
このサイトで扱う演習では、CTFで一般的であり、また軽量であるcpio形式を使います。
cpioコマンドを使って次のようにファイルを展開します。
1 | # mkdir root |
ファイルを追加したり編集したら、次のように再びcpioファイルにまとめます。
1 | # find . -print0 | cpio -o --format=newc --null > ../rootfs_updated.cpio |
cpioがさらにgzで圧縮されている場合もあるので、その際は適宜展開・再圧縮してください。
なお、cpioは権限情報も付与するため、ファイルシステムの編集では適切にファイルの所有者をrootに割り当てる必要があります。上記コマンドはすべてroot権限で実行しているため問題ありませんが、面倒な場合は--owner=root
オプションを与えてパックしても構いません。
1 | $ mkdir root |
(1)
run.sh
を実行して、Linuxが立ち上がることを確認してください。(2) 起動時にシェルがroot権限になるように
rootfs.cpio
を編集してください。(ヒント:起動時のメッセージを表示しているスクリプトを探しましょう。)