バッファオーバーフローのしくみを理解していますが、オーバーフローの方向を理解するのに問題があります。したがって、スタックがdownwardsに増加した場合、戻りアドレスはabove変数の予約スペースであることを意味します。その変数がオーバーフローしたとき、それが上記の代わりにメモリ以下を上書きするのではないですか?
tl; dr:スタックが下向きに大きくなると、バッファオーバーフローによって変数の上のコンテンツがどのように上書きされますか?
スタックが下方向に大きくなると、後で呼び出される関数は、より低いメモリアドレスでスタックフレームを取得します。また、ローカル変数のスペースが予約される前に戻りアドレスがスタックにプッシュされるため、戻りアドレスはローカル変数よりも高アドレスを取得します。しかし、配列とバッファはメモリ内で上方向にインデックスが付けられているため、配列の終わりを超えて書き込むと、スタックの次の戻りアドレスにうまくヒットします。
例、必須ASCII art:
信頼できないソースから入力を受け取り、それをローカルバッファーにコピーする簡単な関数を考えます。
_void foo(char *s)
{
char buf[8];
strcpy(buf, s);
return;
}
_
スタックは次のようになります。
_ <---- stack grows to the left
memory addresses increase to the right -->
0x8000 0x8010
+--------+----------+---------++------------
+ buf[8] | ret addr | char *s || .......
+--------+----------+---------++--------------
<--------- foo() -----------> <---- caller --
_
スタックは、関数の引数、戻りアドレス、関数のローカルの順で、右から左へと埋められます。 buf
から増加するアドレスへの単純なオーバーフローが戻りアドレスにうまくヒットすることは簡単にわかります。
それでは、スタックが反転し、上向きに成長するとどうなるでしょうか。次に、バッファをオーバーフローさせると、スタックが成長するのと同じように、スタックの空の部分に向かって実行されます。
いい感じですが、foo()
がコピーを実行するために別の関数を呼び出しても役に立ちません。これは珍しいことではありません。私はstrcpy
でそれを実行しました。スタックは次のようになります。
_ stack grows to the right -->
memory addresses increase to the right -->
0x8000 0x8010
------------++---------+----------+---------++-----------+-------------+
.... || char *s | ret addr | buf[8] || ret addr | locals ... |
------------++---------+----------+---------++-----------+-------------+
caller ---> <-------- foo() -------------> <---- strcpy() ---------->
_
これで、foo()
のスタックフレームのバッファを(右側に)オーバーランすると、strcpy()
ではなくfoo()
の戻りアドレスが適切に上書きされます。ただし、問題はありませんが、攻撃者が制御するオーバーフローデータによって設定された場所にジャンプします。
スタックは、何かが割り当てられている場合にのみ下向きに成長します。一方、配列の最後を読み取るとは、メモリ内で上方向に読み取ることを意味します。
スタックポインターが0x1002を示しているとしましょう。このアドレスには、気になる戻りポインタがあります。たとえば、2バイトの配列を割り当てると、スタックは下方向に大きくなります。スタックポインターが0x1000に変更され、配列のアドレスがその新しい値に設定されます。最初のバイトを配列に書き込むにはアドレス0x1000を使用し、2番目のバイトを書き込むには0x1001を使用します。終わりを書くことは0x1002を使用し、重要なことを上書きします。
これは、バッファBを表す灰色のボックスが境界を超えて書き込まれるにつれて上向きになるため、投稿した画像で確認できます。
バッファオーバーフローとスタックオーバーフローには違いがあります。割り当てられたバッファはスタックではなくヒープを使用する場合があります。これは、それらがどのように割り当てられているか、コンパイラがより良い/より速い/などに依存しています。
スタックオーバーフローは実際に以下のメモリをオーバーライドします。これは、別の(前の)呼び出しまたは最終的にヒープに割り当てられた可能性があります。ヒープは上向きに成長し、ある時点で衝突する可能性があります。これは、オペレーティングシステムがセグメンテーション違反をスローしたり、プログラムを停止したり、シグナルを送信したりするのに最適な時間です。スタックオーバーフローの問題は、同じプロセスの他のスレッドと接触する可能性があることです。ネットワーク送信/受信用の大きなバッファーを持つスレッドをイメージ化すると、別のスレッドスタックの内容がこのメモリ領域を上書きして、ネットワーク上でメモリリークを引き起こす可能性があります。
バッファオーバーフローはより一般的で、独自のメモリの境界を超えます。アクセスされるメモリがヒープ領域内にある限り、問題はありません(オペレーティングシステムの観点から)。ヒープが小さすぎて追加のデータを割り当てる必要がある場合は、さらに危険になります。この時点では、境界に達したときに何が起こるかは明らかではありません。ヒープ境界を越えて1バイトにアクセスするためにタイするとき、オペレーティングシステムすべき SIGSEGVでプロセスを強制終了します。
スタックはPush命令によって下向きに成長しますが、上向きに書き込みと読み取りを行います。たとえば、スタックが10〜5のアドレスを使用している場合、使用可能なアドレスの長さは6です。Push命令があると、スタックがダウンしてメモリが追加され、スタックがアドレス10〜4を使用します。 7つの使用可能なアドレス。しかし、スタックに書き込んでいて、命令ポインターが4にある場合、4から10に書き込み、アドレス10を渡すと、バッファーオーバーフローが発生します。