私は 論文 を読んでいて、このコードに情報漏えいの脆弱性があることを確認しました。それは次のコードが攻撃者にメモリレイアウト情報を漏らすと言っていました
誰かが私にこれがどのように情報を漏らすか説明してくれませんか?
struct userInfo{
char username[16];
void* (*printName)(char*);
} user;
...
user.printName = publicFunction.
...
n = attacker_controllable_value; //20
memcpy(buf, user.username, n); //get function ptr
SendToServer(buf);
私はmemcpy
が例外を与えることがわかりますが、なぜそれが攻撃者(またはそれが返すもの)にメモリアドレスを返す必要があるのですか?
前もって感謝します
buf
のサイズがnによって制御されるか16より大きいと仮定すると、攻撃者はnを任意の数にして、それを使用して任意の量のメモリを読み取ることができます。 memcpy
およびCは、一般に例外をスローしたり、これが発生したりするのを防ぎません。何らかの種類のページ保護に違反したり、無効なアドレスにヒットしたりしない限り、memcpyは要求されたメモリ量をコピーするまで楽しそうに続けます。
私はuser
とこの脆弱なコードのブロックがどこかの関数にあると想定しています。これはおそらくそれがスタック上にあることを意味します。すべてのローカル関数変数、戻りアドレス、およびその他の情報はスタックに含まれています。以下の図は、Intel Assemblyを使用するシステムの構造を示しています(ほとんどのプラットフォームで使用されており、コンピューターで使用されていると思います)。
Nを十分大きくしてmemcpyをスタックフレーム内で前進させると、このメソッドを使用して戻りアドレスを取得できます。 user
は、この図の「ローカルに宣言された変数」というセクションにあります。 EBPは4バイトの値であるため、それを超えて読み取り、memcpyで次の4バイトをコピーすると、戻りアドレスがコピーされます。
上記はプログラムが実行されているアーキテクチャに依存することに注意してください。このペーパーはiOSに関するものであり、ARMについては何も知らないため、この情報の詳細は多少不正確な場合があります。
良い答えはすでにsashaによって与えられています ですが、これを別の角度から見たいと思います。具体的には、memcpyが実際に実行するものdoes(実行されるコードに関して)。
この素朴な実装でのマイナーなバグの可能性を考慮して、C89/C99/POSIX関数の署名とコントラクトを満たすmemcpy()
の簡単な実装は、完全に異なるものではないかもしれません。
/* copy n bytes starting at source+0, to target+0 through target+(n-1), all inclusive */
void memcpy (void* target, void* source, size_t n)
{
for (size_t i = 0; i < n; i++)
{
*target++ = *source++;
/* or possibly the here equivalent: target[i] = source[i]; */
}
}
現在realの実装では、今日のワイドメモリ(RAM)相互接続バスを利用するために、一度に1バイトよりも大きなチャンクでコピーを実行しますが、原理はまったく同じです。
あなたの質問の目的のために注意すべき重要な部分は、境界チェックがないことです。 これは設計によるものです]これがそうである理由には、3つの重要な理由があります。
(1)から、必要なメモリをどこからでもどこへでもコピーできるはずです。メモリ保護は他の誰かの問題です。具体的には、最近ではOSと [〜#〜] mmu [〜#〜] の責任です(これらの日は一般にCPUの一部です)。 OS自体の関連部分はおそらくCで書かれています...
(2)からわかるように、memcpy()とそのフレンドは、コピーするデータの量を正確に通知する必要があり、ターゲット(またはターゲットポインターが指すアドレスにある他のバッファー)のバッファーが信頼できるものである必要があります。そのデータを保持するのに十分な大きさ。メモリ割り当てはプログラマの問題です。
(3)から、どれだけの量のデータをコピーしても安全かはわかりません。メモリ割り当て(ソースと宛先の両方)が十分であることを確認することはプログラマの問題です。
攻撃者がmemcpy()を使用してコピーするバイト数を制御できる場合、(2)と(3)が分類されます。ターゲットバッファが小さすぎる場合、後続のバッファは上書きされます。運がよければ、メモリアクセス違反が発生しますが、C言語またはその標準ライブラリは、その発生を保証しません。 (メモリの内容をコピーするように要求しましたが、それを実行するか、試行して終了しますが、何がコピーされるかintendedがわかりません。)より小さいソース配列を渡す場合memcpy()にコピーするように要求するバイト数よりも、memcpy()がそうであることを検出する確実な方法はありません。ソース配列の末尾を超えて、ソースの場所とターゲットの場所への書き込みは機能します。
攻撃者がサンプルコードでn
を制御できるようにすることで、n
がコピーのソース側の配列の最大サイズよりも大きくなるため、上記の点が原因でmemcpy()は長さを超えて喜んでコピーを続けます目的のソース配列の。 これは基本的に ハートブリード 一言で言えば攻撃です。
これが、コードがデータをリークする理由です。正確にどのデータがリークされるかは、n
の値とコンパイラーの配置方法の両方に依存します機械語コードとメモリ内のデータを出力します。 sashaの回答の図は概要を示しており、すべてのアーキテクチャは似ていますが異なります。
変数buf
がどのように宣言され、割り当てられ、メモリに配置されるかに応じて、またstack smashing attack プログラムの適切な動作に必要なデータが上書きされ、そこにあるものを上書きしたデータが後で参照されます。ありふれたケースでは、これはクラッシュまたはデバッグ不可能のバグにつながります。深刻な標的型のケースでは、完全に攻撃者の制御下で任意のコードが実行される可能性があります。
私は別の答えを投稿しています。なぜなら、ここでの2つの答えはどちらも正しいですが、私の意見の質問の重要なポイントを逃しているためです。問題は、メモリレイアウトに関する情報漏えいについてです。
提示されたmemcpyは常に正しいサイズの出力バッファーを持っている可能性があるため、攻撃者がサイズを制御しても、この時点でスタックが破壊されるリスクはない可能性があります。情報漏えい(Linuxiosで既に言及されているように、ハートブリードのように)は、どの情報が漏えいするかに応じて、潜在的な問題です。この例では、publicFunction
のアドレスをリークしています。これはアドレス空間レイアウトのランダム化を無効にするため、これは実際の問題です。 ASLRは、たとえば ASLRとDEPはどのように機能しますか? のトピックです。 publicFunction
のアドレスを公開するとすぐに、同じモジュール(DLLまたはEXEファイル)内の他のすべての関数のアドレスが公開され、return-to-libcまたはreturn-oriented-programmingで使用できます攻撃。ただし、これらの攻撃には、ここに示したものとは別の穴が必要です。