~主にプログラミング関係この広告は、90日以上更新していないブログに表示しています。はじめに本記事は、Linuxを対象としたカーネルエクスプロイトの入門記事です。カーネルエクスプロイトというのは、Linuxや*BSD、Windowsを始めとするカーネル自身の脆弱性を突くエクスプロイトです。基本的にカーネルはシステム内で最高権限を持つ特権モードで動作しているので、ここを悪用されるとシステムの大部分(ほぼ全て)を掌握されてしまいます。エクスプロイトと言うと、普通はユーザー空間で動作しているアプリケーションのバグをつく物が多いですが、これだと限られたレベルの権限しか奪えません。SELinuxやjailを始めとする、OSレベルでの保護機構に阻まれるとたちまち効力を失ったりします。カーネル自体の脆弱性をつくカーネルエクスプロイトを利用すると最高権限での任意コード実行が可能なため、大抵の保護機構はものともしません。このカーネルエクスプロイトが特に効力を発揮するのが、ゲーム機やスマートフォンの脱獄、root化です。この手の機器は非常に多くのコンシューマーを抱えており、万が一ハックされてしまうと莫大な金銭的損失が生じるため非常にセキュアな作りになっています。そのため通常、アプリ単一の脆弱性を利用しただけではシステム全体を掌握する事はできません。言うものの、例えば歴代のPlayStation2,3,4は大体root化されていて海賊版ゲームも多く出回ってます。(その度にSonyが修正アップデートを施すイタチごっこなわけですが....で、簡単にはハックできないはずのこれら機器が丸裸にされる致命的な原因というのが、大抵カーネルエクスプロイトです。PlayStation4のOrbisOSはFreeBSDをベースにしていますが、2014年にLinux,*BSDに発見されたBadIRET(CVE-2014-9322)というカーネルエクスプロイトが利用されてあえなく撃沈しました。この後のアップデートで脆弱性は修正されましたが、その後もSonyが加えた独自のFreeBSDシステムコールに脆弱性があり、カーネルエクスプロイトに利用されたりしました。OSのアクセス制限を突破し、見事ハックに成功してしまいました。この変態的なハックが成功したのも発売から4年ぐらい経ってたからの事だったはずです。(PS3はゲーム機史上最高にセキュアと言っても過言じゃないと思いますところで任天堂のWiiは1ヶ月でハックされました。https://events.ccc.de/congress/2010/Fahrplan/attachments/1780_27c3_console_hacking_2010.pdfちょっと前置きが長くなりすぎましたが、本記事では主にLinuxカーネルを焦点にあてたカーネルエクスプロイトを書くための入門記事です。ゲーム機やスマートフォンの脱獄を勧めてるわけじゃないので要注意。技術的な興味として見てください。あ、あと入門系の記事を書くのは苦手なので、見苦しい点もあるかもですが大目にみてください。環境構築さて、まずはカーネルエクスプロイトを書いて動かすための環境構築から初めましょう。カーネルエクスプロイトは頻繁にシステムをクラッシュさせる可能性があるので、まずはVMを用意しましょう。ここではVirtualBoxを使います。64bitで、カーネルはバニラのバージョン3.12とします。VirtualBoxは、ホストとのファイル共有の設定もしておいてください。Additionをインストールする必要があります)ちなみになぜバニラカーネルを選ぶかと言うと、ディストリのパッケージとして落ちてくるlinux-source-はセキュリティパッチがあたっているため、カーネルの脆弱性は逐次塞がれてしまい、カーネルエクスプロイトの演習としては不向きだからです。よってここではパッチが当たっていない、穴がそのまま残っているバニラカーネルを利用する必要があります。バージョン3.12とかなり古めにしたのは、後ほどtowelrootやBadIRETを動かすためです。Treeからcloneしてきましょう。linux-3.12次に上記で落としてきたlinux-3.12のディレクトリに移動してカーネルのconfigを行いましょう。menuconfiglocalmodconfigはゲストOS内で現在ロードされているカーネルモジュール情報から必要な最低限のドライバCONFIGを決定してくれます。何か聞かれたら全部N(Enter叩くだけ)で大丈夫です。その後menuconfigで追加で必要な機能をONにしていきましょう。以下2つのCONFIGをONにしておいてください。Supportなどは切っておいて大丈夫です。CONFIGが設定できたら、ソースをビルドしましょう。-j8などで並列化するためにVirtualBoxの設定でVMにコア数を4つ以上与えるようにしておいてください。-jとコア数は自分の環境に合わせて適時調整してください。1コアだとかなり時間かかるかも)ビルドが完了するとvmlinuxというファイルが出来るはずなので、こいつをホストとの共有ディレクトリにコピーしてホストから参照出来るようにしてください。あとは/etc/default/grubのGRUB_HIDDEN_TIMEOUTをコメントアウトして、再起動すればgrubの選択画面が表示されます。Optionとかを選んでLinux3.12でブートしてみてください。ところでVirtualBoxのファイル共有機能を使っている場合、カーネルをコンパイルしなおして起動した後以下のコマンドを実行すると、ファイル共有機能がまた使えるようになります。virtualbox-guest-dkmsさて、後はnetconsoleを動かしておきましょう。netconsoleはVM内のカーネルがクラッシュした時にログをリモートマシン(ホストマシン)にダンプしてくれる物で、結構重宝します。netconsole=6665@192.168.100.12/eth0,6666@192.168.100.11/04:0c:ce:xx:xx:xxフォーマットはnetconsole=src-port@src-ip/ifname,dst-port@dst-ip/dst-macです。ちなみにdst-macは省略しても全てのデバイスに飛んでくるだけなのでそんなに問題じゃないです。6666とかで、受けてください。VM内のカーネルがクラッシュするとログがここにダンプされます。次にカーネルデバッガを設定しましょう。今回はkgdbを使います。カーネルのブートオプションに以下を追加してください。kgdbwaitこの状態で再起動すると、起動画面でkgdbが立ち上がり止まってくれます。ホストのgdbから接続してみましょう。今度はホスト側で、先ほどvmlinuxを共有したディレクリまで移動してgdbを起動してください。/dev/ttyS0これでゲストOSにアタッチ完了です。ちなみにVirtualBoxのシリアルをホスト側に接続する設定をしておいてください。上の方法だと環境によってはなぜかVirtualBoxのシリアルをホストデバイスに接続できないケースがあります。いう時はVirtualBoxのポートモードを「ホストにパイプ」にして適当な/tmp/gdbsockファイル(名前は何でも良いです)にパイプさせ、以下のようにしてください。/dev/pts/28ではgdb内でcontinueしてOSをブートさせましょう。cContinuing.ただし一度カーネル側に制御が戻るとホストのgdbからCtrl-Cなどで止めることができません。gdbに制御を移したいと思った時は、ゲスト側で以下のコマンドを実行してください。/proc/sysrq-trigger面倒な場合はSys-rqキーとか設定すると良いかも。Linuxメモリ管理基礎さて環境構築が終わったら、いよいよカーネルエクスプロイトに入りたい所ですがその前に、まず本節ではLinuxカーネルのメモリ管理機構の基礎について説明します。アロケーター、カーネルスタックといった基礎については知っておく必要があります。いっても本記事はOSの入門記事では無いので、細かく1から説明はしません。詳細は参考サイトとして挙げるページを逐次参照してください。セグメントとページングまずセグメントとページングについてですが、これはLinuxの機能ではなくintelプロセッサの仕様です。MMUと呼ばれるユニットが主に仮想アドレスと物理アドレスの変換を行い、さら地状態の物理メモリ領域を、誰がどのように分けるかの権限設定や、仮想アドレスへの物理アドレス割当などを行ってくれます。詳細は以下を見てください。続いてLinuxのメモリ管理機構の中心的な要素として、Pageアロケーターについて見ていきます。Linuxは物理メモリ領域をページという単位で管理しており1ぺージは4096byteとなっています。このページ単位で物理メモリの確保や解放を行うのがPageアロケーターです。このPageアロケーターのメモリ管理にはバディシステムという方式を採用しています。これはページの断片化が起こらないよう、なるべく連続したページを割り当てるためのアルゴリズムです。バディシステムではページの空き領域を以下の図のように管理しています。(バディシステムのページ管理)https://www.codeproject.com/Articles/131862/Special-Features-of-Linux-Memory-Management-Mechanバディシステムでは空きページ数を2のべき乗の単位で管理します。1(2^0)ページ、2(2^1)ページ、4(2^2)ページといったぐあいです。これらはべき数ごとにリストで管理されており、例えばある領域に2^2ページ分の空きがあれば、そのエリアは上図の2の空きリストにつながっています。ここで、今4(2^2)ページの領域を確保したいとします。この時バディシステムはまずオーダー2の空きリストを探して、存在すればそのエントリーを返すだけです。オーダー2の空きリストにエントリが無い場合(例えば初めてバディシステムを使った初期の状態や、既存のオーダー2のリストを全て使いきった場合など)はどうするのでしょう。この時バディシステムは一つ上のオーダーである3の空きリストを見に行きます。3のリストに空きリストがあれば8(2^3)ページを2分割して、これら2つを2のリストにつなぎ、うち一つの4(2^2)ページを返すようにします。3のリストにも無ければ4,5...と順番に上がっていき、2^2サイズになるまで分割して、オーダーごとの空きリストに繋いでいくといった具合です。あるエントリーは、一つ上のオーダーサイズのページの中で片割れ(バディ)を持っている状態です。いうなればセグメントツリーの様な仕組みで、連続した領域をあるサイズ(オーダー)ごとに管理しているわけです。することで、確保されたメモリがなるべく連続したページに割り当てられるようにしています。ちなみにページの解放処理はというと、同じく4(2^2)ページを解放する際、オーダー2の空きリストに返却するのに加えて、もし片割れ(バディ)がいてそいつも解放可能であれば、2つまとめて8(2^3)ページ解放し3の空きリストに追加します。まとめると、ページ確保は同じサイズの空きページがリストになければ、一つうえのオーダーから取ってきて半分にする。ページ解放は、なるべく上のオーダーサイズにまとめてからするという感じです。SLABアロケーターPageアロケーターがページ単位でメモリの確保/解放を行っていたのに対して、SLABアロケーターはオブジェクト、オブジェクトをまとめたSLAB、SLABをまとめたSLABキャッシュという単位でメモリを管理するアロケーターです。このオブジェクトのサイズはバイト単位で指定することが出来るため、ページサイズの倍数になっている必要もありません。byte単位で確保していたら無駄でしょうがないわけです。カーネル内の構造体のサイズは静的に分かっているので、あらかじめ各データ構造のサイズにあった適切なメモリプールのような仕組みを数ページの領域で作っておいて、その中でデータ構造を複数割り当てようと思うのは自然な発想ですね。SLABアロケーターはこの発想を上で述べた3つの単位を使って下図のように実現しています。http://softwaretechnique.jp/OS_Development/kernel_development14_1.htmlまずいちばん外側の大枠であるキャッシュというのが、SLABキャッシュでこれはデータ構造の種類ごとに用意されています。(例えばtask_struct構造体専用のSLABキャッシュというのがあるわけです)その中にSLABがリストとしてつながっていてこの各SLABの中にオブジェクトがあるわけですね。オブジェクトはデータ構造のサイズになっています。それから、SLABキャッシュは、オブジェクトが全部割当済みになったSLABリストと、一部だけ割り当て済みと、全てfreeな物の3つのリストでSLABを管理しています。詳しい事は以下のURLを参照してください。実際にデータ構造ごとのSLABを覗いてみましょう。SLABキャッシュの情報は/proc/slabinfoから読めます。0各数字の意味は一番上に出ていますが、例えば1つ目udf_inode_cache用のSLABキャッシュは、全オブジェクトが154個でそのうち88個が使われている。uef_inode_cacheデータ構造体のサイズ)は720byteで、(objperslab)1SLABあたりに22個のオブジェクトが割当られている。(pageperslab)はこの22個のオブジェクトを含んだ1SLABを確保するのに何ページ割り当てたか、という事です。ちなみにSLABを生成する際のページ割当はPageアロケータから行います。カーネルエクスプロイトにおけるメモリ管理機構の利用例さて、では上で説明したPageやSLABアロケーターの知識が実際にカーネルエクスプロイトで必要になるケースを紹介します。昔からあるカーネルヒープオーバーフローのテクニックについて紹介します。overflowshttps://argp.github.io/public/kmalloc_exploitation.pdfLinuxのカーネル内では動的なメモリ割り当てのためのkmalloc/kfreeを利用しますが、このkmallocで割り当てたカーネル内ヒープ領域をオーバーフローしてしまうのがカーネルヒープバッファオーバーフローです。kmallocは最終的に内部でSLABアロケーターを利用してメモリを確保します。SLABはそもそもあらかじめサイズが決まっているオブジェクトのプールじゃなかったか?kmallocは動的にサイズを決められるけどどういう事だ?と思った方は鋭いです。以下のコマンドを実行してみてください。0はい、つまりkmallocは予想されるサイズごとにSLABをあらかじめ用意しているわけです。GFP_KERNEL)とすれば、一番近い128byteに切り上げられてkmalloc-128のSLABからオブジェクトが確保されてその領域がbufferに返されるわけですね。以下のようなコードがあったとしましょう。SLABbufferに128byteのメモリを確保したのに、copy_from_userでユーザーから170byteの入力を受け取ってbufferにコピーするという物です。170-128=42byteのヒープオーバーフローが起きてますね。...とbuffer2を上書きできるでしょうか。答えは、必ずしも出来るとは限らないです。いうと、bufferとbuffer2のために同じSLAB内の隣り合ったオブジェクトが使われる保証がないからです。SLABの中でfreeされた領域を使うLIFOな物になっています。二度目のkmallocを呼んだ時、buffer2のオブジェクトとして選ばれるのは、一番最後にfreeされたオブジェクトなので、これがbufferの隣のオブジェクトとは限らないわけです。オーバーフローさせても、攻撃者が制御できるbuffer2に上書きされないと意図した関数ポインタの書き換えなどができず、結局制御が奪えないわけですね。overflowの論文の主旨です。ここでSLABアロケータの知識が重要になってくるのです。SLABの構造図をもう一度載せます。kmalloc-128キャッシュも複数のSLABを持っていて、その中のどれか一つのオブジェクトがkmallocで選ばれるわけですが、仮に今リストにつながっているSLABが全て限界まで使い切られるとどうなるでしょうか?つまり、全て真ん中の「オブジェクトの全部が割り当て済みリスト」にある状態です。すると、SLABアロケータは新たなSLABを生成するためにPageアロケーターにお願いをしてSLAB用のページを取ってきます。ここが重要で、この段階で先ほどのbuffer1,buffer2を2つ確保すれば必ずこのSLAB内の連続したオブジェクトが使われます。...とできるわけです。後はbuffer2に関数ポインタを持った構造体を持ってきて、buffer1から上書きさせて制御を奪うなりすればエクスプロイト成功というわけですね。Zeroがブログポストしていたエクスプロイト手法を紹介します。興味がある人は読んでみてください。Konovalovさん(xairyさん)は、有名なロシアのカーネルエクスプロイターで去年まで学生だったという異常な若人です。bufferがPageアロケーターで確保されているという点です。overflowはkmallocで確保された領域のオーバーフローでしたが、今回は直接page_allocを利用してPageアロケーターから確保された領域のバッファオーバーフローです。buffer領域の後に、kmallocを使って攻撃者が確保した領域を持ってきたいという物で、ブログ元の図を引用するとこんな感じにしたいのです。左の青い部分がPageアロケーターで確保されてオーバーフローする領域で、その隣にkmalloc-2048で確保されたオブジェクトを持ってこれれば、エクスプロイト成功というわけです。これを達成するためにxairy氏は以下のような方法を取りました。ソケットを大量に生成してkmalloc-2048の現在のSLABを満杯にする。1024個生成して、Pageアロケーターの特定のオーダーの空きリストを枯渇させて、一つ上のオーダーの空きリストから確保させるようにする。bufferをまた一つ作る。これが脆弱性をついてオーバーフローさせる対象。パケットを大量に生成してpacket_sockをkmallocで新しいSLABに確保させまくる。1の調整のおかげで現在kmalloc-2018のSLABは満杯です。bufferとなったページの右半分が使われるわけです。)このように、オーバーフロー自体はPageアロケーターで起きるのだけど、書き換える先はkmallocで確保した領域という応用的な使い方も可能です。おわりにさて、今回はとりあえずここまでです。次回からは実際に手を動かして、カーネル内ROPやStackJackingのコードを書いていきましょう。Linuxカーネルを理解して学ぶ脆弱性入門」を受講する方は本記事を事前課題として読んでおいてください。2,3節はちゃんと理解しておいてください。」を受講する方は、加えて次回からの記事も事前課題とします。早めに公開しますので、それまで本記事をちゃんと理解しておいてください。カーネルエクスプロイト入門記事part2です。)月別アーカイブはてなブログをはじめよう!RKX1209さんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。