MasaのITC Life

夢は起業家!全てにおいて凡人だけど頑張ることだけはいっちょ前!

プログラミング

【C言語】スタックオーバーフローを体験してみる。その対策法も!?

投稿日:2020年1月26日 更新日:



「スタックオーバフローって

良く聞くけどあまり実感がない!」


という方の向けに、、、

その危険性をあえて実感してみましょう。




スタックオーバーフローですが、

よく似た言葉に、

バッファオーバーフロー

というのも存在します。




スタックオーバーフローは、

バッファオーバーフローの一種で、

その他にヒープオーバーフロー

といったのも存在します。




どのメモリ領域で、

オーバーフローが起こるか

というのが区別の仕方です。




スタックオーバーフローなので、

スタック領域をゴチャゴチャやって、

プログラムの抜け目を作ります。




「ゴチャゴチャ」とは、

期待してない操作によって、

メモリ(スタック)領域にデータを

格納しきれなくなって、

バグを引き起こすことを言います。






コードサンプルは以下になります。

※参考「Hacking:美しき策謀」



このソースは、

パスワード判定プログラムです。

実行時にパスワードを指定して、

パスワードが正しいかを出力します。




正しいパスワードは、

"iloveyou"です。




ここで最も恐ろしいこととして、

偽のパスワードが入力されたのに、

それを正しいと判定して、

処理を進めてしまうことです。




どういうことか、やってみます。


"aaaaa"という

パスワードを入れてみます。

当たり前のように拒否されました。




次に正しいパスワードを入力します。

当たり前のように成功しました。




最後に"aを30個"入力してみます。

するとなんと、、、

「パスワードが正しい」と

判定されてしまいました。




もしオーバーフローの脆弱性を持った

コードがログイン画面に使わていたら、

恐ろしいことですよね!?




何が起こったのか、

デバッグしてみましょう。

緑枠がタイプする箇所です。




-gオプションを付与して

コンパイルします。

-gがないとデバッグが出来ません。




gdb(The GNU Debugger)を

-qオプションで起動します。

-qはなくてもOkです。




list 1をタイプすると、

ソースコードが出てきますので、

ブレイクポイントを指定します。




break 9break 13をタイプし、

9行目と13行目にブレイクします。

9行目:パスワードをバッファに格納

13行目:main関数にリターン




ブレイクポイントを指定したら、

run $(python -c "print 'a'*30")

実行させます。




$(python -c "print 'a'*30")は

aを30個タイプしたのと同じです。




aをひたすら30個入力すると、

数え間違え、時間がかかる

の理由からこのようにしてます。




9行目でブレイクした時点で

スタックはどのように

なっているのでしょうか。



※リトルエンディアンである点に注意


x/x bufで、bufの

アドレスとその中身を見ます。




この時点ではパスワードが、

まだ格納されていないので、

中身は空っぽのままです。




x/x &auth_flagで、

auth_flag変数の中身を見ます。




この時点ではまだ、

初期化された状態と

変わってないので0です。




x/32xw $rspで、

ここで肝心なスタックが

どのようになっているのか

実際に見ることが出来ます。




rspはスタックの先頭アドレスで、

そこから32ワード(4×8)を出力します。




スタック領域を見てみると、

bufやauth_flagにまだ0が

格納されていることが分かります。




ここで、cをタイプして

次のブレイクポイントに移行します。

cとは、continueの意味です。




確認ですがこのブレイクポイントは

check_passwd関数がauth_flagを

main関数にリターンする直前です。




そして先ほどと同様に、

この時点での変数の中身を

チェックしてみましょう。




パスワードが入ったbuf配列には、

0x6161616161・・・が

入っています。




この0x61ASCIIコードで

「a」を意味しています。

そう。このaは実行時に

入力したパスワードです。




入力時にaを30個入力したので、

bufの先頭から30バイト分の

メモリが0x61によって

占められています。


30個のa(0x61)は、

buf配列を飛び出して、

auth_flag変数にまで、

浸食しています。




それを証明するかのように、

x/x &auth_flagで

変数の中身を表示したときに、

0x00006161が出力されました。




なのでcheck_passwd関数の

戻り値はauth_flagを返すので、

0x00006161がmain関数に

リターンされてしまいます。




これは偽、つまり0

ではないので真として判定され、

if文が実行されてしまいます。




これがスタックオーバーフローです。

ではこれを回避するためには、

どの対策したらよいでしょうか。




今回の例だと、

buf配列がauth_flag変数に

スタック領域上で意図せず、

影響を与えてしまったことです。




解決策の一つに

static修飾子として、

buf配列を宣言する

という方法があります。




これはbuf配列を

スタック領域以外の

メモリ領域に置くことを

意味しています。




具体的にいうと、

static修飾をした変数は、

bssセグメント(領域)に

格納されることになります。

※未初期化の場合




やり方は以下になります。


ただ、buf配列の前に、

staticを付けるだけです。

それでは実際に上手く

動作するか確認してみましょう。




このように、aを30個入力しても、

スタックオーバーフローが起こらず、

正確な答えが返ってきました。




余談ですが、逆に

auth_flag変数を

スタック領域以外の場所の

確保したらどうなるのか。




答えはダメです。

確かにauth_flag変数は

buf配列の影響を

受けなくなります。




しかしスタック領域には、

main関数に戻るための

フレームポインタや

リターンアドレスなどの

他にも大事な情報が詰まっています。




auth_flagだけがbuf配列からの

影響を逃れたとしても、

buf配列が他の大事な情報に

危害を加える可能性があります。




従って、危険物をスタック領域から

違うメモリ領域に隔離してやること

これが最も手っ取り早いです。





最後まで読んでいただきありがとうございました。


-プログラミング

Copyright© MasaのITC Life , 2023 All Rights Reserved Powered by STINGER.