防御技術ではなく攻撃技術についての解説。spraying技術は出てきません。1909での情報であり、それ以外では異なる可能性があります。overflowなどのその他脆弱性への対策が十分ならば問題にならない前置きASLRにより固定アドレスが参照できないというのは攻撃者にとっては非常に厄介です。overflowを起こしても、アドレスが不明だと素直な攻撃は一切通らず、せいぜいがクラッシュさせる程度が関の山だったり。そのためクラッカーの間で様々なASLRの迂回技術が研究されてきました。spraying(日本語訳:ヒープスプレー)もその1つで、最近は主にブラウザへの攻撃で猛威を振るっています。概要ぐらいは知っている人も多いとは思いますが具体的にどの程度の難度なのかは把握していない人も多いのではないかと思います。今回はそのあたりを少しでも示して行けたらなと。sprayingとは、ヒープを大量のデータで埋めることで固定アドレス指定でも任意のデータにたどり着ける状態を作り出すことです。機械語であれば大量のNOPで埋めた後その末尾に目的の命令を置けば実質的にNOPのどこを指しても目的の命令の先頭アドレスを指したのと同じ意味になります。これはROPなどでも同様で、攻撃手法ごとに様々なNOP互換のデータが考案されている状態です。このようにして固定アドレスを指定できるようになるとASLRの影響を一部とはいえ無視することが可能になり、情報漏洩脆弱性との併用なしでの実行が可能になるなど攻撃コードの可搬性を高めることが出来ます。詳しい解説は置いておいてとりあえず実践してみましょう。)※ROPとはなにかや改造前のコードについての解説はしません、詳細は過去の記事を参照してください。overflowしない形に改造、またなんらかの攻撃を受けた想定で固定アドレスにreturnしています。std::vectorはどこにメモリーを確保するかわかりませんし、スタックポインタをいじれるにしてもぱっと見ではROPにつなげるのは難しく見えますね。攻撃できます、攻撃ファイル生成コードがこちら。result;}ハイライトされている行で60MB詰め物しているだけです。attacker.exeを起動してファイル生成後target.exeを実行してみましょう。電卓が起動したはずです。試しに何度か実行してみても同じ結果のはずです。sprayingの概要説明で大量のNOPで埋めると書きました。実際上記のコードで埋めているコードは無意味にretするコードであり、ROPにおいてはNOPに該当するコードと言って差支えがありません。以前説明したように固定アドレスでアクセスできるようメモリを埋めるのはx86アーキテクチャであってもメモリ空間は4GBもあり、たかが60MBで高確率と言えるほど埋まるのは直感に反しています。この不思議を説明するには、ヒープのアドレスがどう決まるのかについて大雑把にでも理解する必要があります。ヒープアドレスはどう決まる?ヒープアドレスを知るにはメモリがどのように使用されていくかを把握しなければなりません。各種メモリは大きく分けて3種の方法で割り当てられます。具体例を挙げると.exeファイルは固定アドレス、ヒープやスタックは小さい順、ほとんどの.dllは大きい順に属しています。.exeでベースアドレスをfixedで0x00400000と指定すればその通りに割り当てられるのが固定アドレス。※/DYNAMICBASEを指定した.exeも指定ベースアドレスを基準にランダム幅があるだけなので固定に属しています。ntdll.dllが0x775C0000に割り当てられたなら、次のwow64cpu.dllは0x775B0000に割り当てられるといった形でアドレスが若くなっていくので大きい順。小さい順はその逆です。>大きい順の優先度で確保していると考えると大体実態通りの結果になります(もちろん例外はたくさんあります)そしてヒープは小さい順に属しています。他のメモリ確保が存在せず、ランダム幅もなく、その他例外事項もなければ最初のヒープは0x00000000に確保されます。なると小さい順に他の何が属しているのか、サイズはどれぐらいか、ランダム性はあるのか、その他何か特殊挙動はないかが問題となってきます。次項からはわかっている範囲でそれらを順に記載していきます。dereferenceの問題から間違ってもNULLポインターの先にメモリが存在しないよう、0x00000000にはページ割り当てできないようになっています。そのためなにがあっても一番小さいアドレスは0x00010000となります。ASLR昔、小さい順に確保するメモリについてランダム性が低くほぼ固定アドレスだったため問題視されることがありました。具体的には一番最初に確保するメモリを0x00010000-0x00FF0000の範囲を0x10000刻みの255パターンからランダムに選ぶというものです。選んだ箇所が既に使用中の場合、それ以降から下4桁が0x0000になる範囲で最初に空いている箇所が選ばれます(ので.exeの固定配置で0x00010000-0x00FF0000を埋めると実質無効化が出来ます).exeの/DYNAMICBASEベースアドレス未指定の場合.exeも0x00010000-0x00FE0000の範囲からスタートするように割り当てられます。前述のとおり小さい順確保のメモリよりも先に占有するため、既にメモリが埋まっていることがあり、その場合は.exeの使用領域を飛び越えその後ろへ割り当てられます。スタックベースアドレスランダマイズスタックはヒープより先に確保され、なおかつベースアドレスはランダマイズされています。ランダム幅は0x00010000ごとに32通りで最大0x001F0000ずれます。ヒープベースアドレスランダマイズヒープのベースアドレスもランダマイズされています。ランダム幅は0x00010000ごとに32通りで最大0x001F0000ずれます。ヒープ内ランダマイズヒープは既存確保内で提供できるサイズの場合、ヒープ内先頭からではなくランダムな箇所からメモリを払い出す機能を保持しています。そのため同じようにヒープを確保しても同じアドレスにはなりません。残容量など次第ではありますが、おおよそ508KB以下の確保が対象となります。ヒープ内ガードページヒープは一定のルールに従ってガードページ(アクセス不能領域)を挿入しています。合計が64KBを超えるごとに4KBのガードページ一度の確保メモリが508KB以下ガードページなしそれ以上サイズ関係なく毎回4KB*(0~15のランダム)のガードページこれらはヒープアロケーターに何を使っているかに対応しています。詳しくはLFHアロケーター、VSアロケーター、ヒープバックエンドなどで調べてください。Guardのマップ情報もスタックやヒープより前に確保されます。x86だと最大で32MB使用します。連続した巨大メモリが必要であるため、.exeなどのアドレス次第で割り当てメモリが大きくずれることがあります。その他ヒープより手前で確保されるメモリは多岐にわたりそのすべてを紹介することはできません(し、私も把握していません)一応私が把握している範囲で上記にあげなかったものをいくつか名前だけ上げておきます。Blockもちろん上記以外にもあります。ここまでで上げた物以外でランダム幅がある物は今の私が知る範囲では確認していません。Guardの有無も考慮する場合+32MB=>約52MBその他メモリ使用量の増減を警戒し多少幅を持たせる場合=>約60MBというわけで環境にもよりますがヒープは60MB程度しか前後しないことがわかりました。ここから攻撃したいタイミングでどの程度ヒープを消費しているかを加えて計算すれば、おおよそ60MB確保時にアクセス可能なアドレスを導出できてしまいます。今回のtarget.exeで言えばランダム幅を無視したオフセットは0x2E0000であるため、それに60MBを足した0x3EE0000にアクセスすればほぼ確実にヒープ内にアクセスできたわけです。実際手元の環境ではサンプルを1万回実行しても100%攻撃成功しました。当然ですが60MB一続きである必要はなく、508KBほど確保して末尾に攻撃コードを仕込むを120回繰り返しても大体同じ効果があります(その場合若干失敗する可能性が出てきます)また複数の乱数が合わさって機能しているため、最小値や最大値は出る確率が低く平均値付近が出やすいという偏りが発生しています。一度だけ攻撃が通ればいいという状況の場合、さらに攻撃難度は低下するでしょう。Protection)に対策はありません。そのうえで対策を行う場合以下が挙げられます。ASLR)それにより確保しなければならないメモリーを大幅に上昇させることができ、攻撃の難度が大きく向上します。可能であるならば一番現実的かつ効果的な対策になるでしょう。sprayingはあくまで他の脆弱性ごしの攻撃時にASLRを回避するために使用するものです。逆に言えば単独では何ら害のない攻撃であり、放置していても何の問題もありません。overflowやUse-after-free攻撃などへの対策を万全に行い脆弱性の総数を大幅に減らすことが出来れば、相対的に脅威度は大幅に下がります。メモリの確保を外部に許可しないこれはアプリの種類によっては非常に難しい物です。sprayingは何ら問題にはなりません。確保させる場合でも合計で20MB以下に抑えることが出来れば成功率が下がり防御効果がある可能性があります。いえ設定ファイルやアプリ拡張などをユーザーやサードパーティーから読み込む構造である場合どうしようもないことがほとんどです。sprayingについて理解する始めの一歩程度に役立ったなら幸いです。今回浅くしか説明できなかった各要素についていずれ深堀出来たらよいのですが......。今回も敗北回、もしくは注意喚起。前置きクラッカーの攻撃にも段階があるわけですが、今回はヒープやスタックに配置したコードを実行できたが、APIを叩く準備はできていない段階への対策技術です。この段階が重要かというと、脆弱性をついての攻撃では往々にしてリンク処理の恩恵を受けられないため、各種APIのアドレスがわからずただの計算しかできないタイミングというのが高確率で生まれるからです。そこで攻撃者はAPIアドレス特定作業を行うわけですが、仮にそれを全て阻止できたならばクラッシュさせる程度の被害しか出すことが出来ず、被害の最小化を図れるのでは?という考え方ですね。Filtering(以降EAF)です。結果各APIのアドレスは不明この状況から攻撃にもっていきます、本記事では電卓(calc.exe)の起動を目標としましょう。簡便化のため、実際にスタック領域にコードを置くのではなく、普通にコード領域に実装するがメモリアクセスだけスタック領域に置いたコードを通すようにします。見た目的にはただの一般アプリとなります。やることは単純で、普段ローダーがリンク時に行うアドレス解決と同様の手法でWinExec関数のアドレスを入手し”calc.exe”を引数に渡して実行する、これだけ。ちなみに.dllのアドレス取得の流れは本記事では解説しません。調べてみると良いでしょう。実際に試してみようサンプルの中のNoGuard.exeを実行してみると電卓が起動します。いってもこれだけだと何が何やらだと思うのでコードの方も見ていきましょう。TableをパースしてWinExecのアドレスを取得して実行しているだけです。パース自体難しそうに見えるかもしれませんが、全てMSDNに定義が載っているので時間さえかければ誰でも書ける程度のものです。一応スタック上から実行していることをエミュレートするために読み取りはread*関数を通してアクセスしています。※read*はスタック上にコードを書いてそこを実行しています。これで.dllのアドレスさえわかれば中のAPIを呼ぶことは難しくないのは伝わったかと思います。Tableへのアクセスやパースに他APIは必要ない通常はスタックからアクセスしても問題はない次はこれにEAFで対抗します。ProtectionというWindows10に備わったセキュリティ機能群の一つです。Tableへのアクセスを制限する機能です。※AddressOfFunctionsなどは実際に上のコード片でも触れているので読み返してみるのも良いでしょう。メモリーをアクセス禁止にし、アクセス時に上がる例外をキャッチしてあれこれする形で実装されています。具体的には、該当メモリにPAGE_GUARD属性を付与してアクセスを禁止。アクセス時に発生した例外をキャッチし不正なアクセスならばアプリをクラッシュ。正当なら一瞬PAGE_GUARDを解除してシングルステップ実行で読み取らせ再度PAGE_GUARDを付与。正当か不正かはコードの配置されたメモリが正当にリンクされたコード領域上かを見て判断しています。EMETに最初実装された際はデバッグレジスタという該当メモリにアクセスしたこと自体をトリガーとする技術が使われていましたが、4か所しか設定できない、競合することが多いなど批判が多かったためか変更されたようです。Protectionから有効にする必要があります。Protectionと入力>プログラム設定>プログラムを追加してカスタマイズ>ファイルパス上記で対象.exeを追加しEAFを有効にすることで試すことが出来ます。実際に試してみようExploit1の.exeを対象にしてEAFを有効にして実行してみましょう。電卓が起動せずクラッシュしたら成功です。防がれた原理としては、65行目のアクセスがEAFの判定対象となり、スタックからのアクセスであるため不正とみなされたわけです。実はEAFには致命的欠陥があり簡単に回避できてしまいます。EAFはアクセス範囲ではなくアクセスの先頭アドレスだけを見ている上に、完全一致で弾くべきかどうかを見ています。読み取りアドレスを前後させてやるだけで簡単に回避できます。実際に試してみようサンプルのBypassEAF.exeを前回同様の手順でEAF保護したうえで起動してみましょう。今回は問題なく電卓が起動してしまったはずです。なったのか差分はスタックからの読み取り関数に若干の修正を入れただけです。ptr1の上位2byteとptr2の下位2byteをくっつけるに読み方を変えただけです。これらは意味的には完全に等価であり、後者が若干遅いという以外に違いがありません。スタック上に書くのが難しい/実行が難しい/なんらかの技術で検出できる、などの制約も皆無と言ってよく、事実上ほぼ0コストで実現できてしまいます。回避コストがほぼ0であるため、原理がわかれば事実上無防御とほぼ変わりがない状態と言えるでしょう。EAF回避まとめEAFは読み取りアクセスを軽く前後にずらすだけで回避できるずらすのはとても簡単ちなみにこの致命的さ具合は脆弱性なのでは?とMSに報告を送ってみましたが「修正する気はないので公開したければ好きにするといいよ(意訳」とのことで、MS自体もうEAFを切り捨てているも同然のようです。その他回避法色々アドレスずらしただけで回避できた時点で相当ですが、他にもいろいろと回避方法が存在するのでほんのり触れておきます。ROPで回避ROPにより正規.dllのコード領域からメモリ読み込み命令することが可能です。これを用いてAddressOfFunctionsなどを読んでやれば回避できます。マルチスレッドPAGE_GUARDを解く際にもスレッドは中断されないので、他のスレッドが正規アクセスをしPAGE_GUARDが解除された瞬間に読み取りを指示すれば問題なく読むことが出来ます。VirtualProtectPAGE_GUARDを付与しているのが要であるためVirtualProtectAPIなどを利用してPAGE_GUARDを解除できれば問題なく読むことが出来ます。本記事ではさらっと流しましたがEAFはROPによる回避が出来た時点で事実上死んだ技術です。ROPも多少手間とはいえ完全な回避策であるため、ROPの対策が為されない限り不意打ち防御としてしか意味がないためです。現状のWindowsではROPに対する対策はROPを使われる前に止める以上のものがありません。アドレスずらしという更に安易な回避法が見つかっても、そちらに対処することなく放置する選択を取っているのでしょう。EAFに動作不良リスクを負ってまで採用する価値はありません。終わりにネガキャン回が続いていますが、セキュリティ技術というものは基本的にリスクが高い技術なので、突破されるなどして有用性を喪失した技術はしっかりと見分けて除外する必要があります。その点で言うとEAFは間違いなく有用性を喪失しているため、万が一にも採用はお勧めしません。私の書いている記事群がそういった見極めの良い材料となっていることを願います。