株式会社Ninjastars 技術系ブログ

「本質的安全を提供し、デジタル社会を進化させる!!!」

ソフトウェアバグ -Segmentation Fault-

株式会社Ninjastars取締役の齊藤です。

皆様もC言語C++で開発しているときなどに一度はこの実行時エラーを見たことがあるのではないでしょうか?
今回はSegmentation Fault(以下セグフォ)がどのようにして発生するのかを詳細に書いていこうと思います。

1.セグフォとは
割り当てられた仮想メモリ領域(セグメント)に読み込み(r)または書き込み(w)、実行の権限(x)がない状態でアクセスしたときに起こるエラーのことを指します。

では、なぜセグフォが必要なのでしょうか?
それはメモリに展開されているプロセスを保護し、OS全体に影響を及ぼさないためです。

2.セグフォの原因を探す手順
プログラムを用意したのでそれを例に原因を探していきましょう。

まず今回のプログラムは標準入力で10byteのbufferに文字データを格納するものです。
標準入力する際、gets関数を使っているため、改行文字が現れるまでbufferサイズより大きい入力が可能です。(これが原因でスタックベースのバッファオーバーフロー脆弱性があります。)

今回の動作環境はUbuntu18.04を想定しております。

bof.c

#include<stdio.h>
int main(void)
{
 char buffur[10];
 gets(buffur);
 return 0;
}

Segmentation Fault - Google ドライブ



実際にこのプログラムを実行してセグフォを起こしてみましょう。
bufferが10byteしかないのでそれ以上の文字を入力することでオーバーフローが発生します。
(文字列の終端にはNULL文字=0x00が入ります)
とりあえず多めに文字「A」20文字ではじめてみます。

1で述べたようにセグフォはr,w,xの権限がないことで発生します。
そのため、どの権限がないのかを調べる必要があります。
そこでその手がかりとなるのがセグフォが発生するまでに出された実行時のログです。
それを調べるためにLinuxコマンドのstraceを使います。straceはシステムコールをトレースしその引数等を表示してくれます。
コマンド「strace -i ./bof」で標準入力の待機までプログラムを実行してくれます。


f:id:Ninjastars:20181224162438p:plain
strace

上の画像でsi_code=SEGV_MAPERRが今回のセグフォの種類を表しています。
今回はアプリケーションのアドレス空間にマップされていないページにアクセスされたことを表すSEGV_MAPERRが表示されています。
つまり、標準入力した際CPUの命令ポインタ(eip)が実行権限のない領域に飛んでしまったということです。
ではなぜ標準入力しただけでeipが"おかしな"場所へと飛ばされてしまったのでしょうか?
これはx86アーキテクチャのスタック構造に起因します。
以下にこのプログラムのmain関数のスタック構造の模式図を用意しました。

f:id:Ninjastars:20181224162637p:plain
スタック構造の模式図

標準入力する際10文字以上入力してしまうと高位のメモリアドレスの領域まで書き込まれてしまいます。
main_return_addressの値が「bof」がmain関数終了時に戻るべきメモリアドレスです。
ここではこの値が書き換わってしまい、eipが実行権限のない領域(0x41414141)へと飛んでしまいました。

実際にLinuxコマンド「pmap bofのプロセスID」で「bof」の仮想メモリマップを調べると、以下のように0x41414141に実行権限がついていないことが確認できます。


f:id:Ninjastars:20181224162738p:plain
仮想メモリマップ

まとめ
この記事では実行権限のない場合のセグフォの原因を探求しました。セグフォが発生した際の参考にしていただけたら幸いです。

コンパイル時のgccコマンド
gcc -m32 -fno-stack-protector -no-pie -o bof bof.c」
bof自己責任にてコンパイルしてください。

こちらのコンパイルオプションは前回のこちらの記事をご覧ください。
ninjastars.hatenablog.com

ここでは前回の記事で紹介していない「fno-stack-protector」のみご説明致します。
このオプションはgccSSPというスタックベースのバッファオーバーフローを保護する機能をオフにするものです。
カナリアの値は、スタック構造の図の中のbufferと「saved_ebp」の間にあるNULL(0x00)と3byteのランダム値の計4byteの値です。この値が書き換わっているかを判定することでバッファオーバーフローを検知します。
今回は意図的にバッファオーバーフローを発生させる必要があったため、この保護機能をオフにしました。


注意事項
本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。


本レポートについて

お問い合せ
E-mail:saito@ninjastars-net.com

株式会社Ninjastars取締役
齊藤