スタックバッファオーバーフロー(弊研究室の某課題について考える7日目)
はじめに
この記事は弊研究室の某課題について考えるの7日目の記事です。この記事は事前に1日目の記事と2日目の記事を読んでおくことをお勧めします
お品書き
- スタックバッファオーバーフローについて
- シェルコード
- スタックバッファオーバーフローを守る技術
- 実際の攻撃を見る
Stack Buffer Overflow
さて2日目の記事でスタックフレームの説明をしました。スタックフレームの図を見るとこのようなことが考えられます。
左の図では局所変数のところにchar型の4文字分確保されていますが、そこに20文字来たらどうなるかというのが右の図です。
4文字分のチェックを行わないとmain関数のebpやリターンアドレスが上書きされてしまい、main関数に戻ることができなくなります。
これがスタックバッファオーバーフローという脆弱性になります。この脆弱性を用いてシェルの奪取等ができるようになります。
たとえばbufにコードを埋め込んでおいてリターンアドレスをそのbufに設定することで/bin/shを実行するという流れを作ることができます。このbufに埋め込む/bin/sh
を実行するコードをシェルコードと言います
シェルコード
シェルコードとは、コンピュータセキュリティにおいて、ソフトウェアのセキュリティホールを利用するペイロードとして使われるコード断片である。 侵入したマシンを攻撃者が制御できるようにするため、シェルを起動することが多いことから「シェルコード」と呼ぶ。(Wikipediaより)
要はこのシェルコードを実行すると/bin/sh
等のコマンドが実行できるわけです。
シェルコードの作り方等はももいろテクノロジー様の記事を参考にしてください。
我々は使えればいいのでここからもらってきましょう。
試しに実行してみましょう。
/* shell.c */ #include <stdio.h> char shellcode[] = "\xeb\x0b\x5f\x48\x31\xd2\x52\x5e\x6a\x3b\x58\x0f\x05\xe8\xf0\xff\xff\xff\x2f\x2f\x2f\x2f\x62\x69\x6e\x2f\x2f\x2f\x2f\x62\x61\x73\x68"; int main() { (*(void (*)())shellcode)(); }
これでコンパイルして実行すれば/bin/sh
が起動します。試しにls
コマンドでもやってみればわかります。
コンパイル時にはオプションで-z execstack
をつける必要があります。
このシェルコードをスタックに積み、リターンアドレスをシェルコードの先頭に指定することで脆弱性がある場合は実行できるようになります。
スタックバッファオーバーフローから守る技術
ここ思い出しながら書いてるので忘れてたら突っ込みいれて
DEP
DEP(Data Execution Prevention)というのはスタックでの実行を不可にします。前節で-z execstack
というオプションはこのDEPを無効化するオプションです。
ASLR
ASLR (Address Space Layout Randomization)とは重要なデータ領域 の位置(通常、プロセスのアドレス空間における実行ファイルの基底とライブラリ、ヒープ、およびスタックの位置が含まれる)を無作為に配置するコンピュータセキュリティの技術です。スタックバッファオーバーフローだけ守る技術ではないですがこれは守る技術の一つです。
プログラムを実行するアドレス空間が毎回同じであるとシェルコードを配置したアドレスが固定となるのでリターンアドレスの書き換えが簡単です。ですが毎回ランダムであるとスタックの位置を調べる必要があります。
SSP
SSP(Stack Smash Protecting)とはスタックのオーバーフローを検知します。canary(カナリア)という値をスタックフレームのebp
と局所変数の間に置き、関数の最後にcanaryのチェックをすることによってスタックバッファオーバーフローを検知します。
実際のスタックバッファオーバーフロー
さてサンプルプログラムです。脆弱性があるのはauthcheckです。このプログラムは簡単です
#include <stdio.h> #include <stdlib.h> #include <string.h> int authcheck(char *passwd ){ int auth_flag = 0; char pass_buf [16]; strcpy(pass_buf ,passwd ); if(strcmp(pass_buf , "44GC44GBXu+9nuW/g+OBjOOBtOOCh+" "OCk+OBtOOCh+OCk+OBmeOCi+OCk+OBmOOCg+OBgV7vvZ4g ") == 0){ auth_flag = 1; } return auth_flag; } int main(int argc , char *argv []){ setbuf(stdin ,NULL); setbuf(stdout ,NULL); FILE *fp; char str [1000]; char readline [256] = {'\0'}; char no[] = "no"; fp = fopen ("flag.txt","r"); while (1){ printf ("Hi. Do you want to flag?\ nenter pass: "); if(fgets(str ,1000 , stdin) != NULL){ if(strstr(str ,no) != NULL){ break ;} if(authcheck(str) != 0){ while(fgets(readline ,256,fp) != NULL ) { puts(readline ); } break; } printf ("Oops. Password is wrong.\n"); }else{ printf (" Please input in 1000 character\n"); } } printf ("bye"); }
この時のスタックはこんな感じ
という感じで20文字書き込んでauth_flag
上書きしてしまえばmainのif(authcheck(str) != 0)
を通過するわけですね。おしまい
おわりに
シェルコードを使ったシェル奪取もやろうかなと思ったんですが、この記事だけで結構お腹いっぱいになりそうなのでどっかで番外編としてあげようかなと思います。(3日目と6日目もがんばります)