スタックバッファオーバーフロー(弊研究室の某課題について考える7日目)

はじめに

この記事は弊研究室の某課題について考えるの7日目の記事です。この記事は事前に1日目の記事2日目の記事を読んでおくことをお勧めします

お品書き

Stack Buffer Overflow

さて2日目の記事でスタックフレームの説明をしました。スタックフレームの図を見るとこのようなことが考えられます。

f:id:kataware8136:20171208140558p:plain

左の図では局所変数のところに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");
}

この時のスタックはこんな感じ

f:id:kataware8136:20171208201609p:plain

という感じで20文字書き込んでauth_flag上書きしてしまえばmainのif(authcheck(str) != 0)を通過するわけですね。おしまい

おわりに

シェルコードを使ったシェル奪取もやろうかなと思ったんですが、この記事だけで結構お腹いっぱいになりそうなのでどっかで番外編としてあげようかなと思います。(3日目と6日目もがんばります)