スタックバッファオーバーフロー

ソフトウェアでは、スタック バッファ オーバーフローまたはスタック バッファ オーバーランは、プログラムがプログラムの呼び出しスタック上のメモリアドレスに、通常は固定長バッファである意図されたデータ構造外に書き込むときに発生します。[ 1 ] [ 2 ] スタック バッファ オーバーフローのバグは、プログラムがスタック上にあるバッファに、そのバッファに実際に割り当てられているよりも多くのデータを書き込むときに発生します。これにより、ほとんどの場合、スタック上の隣接したデータが破損し、オーバーフローが誤ってトリガーされた場合は、プログラムがクラッシュしたり、正しく動作しなくなったりします。スタック バッファ オーバーフローは、バッファ オーバーフロー(またはバッファ オーバーラン) と呼ばれる、より一般的なプログラミングの不具合の一種です。[ 1 ]スタックにはすべてのアクティブな関数呼び出しの戻りアドレスが含まれているため、スタック上のバ​​ッファがいっぱいになると、ヒープ上のバッファがいっぱいになるよりもプログラムの実行が妨げられる可能性が高くなります。

スタックバッファオーバーフローは、いわゆる攻撃の一環として意図的に引き起こされる可能性がある。スタック破壊。影響を受けるプログラムが特別な権限で実行されている場合、または信頼できないネットワークホスト(例:ウェブサーバー)からデータを受け取る場合、このバグは潜在的なセキュリティ脆弱性。スタックバッファが信頼できないユーザーから提供されたデータで満たされると、そのユーザーはスタックを破壊し、実行中のプログラムに実行コードを挿入してプロセスを制御できるようになります。これは、攻撃者がコンピュータに不正アクセスするための最も古く、かつ信頼性の高い方法の1つです。 [ 3 ] [ 4 ] [ 5 ]

スタックバッファオーバーフローの悪用

スタックベースのバッファオーバーフローを悪用する標準的な方法は、関数の戻りアドレスを攻撃者が制御するデータ(通常はスタック自体にある)へのポインタで上書きすることです。 [ 3 ] [ 6 ]strcpy()これは次の例で 示されています。

#include <文字列.h>void foo ( char * bar ) { char c [ 12 ];strcpy ( c , bar ); // 境界チェックなし}int main ( int argc , char * argv []) { foo ( argv [ 1 ] ); 0を返す; }

このコードはコマンドラインから引数を受け取り、それをローカルスタック変数にコピーしますc。これは、12文字未満のコマンドライン引数の場合は正常に機能します(下の図Bを参照)。11文字を超える引数はスタックの破損を引き起こします。(安全な最大文字数は、ここではバッファサイズより1文字少ないです。これは、C言語では文字列がヌルバイト文字で終了するためです。したがって、12文字の入力を格納するには、入力の後にゼロバイト(センチネルバイト)が続く13バイトが必要です。このゼロバイトは、バッファの末尾から1バイト先のメモリ位置を上書きすることになります。)

プログラムはfoo()さまざまな入力でスタックされます:

A. - データがコピーされる前。
B. - 「hello」は最初のコマンドライン引数です。
C. - 「AAAAAAAAAAAAAAAAAAAAA\x08\x35\xC0\x80」は最初のコマンドライン引数です。

上図Cでは、コマンドラインで11バイトを超える引数が指定されるとfoo()、ローカルスタックデータ、保存されたフレームポインタ、そして最も重要なリターンアドレスが上書きされます。foo()リターン時に、リターンアドレスがスタックからポップされ、そのアドレスにジャンプします(つまり、そのアドレスから命令の実行を開始します)。このように、攻撃者はリターンアドレスをスタックバッファへのポインタで上書きしchar c[12]、スタックバッファには攻撃者が指定したデータが格納されます。実際のスタックバッファオーバーフロー攻撃では、「A」の文字列は、プラットフォームと目的の機能に適したシェルコードになります。このプログラムに特別な権限(例えば、スーパーユーザーとして実行するためのSUIDビットが設定されている)がある場合、攻撃者はこの脆弱性を利用して、影響を受けるマシンでスーパーユーザー権限を取得する可能性があります。[ 3 ]

攻撃者は内部変数の値を変更してバグを悪用することもできます。次の例をご覧ください。

#include <stdio.h> #include <string.h>void foo ( char * bar ) { float myFloat = 10.5 ; // アドレス = 0x0023FF4C char c [ 28 ]; // アドレス = 0x0023FF30// 10.500000 を出力しますprintf ( "myFloat value = %f \n " , myFloat );/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ メモリマップ:  @ : c 割り当てられたメモリ # : myFloat 割り当てられたメモリ *c *myFloat  0x0023FF30 0x0023FF4C  | |  @@@@@@@@@@@@@@@@@@@@@@@@@@@#####  foo("文字列が長すぎます !!!!! XXXXX"); memcpy は、myFloat 値に 0x1010C042 (リトルエンディアン) を格納します。 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/memcpy ( c , bar , strlen ( bar )); // 境界チェックなし...// 96.031372 を出力しますprintf ( "myFloat value = %f \n " , myFloat ); }int main ( int argc , char * argv []) { foo ( "文字列が長すぎます !!!!! \x10\x10\xc0\x42 " ); return 0 ; }

スタックに格納されたアドレスを変更するには、通常、直接攻撃と間接攻撃の2つの方法があります。攻撃者は、直接攻撃を軽減するために講じられた保護対策を回避するために、依存関係の少ない間接攻撃を開発し始めました。[ 7 ]

多くのプラットフォームでは、コールスタックの実装に微妙な違いがあり、それがスタックバッファオーバーフロー攻撃の動作に影響を与える可能性があります。一部のマシンアーキテクチャでは、コールスタックの最上位レベルのリターンアドレスをレジスタに格納します。つまり、上書きされたリターンアドレスは、後でコールスタックが展開されるまで使用されません。攻撃手法の選択に影響を与える可能性のあるマシン固有の詳細のもう1つの例として、ほとんどのRISCスタイルのマシンアーキテクチャでは、メモリへの非整列アクセスが許可されないという事実があります。[ 8 ]マシンオペコードの長さが固定されていることと相まって、このマシンの制限により、スタックへのジャンプ手法の実装がほぼ不可能になる可能性があります(唯一の例外は、プログラムにスタックレジスタに明示的にジャンプするありそうもないコードが含まれている場合です)。[ 9 ] [ 10 ]

成長するスタック

スタックバッファオーバーフローの話題において、よく議論されるものの、めったに見られないアーキテクチャとして、スタックが逆方向に拡張するアーキテクチャがあります。このアーキテクチャの変更は、スタックバッファオーバーフロー問題の解決策として頻繁に提案されています。これは、同じスタックフレーム内で発生したスタックバッファのオーバーフローは、リターンポインタを上書きできないためです。しかし、以前のスタックフレームのバッファで発生したオーバーフローは、依然としてリターンポインタを上書きし、バグを悪用する悪意のある攻撃を可能にします。[ 11 ]例えば、上記の例では、fooオーバーフローは実際には のスタックフレーム内で発生するため、 のリターンポインタは上書きされませんmemcpy。しかし、 の呼び出し中にオーバーフローするバッファはmemcpy以前のスタックフレームに存在するため、 のリターンポインタはmemcpyバッファよりも数値的に高いメモリアドレスを持ちます。つまり、 のリターンポインタがfoo上書きされるのではなく、 のリターンポインタがmemcpy上書きされることになります。つまり、スタックを逆方向に拡張することで、スタックバッファオーバーフローの悪用方法の詳細が多少変わる程度で、悪用可能なバグの数が大幅に減少するわけではないということです。

保護スキーム

長年にわたり、悪意のあるスタックバッファオーバーフロー攻撃を阻止するために、制御フロー整合性スキームが数多く開発されてきました。これらは通常、以下の3つのカテゴリに分類できます。

  • スタック バッファ オーバーフローが発生したことを検出し、命令ポインタが悪意のあるコードにリダイレクトされるのを防ぎます。
  • スタック バッファ オーバーフローを直接検出せずに、スタックからの悪意のあるコードの実行を防止します。
  • 実行可能コードを見つけるのが信頼できなくなるように、メモリ空間をランダム化します。

スタックカナリア

スタック カナリアは、炭鉱のカナリアに例えて名付けられ、悪意のあるコードが実行される前にスタック バッファ オーバーフローを検出するために使用されます。この手法は、プログラム開始時にランダムに選択される値の小さな整数を、スタック リターン ポインタの直前のメモリに配置することで機能します。ほとんどのバッファ オーバーフローは、メモリを下位のメモリ アドレスから上位のメモリ アドレスに上書きするため、リターン ポインタを上書きしてプロセスを制御するには、カナリア値も上書きする必要があります。この値は、ルーチンがスタック上のリターン ポインタを使用する前に変更されていないことがチェックされます。[ 2 ]この手法では、スタック上の他の重要な変数を破壊するなどの非伝統的な手段によって攻撃者に命令ポインタの制御権を取得させるため、スタック バッファ オーバーフローの悪用が著しく困難になります。[ 2 ]

実行不可能なスタック

スタックバッファオーバーフロー攻撃を防ぐもう一つの方法は、スタックメモリ領域にスタックからの実行を禁止するメモリポリシー(W^X、「Write XOR Execute」)を適用することです。つまり、スタックからシェルコードを実行するには、攻撃者はメモリからの実行保護を無効にするか、シェルコードのペイロードを保護されていないメモリ領域に配置する必要があります。この手法は、ほとんどのデスクトッププロセッサで実行禁止フラグのハードウェアサポートが利用できるようになったため、より一般的になりつつあります。

この方法は典型的なスタックスマッシング攻撃を阻止しますが、スタックオーバーフローは他の方法でも悪用される可能性があります。まず、シェルコードをヒープのような保護されていないメモリ領域に保存する方法は一般的であるため、攻撃方法にほとんど変更を加える必要はありません。[ 12 ]

もう一つの攻撃は、いわゆるReturn to libc方式によるシェルコード生成です。この攻撃では、悪意のあるペイロードはシェルコードではなく、適切なコールスタックをスタックにロードします。これにより、実行は一連の標準ライブラリ呼び出しにベクトル化されます。通常、これによりメモリ実行保護が無効化され、シェルコードは通常通り実行されます。[ 13 ]この攻撃が機能するのは、実行が実際にはスタック自体にベクトル化されないためです。

return-to-libc の亜種として、リターン指向プログラミング(ROP)があります。これは、一連のリターンアドレスを設定し、各リターンアドレスは既存のプログラムコードまたはシステムライブラリから厳選されたマシン命令の小さなシーケンスを実行し、最後に return で終了します。これらのいわゆるガジェットは、それぞれがリターンする前に単純なレジスタ操作などの実行を行い、これらを連結することで攻撃者の目的を達成します。リターン命令とよく似た動作をする命令または命令群を悪用することで、「リターンレス」なリターン指向プログラミングを使用することさえ可能です。[ 14 ]

ランダム化

コードとデータを分離する代わりに、実行プログラムのメモリ空間にランダム化を導入するという軽減策もあります。攻撃者は使用可能な実行コードがどこに存在するかを判断する必要があるため、実行可能ペイロード(実行可能スタックを含む)を提供するか、ret2libcやリターン指向プログラミング(ROP)などのコード再利用を用いてペイロードを構築します。メモリレイアウトをランダム化することで、概念的には攻撃者がコードの場所を把握できなくなります。しかし、実装によっては通常、すべてがランダム化されるわけではありません。通常、実行可能ファイル自体は固定アドレスにロードされるため、ASLR(アドレス空間レイアウトランダム化)と非実行スタックを組み合わせた場合でも、攻撃者はこの固定メモリ領域を利用できます。したがって、すべてのプログラムはPIE(位置独立実行可能ファイル)でコンパイルし、このメモリ領域もランダム化する必要があります。ランダム化のエントロピーは実装ごとに異なり、エントロピーが十分に低い場合、ランダム化されたメモリ空間に対するブルートフォース攻撃において問題となる可能性があります。

バイパス対策

これまでの緩和策により、エクスプロイトの手順はより困難になっています。しかし、何らかの脆弱性が存在する場合、または特定の条件が満たされた場合、スタックバッファオーバーフローを悪用することは依然として可能です。[ 15 ]

スタックカナリアバイパス

フォーマット文字列の脆弱性を悪用した情報漏洩

攻撃者はフォーマット文字列の脆弱性を悪用して、脆弱なプログラム内のメモリ位置を明らかにすることができます。[ 16 ]

実行不可能なスタックバイパス

データ実行防止(DEP)が有効になってスタックへの実行アクセスが禁止されている場合でも、攻撃者は上書きされた戻りアドレス(命令ポインタ)を使用して、コードセグメント( Linuxでは.text)またはプログラムの他のすべての実行可能セクション内のデータを指すことができます。その目的は、既存のコードを再利用することです。[ 17 ]

ロープチェーン

プログラムのリターン命令(x86ではret)の直前のリターンポインタを上書きする。新しいリターンポインタとリターン命令の間の命令が実行され、リターン命令はエクスプロイトによって制御されるペイロードに戻る。[ 17 ]

ジョブチェーン

ジャンプ指向プログラミングは、ret命令の代わりにジャンプ命令を使ってコードを再利用する手法である。[ 18 ]

ランダム化バイパス

64ビットシステムにおけるASLR実現の限界は、メモリ開示や情報漏洩攻撃に対して脆弱であることです。攻撃者は情報漏洩攻撃を用いて単一の関数アドレスを明らかにすることで、ROPを起動することができます。次のセクションでは、ASLR保護を破るための同様の既存の戦略について説明します。[ 19 ]

注目すべき例

参照

参考文献

  1. ^ a b Fithen, William L.; Seacord, Robert (2007-03-27). 「VT-MB. メモリ境界違反」 US CERT .
  2. ^ a b cダウド, マーク; マクドナルド, ジョン; シュー, ジャスティン (2006年11月).ソフトウェアセキュリティ評価の技法.アディソン・ウェスリー. pp.  169– 196. ISBN 0-321-44442-6
  3. ^ a b cレヴィ、エリアス(1996年11月8日). 「楽しみと利益のためにスタックを壊す」 . Phrack . 7 (49): 14.
  4. ^ Pincus, J.; Baker, B. (2004年7~8月). 「スタックスマッシングを超えて:バッファオーバーランの悪用における最近の進歩」(PDF) . IEEE Security & Privacy . 2 (4): 20– 27. Bibcode : 2004ISPri...2d..20P . doi : 10.1109/MSP.2004.36 . S2CID 6647392 . 
  5. ^ Burebista. 「Stack Overflows」(PDF) . 2007年9月28日時点のオリジナル(PDF)からのアーカイブ
  6. ^ Bertrand, Louis (2002). 「OpenBSD: バグを修正し、システムを安全にする」 . MUSESS '02: McMaster University Software Engineering Symposium . 2007年9月30日時点のオリジナルよりアーカイブ
  7. ^ Kuperman, Benjamin A.; Brodley, Carla E.; Ozdoganoglu, Hilmi; Vijaykumar, TN; Jalote, Ankit (2005年11月). 「スタックバッファオーバーフロー攻撃の検出と防止」 Communications of the ACM . 48 (11): 50– 56. doi : 10.1145/1096000.1096004 . ISSN 0001-0782 . S2CID 120462 .  
  8. ^ pr1. 「SPARCバッファオーバーフローの脆弱性の悪用」{{cite web}}: CS1 maint: 数値名: 著者リスト (リンク)
  9. ^ Curious (2005-01-08). 「リバースエンジニアリング - GDBを用いたMac OS XでのPowerPCクラッキング」 . Phrack . 11 (63): 16.
  10. ^ Sovarel, Ana Nora; Evans, David; Paul, Nathanael. FEEBはどこにあるのか?命令セットランダム化の有効性(レポート).
  11. ^ Zhodiac (2001-12-28). 「HP-UX (PA-RISC 1.1) のオーバーフロー」 . Phrack . 11 (58): 11.
  12. ^ Foster, James C.; Osipov, Vitaly; Bhalla, Nish; Heinen, Niels (2005).バッファオーバーフロー攻撃:検出、悪用、防止(PDF) . アメリカ合衆国: Syngress Publishing, Inc. ISBN 1-932266-67-4
  13. ^ Nergal (2001-12-28). 「高度な return-into-lib(c) エクスプロイト:PaX のケーススタディ」 . Phrack . 11 (58): 4.
  14. ^ Checkoway, S.; Davi, L.; Dmitrienko, A.; Sadeghi, AR; Shacham, H.; Winandy, M. (2010年10月). 「リターンを使わないリターン指向プログラミング」.第17回ACMコンピュータおよび通信セキュリティ会議 - CCS '10 議事録. pp.  559– 572. doi : 10.1145/1866307.1866370 . ISBN 978-1-4503-0245-6. S2CID  207182734 .
  15. ^ Shoshitaishvili, Yan. 「メモリエラーとプログラムセキュリティ」 . pwn college . 2024年9月7日閲覧
  16. ^ Butt, Muhammad Arif; Ajmal, Zarafshan; Khan, Zafar Iqbal; Idrees, Muhammad; Javed, Yasir (2022年1月). 「バッファオーバーフロー回避技術に関する詳細な調査」 .応用科学. 12 (26): 6702. doi : 10.3390/app12136702 . ISSN 2076-3417 . 
  17. ^ a b Butt, Muhammad Arif; Ajmal, Zarafshan; Khan, Zafar Iqbal; Idrees, Muhammad; Javed, Yasir (2022年1月). 「バッファオーバーフロー回避技術の徹底調査」 .応用科学. 12 (13): 12– 13. doi : 10.3390/app12136702 . ISSN 2076-3417 . 
  18. ^ Sécurité matérielle des systèmes (フランス語)。 2022年9月3日。
  19. ^ Butt, Muhammad Arif; Ajmal, Zarafshan; Khan, Zafar Iqbal; Idrees, Muhammad; Javed, Yasir (2022年1月). 「バッファオーバーフロー回避技術に関する詳細な調査」 .応用科学. 12 (16): 6702. doi : 10.3390/app12136702 . ISSN 2076-3417 . 
  20. ^ 「インターネットワームに関する報告」 1988年11月7日。
  21. ^ [1]
  22. ^ [2]
  23. ^ 「Twilight Hack - WiiBrew」 . wiibrew.org . 2018年1月18日閲覧
  24. ^ 「Smash Stack - WiiBrew」 . wiibrew.org . 2018年1月18日閲覧