るくすの日記 ~ Out_Of_Range ~

主にプログラミング関係

カーネルエクスプロイト入門 - Linuxカーネル解析の基礎 -

0. はじめに

本記事は、Linuxを対象としたカーネルエクスプロイトの入門記事です。
カーネルエクスプロイトというのは、Linuxや*BSDWindowsを始めとするカーネル自身の脆弱性を突くエクスプロイトです。
基本的にカーネルはシステム内で最高権限を持つ特権モードで動作しているので、ここを悪用されるとシステムの大部分(ほぼ全て)を掌握されてしまいます。

エクスプロイトと言うと、普通はユーザー空間で動作しているアプリケーションのバグをつく物が多いですが、これだと限られたレベルの権限しか奪えません。
SELinuxやjailを始めとする、OSレベルでの保護機構に阻まれるとたちまち効力を失ったりします。
しかし、カーネル自体の脆弱性をつくカーネルエクスプロイトを利用すると最高権限での任意コード実行が可能なため、大抵の保護機構はものともしません

このカーネルエクスプロイトが特に効力を発揮するのが、ゲーム機やスマートフォンの脱獄、root化です。
この手の機器は非常に多くのコンシューマーを抱えており、万が一ハックされてしまうと莫大な金銭的損失が生じるため非常にセキュアな作りになっています。そのため通常、アプリ単一の脆弱性を利用しただけではシステム全体を掌握する事はできません。

f:id:RKX1209:20170711191416j:plain

とは言うものの、例えば歴代のPlayStation2,3,4は大体root化されていて海賊版ゲームも多く出回ってます。(その度にSonyが修正アップデートを施すイタチごっこなわけですが....
で、簡単にはハックできないはずのこれら機器が丸裸にされる致命的な原因というのが、大抵カーネルエクスプロイトです。
例えばPlayStation4のOrbisOSはFreeBSDをベースにしていますが、2014年にLinux,*BSDに発見されたBadIRET(CVE-2014-9322)というカーネルエクスプロイトが利用されてあえなく撃沈しました。
この後のアップデートで脆弱性は修正されましたが、その後もSonyが加えた独自のFreeBSDシステムコール脆弱性があり、カーネルエクスプロイトに利用されたりしました。


また一世代前のPS3は、セキュリティに関して異常なまでの執念を感じさせる作りになっており長い間誰もroot化できなかったのですが、伝説のハッカーことGeorge Hotz(geohot)氏がGlitching Attackというハードウェアレベルの攻撃で、ハイパーバイザによるOther OSのアクセス制限を突破し、見事ハックに成功してしまいました。この変態的なハックが成功したのも発売から4年ぐらい経ってたからの事だったはずです。(PS3はゲーム機史上最高にセキュアと言っても過言じゃないと思います

ところで任天堂Wiiは1ヶ月でハックされました。(もうちょっと何とかならなかったのか....


(ゲーム機ハック年表)
f:id:RKX1209:20170711184929j:plain
fail0verflow, "Console Hacking 2010 PS3 Epic Fail" より
https://events.ccc.de/congress/2010/Fahrplan/attachments/1780_27c3_console_hacking_2010.pdf


ちょっと前置きが長くなりすぎましたが、本記事では主にLinuxカーネルを焦点にあてたカーネルエクスプロイトを書くための入門記事です。

決してゲーム機やスマートフォンの脱獄を勧めてるわけじゃないので要注意。技術的な興味として見てください。

あ、あと入門系の記事を書くのは苦手なので、見苦しい点もあるかもですが大目にみてください。
(既にどういう文体を目指せば良いのか困惑しているところです)

1. 環境構築

さて、まずはカーネルエクスプロイトを書いて動かすための環境構築から初めましょう。

カーネルエクスプロイトは頻繁にシステムをクラッシュさせる可能性があるので、まずはVMを用意しましょう。
ここではVirtualBoxを使います。(個人的にはQEMUKVMの方がデバッグ用途ではおすすめですが...

ゲストOSはUbutnu14.04 amd 64bitで、カーネルはバニラのバージョン3.12とします。
VirtualBoxは、ホストとのファイル共有の設定もしておいてください。
(VirtualBoxのファイル共有にはGuest Additionをインストールする必要があります)

ちなみになぜバニラカーネルを選ぶかと言うと、ディストリのパッケージとして落ちてくるlinux-source-はセキュリティパッチがあたっているため、カーネル脆弱性は逐次塞がれてしまい、カーネルエクスプロイトの演習としては不向きだからです。よってここではパッチが当たっていない、穴がそのまま残っているバニラカーネルを利用する必要があります。
バージョン3.12とかなり古めにしたのは、後ほどtowelrootBadIRETを動かすためです。


まずゲスト内でバニラカーネルLinus Treeからcloneしてきましょう。
(わざわざcloneしなくてもという人はzip等でダウンロードでも大丈夫です)

$ git clone --depth=1 -b v3.12 https://github.com/torvalds/linux.git linux-3.12

次に上記で落としてきたlinux-3.12のディレクトリに移動してカーネルのconfigを行いましょう。

$ cd /path/to/linux-3.12_source (linux-3.12をcloneしたパス)
$ make localmodconfig
$ make menuconfig

localmodconfigはゲストOS内で現在ロードされているカーネルモジュール情報から必要な最低限のドライバCONFIGを決定してくれます。何か聞かれたら全部N(Enter叩くだけ)で大丈夫です。
その後menuconfigで追加で必要な機能をONにしていきましょう。

とりあえず以下2つのCONFIGをONにしておいてください。

Kernel Hacking ---> 
  <*> KGDB: kernel debugger

Device Drivers ---> 
  Network device support --->     
   <M> Network console logging support (EXPERIMENTAL)

後は明らかに必要ないVirtualizationやWireless Supportなどは切っておいて大丈夫です。

CONFIGが設定できたら、ソースをビルドしましょう。

$ make -j8
$ sudo make modules_install
$ sudo make install

ちなみに、make -j8などで並列化するためにVirtualBoxの設定でVMにコア数を4つ以上与えるようにしておいてください。(make -jとコア数は自分の環境に合わせて適時調整してください。ただ1コアだとかなり時間かかるかも)

ビルドが完了するとvmlinuxというファイルが出来るはずなので、こいつをホストとの共有ディレクトリにコピーしてホストから参照出来るようにしてください。

あとは/etc/default/grubGRUB_HIDDEN_TIMEOUTコメントアウトして、再起動すればgrubの選択画面が表示されます。Advanced Optionとかを選んでLinux3.12でブートしてみてください。ちゃんと起動するはずです。

ところでVirtualBoxのファイル共有機能を使っている場合、カーネルコンパイルしなおして起動した後以下のコマンドを実行すると、ファイル共有機能がまた使えるようになります。

$ sudo apt-get -y install --reinstall virtualbox-guest-dkms

さて、後はnetconsoleを動かしておきましょう。netconsoleはVM内のカーネルがクラッシュした時にログをリモートマシン(ホストマシン)にダンプしてくれる物で、結構重宝します。

sudo modprobe netconsole 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は省略しても全てのデバイスに飛んでくるだけなのでそんなに問題じゃないです。

ホスト側では、

$ nc -l -u 192.168.100.11 6666

とかで、受けてください。 VM内のカーネルがクラッシュするとログがここにダンプされます。

次にカーネルデバッガを設定しましょう。今回はkgdbを使います。
カーネルのブートオプションに以下を追加してください。

kernel ... kgdboc=ttyS0,115200 kgdbwait

この状態で再起動すると、起動画面でkgdbが立ち上がり止まってくれます。
ではホストのgdbから接続してみましょう。今度はホスト側で、先ほどvmlinuxを共有したディレクリまで移動してgdbを起動してください。

$ gdb -q 
(gdb) file vmlinux
(gdb) target remote /dev/ttyS0

これでゲストOSにアタッチ完了です。ちなみにVirtualBoxのシリアルをホスト側に接続する設定をしておいてください。
ただし、上の方法だと環境によってはなぜかVirtualBoxのシリアルをホストデバイスに接続できないケースがあります。
そういう時はVirtualBoxのポートモードを「ホストにパイプ」にして適当な/tmp/gdbsockファイル(名前は何でも良いです)にパイプさせ、以下のようにしてください。

$ socat -d -d unix-connect:/tmp/gdbsock pty,raw,echo=0 &
...
socat[30729] N PTY is /dev/pts/28
...
$ gdb -q 
(gdb) file vmlinux
(gdb) target remote /dev/pts/28

ではgdb内でcontinueしてOSをブートさせましょう。

(gdb) c
Continuing.

ただし一度カーネル側に制御が戻るとホストのgdbからCtrl-Cなどで止めることができません。
gdbに制御を移したいと思った時は、ゲスト側で以下のコマンドを実行してください。

$ su -
# echo g > /proc/sysrq-trigger

面倒な場合はSys-rqキーとか設定すると良いかも。

2. Linuxメモリ管理基礎

さて環境構築が終わったら、いよいよカーネルエクスプロイトに入りたい所ですがその前に、まず本節ではLinuxカーネルメモリ管理機構の基礎について説明します。カーネルエクスプロイトを書くには最低限セグメントページングPageアロケーターSLAB アロケーターカーネルスタックといった基礎については知っておく必要があります。
といっても本記事はOSの入門記事では無いので、細かく1から説明はしません。
詳細は参考サイトとして挙げるページを逐次参照してください。

セグメントとページング

まずセグメントとページングについてですが、これはLinuxの機能ではなくintelプロセッサの仕様です。MMUと呼ばれるユニットが主に仮想アドレスと物理アドレスの変換を行い、さら地状態の物理メモリ領域を、誰がどのように分けるかの権限設定や、仮想アドレスへの物理アドレス割当などを行ってくれます。
詳細は以下を見てください。

0から作るOS開発 ページングその1 ページとPTEとPDE
0から作るOS開発 ページングその2 仮想メモリ管理

(仮装アドレス->物理アドレスへの変換)
f:id:RKX1209:20170712131748p:plain

https://www.cs.uaf.edu/2012/fall/cs301/lecture/11_05_mmap.html

Pageアロケーター

続いてLinuxのメモリ管理機構の中心的な要素として、Pageアロケーターについて見ていきます。
Linuxは物理メモリ領域をページという単位で管理しており1ぺージは4096byteとなっています。このページ単位で物理メモリの確保や解放を行うのがPageアロケーターです。このPageアロケーターのメモリ管理にはバディシステムという方式を採用しています。
これはページの断片化が起こらないよう、なるべく連続したページを割り当てるためのアルゴリズムです。
バディシステムではページの空き領域を以下の図のように管理しています。

(バディシステムのページ管理)
f:id:RKX1209:20170712173507p:plain
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キャッシュという単位でメモリを管理するアロケーターです。このオブジェクトのサイズはバイト単位で指定することが出来るため、ページサイズの倍数になっている必要もありません。
一例として、例えば1ページより小さな構造体を一つ確保するためにわざわざ4096 byte単位で確保していたら無駄でしょうがないわけです。
しかもカーネル内の構造体のサイズは静的に分かっているので、あらかじめ各データ構造のサイズにあった適切なメモリプールのような仕組みを数ページの領域で作っておいて、その中でデータ構造を複数割り当てようと思うのは自然な発想ですね。

SLABアロケーターはこの発想を上で述べた3つの単位を使って下図のように実現しています。
f:id:RKX1209:20170713160505p:plain
http://softwaretechnique.jp/OS_Development/kernel_development14_1.html

まずいちばん外側の大枠であるキャッシュというのが、SLABキャッシュでこれはデータ構造の種類ごとに用意されています。(例えばtask_struct構造体専用のSLABキャッシュというのがあるわけです)
その中にSLABがリストとしてつながっていてこの各SLABの中にオブジェクトがあるわけですね。オブジェクトはデータ構造のサイズになっています。
それから、SLABキャッシュは、オブジェクトが全部割当済みになったSLABリストと、一部だけ割り当て済みと、全てfreeな物の3つのリストでSLABを管理しています。

詳しい事は以下のURLを参照してください。

0から作るOS開発 ヒープとkmallocとスラブアロケーターその1 概略

実際にデータ構造ごとのSLABを覗いてみましょう。
SLABキャッシュの情報は/proc/slabinfoから読めます。

$ sudo head /proc/slabinfo 
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
udf_inode_cache       88    154    720   22    4 : tunables    0    0    0 : slabdata      7      7      0
nf_conntrack_6         0      0    320   25    2 : tunables    0    0    0 : slabdata      0      0      0
nf_conntrack_5         0      0    320   25    2 : tunables    0    0    0 : slabdata      0      0      0
nf_conntrack_3         0      0    320   25    2 : tunables    0    0    0 : slabdata      0      0      0
nf_conntrack_1      1125   1125    320   25    2 : tunables    0    0    0 : slabdata     45     45      0
au_finfo               0      0    192   21    1 : tunables    0    0    0 : slabdata      0      0      0
au_icntnr              0      0    768   21    4 : tunables    0    0    0 : slabdata      0      0      0
au_dinfo               0      0    128   32    1 : tunables    0    0    0 : slabdata      0      0      0

各数字の意味は一番上に出ていますが、例えば1つ目udf_inode_cache用のSLABキャッシュは、全オブジェクトが154個でそのうち88個が使われている。各オブジェクトのサイズ(= uef_inode_cacheデータ構造体のサイズ)は720byteで、(objperslab)1SLABあたりに22個のオブジェクトが割当られている。(pageperslab)はこの22個のオブジェクトを含んだ1SLABを確保するのに何ページ割り当てたか、という事です。

ちなみにSLABを生成する際のページ割当はPageアロケータから行います

3. カーネルエクスプロイトにおけるメモリ管理機構の利用例

さて、では上で説明したPageやSLABアロケーターの知識が実際にカーネルエクスプロイトで必要になるケースを紹介します。

まずは結構昔からあるカーネルヒープオーバーフローのテクニックについて紹介します。

kmallocオーバーフロー

オリジナル論文
The story of exploiting kmalloc() overflows
https://argp.github.io/public/kmalloc_exploitation.pdf

Linuxカーネル内では動的なメモリ割り当てのためのkmalloc/kfreeを利用しますが、このkmallocで割り当てたカーネル内ヒープ領域をオーバーフローしてしまうのがカーネルヒープバッファオーバーフローです。
kmallocは最終的に内部でSLABアロケーターを利用してメモリを確保します。

あれ、待てよ。SLABはそもそもあらかじめサイズが決まっているオブジェクトのプールじゃなかったか?
kmallocは動的にサイズを決められるけどどういう事だ?

と思った方は鋭いです。
以下のコマンドを実行してみてください。

sudo cat /proc/slabinfo | grep '^kmalloc'
kmalloc-8192        1856   1856   8192    4    8 : tunables    0    0    0 : slabdata    464    464      0
kmalloc-4096         824    896   4096    8    8 : tunables    0    0    0 : slabdata    112    112      0
kmalloc-2048        1686   1872   2048   16    8 : tunables    0    0    0 : slabdata    117    117      0
kmalloc-1024        4065   4224   1024   32    8 : tunables    0    0    0 : slabdata    132    132      0
kmalloc-512         8122   9408    512   32    4 : tunables    0    0    0 : slabdata    294    294      0
kmalloc-256        15656  18560    256   32    2 : tunables    0    0    0 : slabdata    580    580      0
kmalloc-192        10372  10668    192   21    1 : tunables    0    0    0 : slabdata    508    508      0
kmalloc-128         7558   8704    128   32    1 : tunables    0    0    0 : slabdata    272    272      0
kmalloc-96         13828  13944     96   42    1 : tunables    0    0    0 : slabdata    332    332      0
kmalloc-64        133803 136320     64   64    1 : tunables    0    0    0 : slabdata   2130   2130      0
kmalloc-32         30312  38656     32  128    1 : tunables    0    0    0 : slabdata    302    302      0
kmalloc-16         12719  12800     16  256    1 : tunables    0    0    0 : slabdata     50     50      0
kmalloc-8          12288  12288      8  512    1 : tunables    0    0    0 : slabdata     24     24      0

はい、つまりkmallocは予想されるサイズごとにSLABをあらかじめ用意しているわけです。
だから例えば
buffer = kmalloc(120, GFP_KERNEL)
とすれば、一番近い128byteに切り上げられてkmalloc-128のSLABからオブジェクトが確保されてその領域がbufferに返されるわけですね。

さて、kmallocの領域がSLABで管理されているのが分かった所でヒープオーバーフローの話に戻りますが、
例えば以下のようなコードがあったとしましょう。

buffer = kmalloc(128, GFP_KERNEL); // allocate 128 byte object in kmalloc-128 SLAB
copy_from_user(buffer,ptr,170);    // copy 170 byte data from user space
buffer2 = kmalloc(128, GFP_KERNEL);// allocate 128 byte object in kmalloc-128 SLAB

bufferに128byteのメモリを確保したのに、copy_from_userでユーザーから170byteの入力を受け取ってbufferにコピーするという物です。
170-128=42byteのヒープオーバーフローが起きてますね。
ではこの42byteのオーバーフローした部分は一体どこに上書きされるでしょうか?
直後にbuffer2に128byteの領域を確保しているので、

kmalloc-128 SLAB: [obj1: buffer] [obj2: buffer2] [obj3] [obj4] ....

となっているから、AAAAAAA.....AAAA(170文字)とか与えると

kmalloc-128 SLAB: [AAAAAAAAA(128byte)] [AAAA(42byte)    ] [obj3] [obj4] ...

とbuffer2を上書きできるでしょうか。

答えは、必ずしも出来るとは限らないです。

なぜかというと、bufferとbuffer2のために同じSLAB内の隣り合ったオブジェクトが使われる保証がないからです。

kmallocというのは、基本的に一番最後にkmalloc-128 SLABの中でfreeされた領域を使うLIFOな物になっています。
つまり二度目のkmallocを呼んだ時、buffer2のオブジェクトとして選ばれるのは、一番最後にfreeされたオブジェクトなので、これがbufferの隣のオブジェクトとは限らないわけです。

せっかくオーバーフローさせても、攻撃者が制御できるbuffer2に上書きされないと意図した関数ポインタの書き換えなどができず、結局制御が奪えないわけですね。

じゃあどうする? というのが上に挙げたkmalloc() overflowの論文の主旨です。

ここでSLABアロケータの知識が重要になってくるのです。

SLABの構造図をもう一度載せます。
f:id:RKX1209:20170713160505p:plain

kmalloc-128キャッシュも複数のSLABを持っていて、その中のどれか一つのオブジェクトがkmallocで選ばれるわけですが、仮に今リストにつながっているSLABが全て限界まで使い切られるとどうなるでしょうか?
つまり、全て真ん中の「オブジェクトの全部が割り当て済みリスト」にある状態です。

すると、SLABアロケータは新たなSLABを生成するためにPageアロケーターにお願いをしてSLAB用のページを取ってきます。ここが重要で、この段階で先ほどのbuffer1,buffer2を2つ確保すれば必ずこのSLAB内の連続したオブジェクトが使われます

これで見事

kmalloc-128 SLAB: [AAAAAAAAA(128byte)] [AAAA(42byte)    ] [obj3] [obj4] ...

とできるわけです。
後はbuffer2に関数ポインタを持った構造体を持ってきて、buffer1から上書きさせて制御を奪うなりすればエクスプロイト成功というわけですね。

4. もう少し応用的な例: CVE-2017-7308 (Reported by Andrey Konovalov)

もう少し応用的なPage/SLABアロケータの利用例として最近Google Project Zeroがブログポストしていたエクスプロイト手法を紹介します。興味がある人は読んでみてください。

元ポスト
googleprojectzero.blogspot.jp

ちなみに報告者のAndrey Konovalovさん(xairyさん)は、有名なロシアのカーネルエクスプロイターで去年まで学生だったという異常な若人です。恐らくGoogleに就職したはずですが、Project ZeroのポストはGuest blog postとなっているので、チーム配属はまだなのかな?


さて、CVE-2017-7308というのはLinuxAF_PACKETソケットにあるバグを利用した物で、ソケット内のring bufferがオーバーフローしてしまうという物なのですが、今回ピックアップしたいのはこのring bufferがPageアロケーターで確保されているという点です。

前節のkmalloc overflowはkmallocで確保された領域のオーバーフローでしたが、今回は直接page_allocを利用してPageアロケーターから確保された領域のバッファオーバーフローです。

さて、このエクスプロイトの本質はpage_allocで確保されたring buffer領域の後に、kmallocを使って攻撃者が確保した領域を持ってきたいという物で、ブログ元の図を引用するとこんな感じにしたいのです。

f:id:RKX1209:20170713181343p:plain

左の青い部分がPageアロケーターで確保されてオーバーフローする領域で、その隣にkmalloc-2048で確保されたオブジェクトを持ってこれれば、エクスプロイト成功というわけです。

これを達成するためにxairy氏は以下のような方法を取りました。

(引用元)
Keeping all that in mind, this is how we can allocate a kmalloc-2048 slab next to a ring buffer block:
1. Allocate a lot (512 worked for me) of objects of size 2048 to fill currently existing slabs in the kmalloc-2048 cache. To do that we can create a bunch of packet sockets to cause allocation of packet_sock structs.
2. Allocate a lot (1024 worked for me) page blocks of size 0x8000 to drain the page allocator freelists and cause some high-order page block to be split. To do that we can create another packet socket and attach a ring buffer with 1024 blocks of size 0x8000.
3. Create a packet socket and attach a ring buffer with blocks of size 0x8000. The last one of these blocks (I’m using 2 blocks, the reason is explained below) is the one we’re going to overflow.
4. Create a bunch of packet sockets to allocate packet_sock structs and cause an allocation of at least one new slab.

訳すと、
1. まずソケットを大量に生成してkmalloc-2048の現在のSLABを満杯にする
2. パケットのring bufferを0x8000 * 1024個生成して、Pageアロケーターの特定のオーダーの空きリストを枯渇させて、一つ上のオーダーの空きリストから確保させるようにする。
(訳注: こうすると一つ上のオーダーが2つにsplitされて、上の図のように左、右で分けられるわけですね)
3. 0x8000のring bufferをまた一つ作る。これが脆弱性をついてオーバーフローさせる対象。
(訳注: この段階で確保された0x8000バイトのring bufferは2で行った調整のおかげで、一つ上のオーダーがsplitされてその左半分が使われるわけです)
4. パケットを大量に生成してpacket_sockをkmallocで新しいSLABに確保させまくる。
(訳注: 1の調整のおかげで現在kmalloc-2018のSLABは満杯です。よって新しいSLABをPageアロケーターから確保されるわけですが、こいつが3で作った左半分がring bufferとなったページの右半分が使われるわけです。
うまい調整ですね。)


このように、オーバーフロー自体はPageアロケーターで起きるのだけど、書き換える先はkmallocで確保した領域という応用的な使い方も可能です。

5. おわりに

さて、今回はとりあえずここまでです。
次回からは実際に手を動かして、カーネル内ROPやStackJackingのコードを書いていきましょう。

あと今年のセキュリティキャンプ受講生は、

小崎先生の「D1 Linuxカーネルを理解して学ぶ脆弱性入門」を受講する方は本記事を事前課題として読んでおいてください。特に2,3節はちゃんと理解しておいてください。

私の「D2-3 カーネルエクスプロイトによるシステム権限奪取 」を受講する方は、加えて次回からの記事も事前課題とします。なるべく早めに公開しますので、それまで本記事をちゃんと理解しておいてください。