私は低レベルのプログラマーではありません。主にマネージ言語であるC#でプログラミングします。それでも、ビデオを含むメモリ安全性の修正について話している最も多様なソフトウェアに関する記事、ニュース、パッチノートを時々読んでいます(Rustとメモリの安全性について正しく覚えていれば) Microsoftはメモリの安全性を修正するために人々にお金を払うだけで多くのお金を失うとホストは言いました。「メモリの安全性」が正確に何を意味するか、どのようなコードがメモリの安全性を生み出すのか、科学者(または攻撃者)がそれらをどのように見つけるか、方法そして、どの程度、それを悪用することができ、それを含むコードの部分をどのように修正しますか?誰かが最小限のコード例(できればCで記述)でこれらの質問に答えることができれば、非常に有益です。
ほとんど メモリ関連のセキュリティ問題の悪用 バッファオーバーフロー :
このようなバグは攻撃者によってどのように検出されますか?
それらの多くはI/Oで発生するため、ネットワークトラフィック、ファイルI/O、ユーザー入力などです。したがって、攻撃の最初の行は、これらの機能を非常に大きなデータに送信することです。次に、そのようなデータがアプリケーション全体に伝播されるさまざまな状況をテストします。
別の方法は、コードが使用しているライブラリとプラットフォームを見つけ、悪用される可能性のある既知の脆弱性があるかどうかを確認することです。
それから確かに他のトリックがありますが、私はセキュリティ研究者ではないので、ここでは一般的な知識以上のものを伝えることはできません。
良い知らせは、テストケースで最初に実行して、そのような問題がリリースされるのを防ぐことができることです。 2番目のことを行うこともでき、クライアントがそれらのライブラリの最新のパッチバージョンを取得していることを確認します。
別のメモリの安全性は、メモリ割り当ての処理が不適切です。これは十分なメモリがない場合と関係がありますが、プログラムはこれが発生しないことを前提としています。
これらは、メモリが非常に少ない環境でコードを実行することにより、攻撃者によって検出されます。
幸いなことに、通常のフローと同じくらい真剣に例外状況をテストすることで、問題を防ぐことができます。
最後に、メモリリークは、メモリ不足(上記参照)を介して間接的に、またはメモリにデータを必要以上に長く保持することにより、セキュリティの脆弱性につながる可能性があり、メモリダンプの場合にそれを公開します。繰り返しになりますが、これはC#では発生しないはずですが、たとえば不要な循環参照などを検出するには、C#環境でトリッキーなアルゴリズムが必要です。そのため、マイクロソフトはバグハンティングに多額の投資をしなければなりません。
Cでは、標準的な例は(もはや標準ではない)gets
ライブラリ関数です。
char *gets( char *buf );
gets
関数は、バッファのアドレスを受け取り、改行またはファイルの終わりが見つかるまで標準入力からそのバッファに文字を読み込みます。ここに問題があります-gets
はターゲットバッファの大きさを知りません。それが知っているのは開始アドレスだけです。
したがって、10文字を保持できるサイズのバッファのアドレスを渡しても、次の改行の前に100文字ある場合、それらの余分な文字はバッファの終わりの直後にメモリに書き込まれ、他の変数を上書きする可能性がありますまたはスタックを破壊しています。
これは、ほとんどのC標準ライブラリの問題です。バッファの終わりを超えて書き込まれていないこと、またはバッファが所有していないメモリに書き込まれていないことを確認するための境界チェックは行われません。 gets
は特に悪質で、1999年の改訂後に廃止され、2011年の改訂から完全に削除されましたが、その問題がある唯一のライブラリ関数とはほど遠いものです。
同様に、Cは算術型の範囲チェックを行いません。 9999999999999999999999
のような入力をint
に保存しようとすると、somethingが保存されますが、期待したものは保存されません。
これは意識的な決定であり、Cコードを高速かつコンパクトにする理由の1つです。境界チェックはCPUサイクルを消費し、特にCがアプリケーションプログラミング言語ではなくシステムプログラミング言語として意図されていたため、1970年代初頭(Cが最初に開発されたとき)に重要でした。残念ながら、それは、これらのチェックを自分でコーディングするか、バッファがすべての可能な入力に対して常に十分な大きさであることを確認するかは、プログラマー次第であることを意味します。
JavaやC#などの新しい言語はこれらの境界チェックを行い、配列の境界外のメモリにアクセスしようとすると例外をスローし、キャッチして回復する方法を提供します。
安全ではないメモリのアンチパターンは多数あり、タイプごとに複数のエクスプロイトが存在する可能性があります。大まかに言えば、アプリケーションが持つべきではないメモリの部分へのアクセスをアプリケーションが許可する場合です。 1つのアプリケーションが別のアプリケーションの作業メモリに直接アクセスできるようにするオペレーティングシステムの非常に基本的な例。その場合、オペレーティングシステムは「安全でないメモリ」へのアクセスを許可します。このような問題は、コンピューティングの初期の時代には前例のないものではありませんでしたが、今は解決された問題だと考えるのはかなり安全だと思います。
エクスプロイトに関連する主なものは、クリストフの回答で述べられているバッファオーバーフローです。一般的なシナリオは、プログラムが配列アクセスが配列の境界内にあるかどうかをチェックしない場合です。たとえば、100の配列を割り当てても、インデックス100の値の読み取りまたは書き込みは許可されている場合。私のCスキルは基本的に存在しないため、例を この記事 からコピーしました:
#include <stdio.h>
#include <string.h>
int main(void)
{
char s[] = "Hello world";
s[strlen(s)] = ’!’; // Try to append a ! to the string
printf("%s\n", s);
return 0;
}
この説明は次のとおりです。
7行目では、末尾のNULL文字が!で上書きされ、新しい文字は追加されません。行8はこの文字列をstdoutに送信しようとします-おそらく文字列の後に何らかのガベージが続いて出力されますが、クラッシュする可能性があります。
これは、欠陥の基本的な形状です。そこで、境界をチェックしないコードにユーザー提供の入力を取り込むとどうなるかを考えてみましょう。ユーザーが配列に割り当てられているよりも多くの文字を指定すると、余分な文字が隣接するメモリの場所に配置されます。エクスプロイトが厄介になるのは、それらの隣接するメモリの場所に命令が含まれている場合です。ユーザー(またはこの場合は攻撃者)は、これらの指示を自分で選択したものに置き換えることができます。何年も前に、これは非常に効果的でした。隣接するメモリの内容を把握できれば、同じ(または同様の)アーキテクチャで実行されているアプリケーションのどのインスタンスでも同じになるためです。
コンパイラには バッファオーバーフロー保護 があり、オペレーティングシステムでは アドレスレイアウトのランダム化 のような手法を使用しているため、この種の欠陥を悪用することが難しくなっています。