いうメモを書いて色々調べていたら、面白い記事を見つけた。Exploitshttp://www.fireeye.com/blog/technical/cyber-exploits/2013/10/aslr-bypass-apocalypse-in-lately-zero-day-exploits.htmlどうやら最近はASLRも回避する方法が確立されつつあるらしい。リンク先で幾つか回避方法が紹介されているが、その最も有力な手法がArrayオブジェクトのlengthの書き換え。overflowなりuse-after-freeなりでarrayのlengthを不正に書き換えることで、最終的に全メモリへのアクセス権をスクリプト上から得る。なればexploitは成功したも同然。ROPのガジェットを集めたり、JITのrwxなページを書き換えることができる。オブジェクトのvtblを書き換えればEIPも奪えるわけで事実上完全に制御を奪うことが出来る。Chromeのsandbox破りこういったArrayの書き換えから実際にブラウザをexploitする解説もあった。PWN2OWNでAndroid上のChromeでexploitを成功させたPinkie氏のコードをchromium.orgの人が解説した資料だ。Writeuphttps://docs.google.com/document/d/1tHElG04AJR5OR2Ex-m_Jsmc8S5fAbRB3s4RmTG_PFnw/edit前述のASLR破りの説明も衝撃だったが、この解説もさらに衝撃的だ。Chromeのsandboxを破っている。ChromeはSandboxに守られているから、DEPを破りASLRを破ったところでやれることは限られている。sandbox内ではファイルシステムへのアクセスやシステムコールの呼び出しが制限されているのだから安心。はずなのに破ったというのだ。やったらそんなことができるのだろう。一応ひと通り上記は読んで理解はしたつもりなので、備忘録も兼ねてその説明を書いていく。最初の綻び、オーバーフロー判定のバグ簡単に解説していくと、まず任意のメモリへのアクセスを得るためにv8のTypedArrayにあるバグを利用する。これがまずちょっとおもしろい。{length:0x1234}のようなオブジェクト(コード中はhugetemplというobj)を作り、それを元にTypedArrayを初期化するとRuntime_TypedArrayInitializeFromArrayLikeというコードが呼ばれる。オブジェクトのlengthプロパティを信用して、TypedArrayを作りましょう、ということだ。その際のメモリ確保量のオーバーフロー判定が肝。0)));9}えーと、配列長と要素のバイト数を掛けて、掛けたのに配列長より下回ったらオーバーフローしていることが分かるので例外を投げる、と。別に問題なくない?僕もそう思ってた時代がありました。これはある条件ではオーバーフローしつつも正常系に進んでしまうのだ。32bit環境でfloat64(8バイト)を0x24924924個確保する。lengthを満たさずにチェックをすり抜けてしまう。ArrayBufferのコピーコンストラクタを利用した隣のヒープの書き換えするとこのとんでもないサイズのアロケーションが走るわけでおかしなことになる。0x24924928バイトのdlmallocをした後にコピーコンストラクタ、これがアロケーション以上のメモリコピー(float64の0x24924924個分)を引き起こし、メモリ破壊が生じる。ここからが個人的にこのexploitの最も理解が難しいところだと思う。普通にこのコピーが走るとヒープを壊してunmapなメモリを触ってクラッシュして終わりになってしまう。このexploitが振るっているのは、コピー元のobject(hugetempl)のgetterをoverrideしていること(__defineGetter__)。getterではオーバーフローして隣のヒープブロックを壊すときのarrayのindexが、確保した大きさとdlmallocの性質から分かっている(このときのarrayのindexをbadとして定義している)。隣のヒープブロックを所定の値に不正に上書きすることができる。sprayの要領で大量の0x20000バイトのArrayを作成して(arrays)、badの位置つまり確保したバカでかいヒープの隣にArrayが配置されるようにする。badに差し掛かった時にそのArrayのデータをgetterの返り値で任意の値に書き換える(Float64なので8バイトごとの書き換えになる点に注意)。ここにはArrayBufferの中身があるだけで、それを書き換えただけでは意味が無い。面白いのは書き換えた次の要素のgetterで自分がsprayしておいたどのarrayを書き換えたかを特定する点。これはarraysを作る際に0x42で全部埋めて、badに差し掛かった時に0x41を書き込むので、どのarrayが書き換わったかは調べれば分かる。コンストラクタ実行中にオブジェクトをすり替えるそしてここからがさらに面白い。その書き換えられたarrayつまり現在不正に書き換えている最中のarrayを解放して、強制的にGCを起こし、同じ場所にWTF::ArrayBufferというインスタンス(詳細は後述)を作る。コンストラクタに制御を戻しbad+2の位置のgetterが呼ばれる。これがちょうどさっき作ったWTF::ArrayBufferのm_sizeInBytesつまり配列の長さの部分になっており、getterで0x7fffffffを返すことでlengthが壊れてそのWTF::ArrayBufferのアドレス以降に自由にアクセスできる配列ができる!そしてbad+3のgetterではexceptionを投げてコピーコンストラクタを途中で終了させる。hugetemplからのarraybufferの生成自体は失敗して制御がJavaScriptに戻る。ちなみにWTF::ArrayBufferというのはWebKit側のオブジェクトであり、v8(JavaScript)の配列(JSArrayBuffer)がexternalizeされるとそのbacking_storeとして生成される。externalはv8からみた外側ということで、blink(WebKit)側のヒープで管理されていることを示す。Arrayをv8側で作ってBlinkの世界へ引き渡すときに、このexternalizeが起こる。いうことでWTFArrayを作るためにWebGLのbufferDataという関数を使っている(force)。blinkからv8へさて、これでCのポインタの性質にほぼ近い魔法の杖WTF::ArrayBufferが手に入った。いうのも上で述べたようにWTF::ArrayBufferはあくまでblinkの世界の話、JSから見えるArrayBufferは書き換わっていないので、相変わらずJSからは正常なrangeしかアクセス出来ない。するんだ、と思ったらmessagingを使ってWTF::ArrayBufferからJSArrayBufferへのロンダリングができるらしい。window.postMessageはwindow間でオブジェクトを送ることが出来るのだけど、そのときに所有権を移転するように指定するとそのロンダリングが起きてしまう。WTF::ArrayBufferによってJSArrayBufferが作られるらしい。いうことで、魔法の杖が出来上がり。任意のメモリ書き換えの実現ただ、この魔法の杖は配列のベースアドレス以降はアクセスできるけど、それより低いアドレスにはアクセス出来ない。いうことで、先ほどのようにWTF::ArrayBufferを量産し、既にある魔法の杖を使ってそのベースアドレスと長さを書き換えてロンダリングすることで、祝福された魔法の杖の出来上がり。これで全メモリ空間にjs上から自由にアクセスできるようになった!任意のコードの実行の実現さてここからは、任意のコードの実行の準備に入る(prepareForCalls)。WTF::DataViewも同様に作ってそのvtblを読む。すると.textのある位置が分かるのでそこからdlsymを呼ぶgadget(コード片)を探す。Table)を辿って、v8のthread_data_table_ポインタをロードする。するとその構造体を辿って行くとJSのヒープの位置が分かる。そこにはrwxなJITされたコード置き場でもある。そこでダミーの見つけやすいコードが生成される関数をevalしてJITされた機械語コードを見つけ、トランポリンに書き換える。トランポリンはcallbufに書き込まれた値をレジスタに書き戻して関数をコールするコード片で、これで任意のnativeの関数やsystemcallを任意の引数でJS上の関数を使ってコールできるようになった!これで攻撃者はsandbox内を完全に制御下に置くことができた。Processとpipeで通信することしかできない。sandbox単体ではファイルシステムにもアクセスできないし(そもそもsandbox内はLinuxならchrootされており仮にファイルシステムが見えてもあまり意味が無い)、ネットワークアクセスもできない。Processが受け持っている。そこではユーザのコンテンツを動かすことはないため、乗っ取られることはない(と信じられている)。Processを攻略する必要がある。landからkernelを攻略するようなもので、相当難しいように思えるが、一体どんな魔法があるのだろう。Process)との間のIPC(プロセス間通信)はpipeで行われる。Processでは送られてきたメッセージをみてsandboxがその機能を使う権限があるのかをチェックして、何らかの処理を実行する。pipeを使うためにはfdが必要だが、これも任意のコードが実行できる状態なので、getsockoptを順に呼ぶことで力技で割り出すことができる。これで任意のメッセージを送れるようになったが、これ自体はsandboxの正当な権利であって当たり前のことができるようになっただけ。あと面白いのはsandbox内のIOスレッドなど自分以外のスレッドのSIGUSR2のシグナルハンドラにfutexを呼ぶように設定し、SIGUSR2を投げて全て止めていること。Processからのメッセージによる外乱を排除してsandboxの制御をより安定なものにする。BrowserProcessの綻び、クリップボードIPCのバグさてこれでIPCでメッセージを送れるようになった。ここでBrowserProcess内に存在するバグを利用する。使うのはClipboardHostMsg_WriteObjectsAsyncというメッセージで、これが凄まじいバグを持っていて任意のアドレスをfreeできてしまうのだ。sprayと組み合わせてvtblの書き換えを行えばプログラムカウンタは攻撃者の手に渡ることは容易に想像がつく。なんてこった、sandboxの意味ねー。sprayにはAudioHostMsg_CreateStreamというメッセージを使う。Processとsandbox間に任意のサイズの共有メモリを作ることが出来るため、sprayにうってつけだ。Processから共有メモリのハンドル(fd)と長さがIPCで送られてくるので、それをmessageReceiveで受け取りmmapで論理アドレスに割り付ける。スプレーの内容がちょっと面白い。mmapしたメモリにサイズが0x68のふりをしたdlmallocのフェイクチャンクをひたすら書き込んでおく。ClipboardHostMsg_WriteObjectsAsyncをそのどこかのアドレスに引っかかるように送信する。process内のアドレスでありsandbox内からは推測することが出来ないからこそ、sprayしておく必要があるわけだ。Processでfree(というかクリップボードのメッセージを処理する)する過程でそのメモリが書き換わるため、freeされたブロックのアドレスをsanboxから特定することができる。call呼び出しへ先ほど0x68バイトのheapを解放したが、それはdlmallocの0x68バイトの解放リストに加わっている。そこでP2PHostMsg_CreateSocketを呼ぶと0x68バイトのcontent::P2PSocketHostTcpが生成されるが、ここで先ほどfreeしておいたフェイクのチャンクが使われる。そのメモリ空間は共有メモリであり、sandboxからP2PSocketHostTcpインスタンスを書き換え可能である!そしてこれはvtblを持っており、そのうちのひとつはP2PHostMsg_DestroySocketを呼ぶことで発動する。Process内で実行されるのは下記で書き込まれたコードである。P2PHostMsg_DestroySocketこれは全てのcookieの中身をurlのqueryに引っ付けることで攻撃サーバへ送信している。Cookieだけじゃなくてブラウザに覚えさせたパスワードも同様の手口で送信できると思われる。Process内で任意のシステムコールを実行できてしまう。これはAndroidでは全てのアプリはzygoteからforkするため、基本的なライブラリに関して同じメモリマップになってしまうのが原因かもしれない。Androidhttp://pdos.csail.mit.edu/~taesoo/pubs/2014/morula/morula.pdfということでsandboxがあってもアプリにバグが有りOSに弱みがあると、このように攻撃者につけ込まれてしまうのであった。所感Chromiumのsandboxを破るなど、僕には到底想像の出来ない所業であったので、この魔法のようなexploitは読んでいて非常に驚きと感動と恐怖を与えてくれた。同時に破られはしたもののsandboxの強力さも実感することが出来た。OSが64bitできっちりASLRを行えばかなり攻略の難易度は高く、相対的にかなり安全と言える。(ただしWindows版Chromeは32bitプロセスで動いているようだが...64bitで動くLinux版のほうが安全なのかもしれない。setuid+seccompのsandboxもWindowsと同じくらい強力だと思うし)しかしである。実はsandboxを無理やり破る必要もないのかもしれない。Processを攻撃せずに、正規の手続きで主要のドメインのcookieを取得することは可能だ。policy)はblinkが自主規制で行っているだけで、webSecurityEnabledというフラグを一つfalseに倒すだけで無効になる。sites.つまり、そのフラグの在処を何かしらの方法で探しだして書き換えてしまえば、iframeの中の情報は抜き放題。取得したデータをXHRで送ればいいのだから、攻略はかなり簡単だ。なると、sandboxがあるから安心とは全く言えない事がわかる。乗っ取りなどの最悪の自体のリスクは劇的に下がることが期待できるが、セッションハイジャック等に関しては特に安全にはなっていない。Processで行う必要があるだろう。1origin=1sandboxとする。isolationというプロジェクトが立ち上がっているようだ。Seidelが提案した今年のプロジェクトのゴールのひとつにも入っているから、重点領域のひとつという位置づけなのだろうか。https://groups.google.com/a/chromium.org/forum/#!searchin/blink-dev/2014$20eric/blink-dev/Z5OzwYh3Wfk/ud32u-5uhu8J所感:Webはどこへ行くのかChromiumは更なるセキュリティ強化に邁進しているようで、これからも安全なブラウザのトップランナーで居続けてくれそうだ。心配なのは、こういったセキュリティの強化は代償として速度やメモリ消費量が犠牲になるということ(sandbox自体も勿論オーバーヘッドがある)。Seidelの計画案の中心はデスクトップからモバイルへのシフトとなっている。ChromiumはデスクトップPCの潤沢なメモリと潤沢なCPU性能の上で如何に高速に動作するか、を念頭に開発されてきたフシがあり、現状のCPU性能が高くはなく低消費電力が求められるモバイルとはかなり相性が悪い。このままこの状態が続くとブラウザが遅いためにHTML5アプリの時代なんぞはモバイルには到来せずネイティブアプリはますます隆盛を極め、衰退しつつあるPCと共にChromiumひいてはWebというもの自体がレガシーなものになりかねない。ブラウザは数あるソフトウェアの中でも、トップクラスの複雑度と高度さを備える大規模ソフトウェアとなってしまった。恐竜のようなものである。この恐竜はPCという環境を最大限に利用して成長し栄華を極めたわけだが、そういったものほど環境の変化には弱い。環境に適応した利点は環境が変わると弱点に変わってしまうことがある。この業界は、今まさにモバイルという大きな隕石が地表に激突し大きく環境が変化しているところだ。様々なハードウェアやソフトウェアの勢力図が塗り替えられている。その中でブラウザは哺乳類のように生き残り更なる繁栄を遂げるのか、恐竜のように化石でしか見られない存在になるのか。いった歴史的な岐路に立っているのかもしれない。