バッファオーバーフロー
競技やったりセキュリティ勉強したりといろいろな分野に手を出しすぎている感は否めませんが、とりあえず今回はセキュリティのことについて書きます。
脆弱性としてもっとも基本的なものとして「バッファオーバーフロー」が挙げられます。自分の書いたプログラムでちょっと実験してみます
#include<stdio.h> #include<stdlib.h> #include<string.h> int check(char *pass){ int accept=0; char pass_buffer[16]; strcpy(pass_buffer,pass); if(strcmp(pass_buffer,"RKX1209")==0) accept=1; return accept; } int main(int argc,char *argv[]){ if(argc<2){ printf("使用方法:%s <パスワード>\n",argv[0]); exit(0); } if(check(argv[1])){ printf("\n=-=-=-=-=-=-=-=-=-=-=\n"); printf("アクセスを許可します\n"); printf("\n=-=-=-=-=-=-=-=-=-=-=\n"); }else{ printf("アクセスを拒否しました\n"); } }
これは正しいパスワードを入力すると「アクセスを許可」と表示し、違うなら「アクセスを拒否」と出力するだけの極単純な物です。 実行結果は以下のとおりです。
$./a.out hoge アクセスを拒否しました $./a.out aaa アクセスを拒否しました $./a.out RKX1209 =-=-=-=-=-=-=-=-=-=-= アクセスを許可します =-=-=-=-=-=-=-=-=-=-=
しかしこのプログラム、一見普通そうですが結構まずい脆弱性を含んでいたりします。
とりあえずgdbを使って解析してみましょう
(gdb) list 3 #include<string.h> 4 int check(char *pass){ 5 int accept=0; 6 char pass_buffer[16]; 7 strcpy(pass_buffer,pass); 8 if(strcmp(pass_buffer,"RKX1209")==0) accept=1; 9 return accept; 10 } 11 int main(int argc,char *argv[]){ 12 if(argc<2){ (gdb) break 9 Breakpoint 1 at 0x80484c1: file paswd.c, line 9. (gdb) run hoge Starting program: /home/rkx/Programing/C/a.out hoge Breakpoint 1, check (pass=0xbffff5af "hoge") at paswd.c:9 9 return accept; (gdb) x/s pass_buffer 0xbffff32c: "hoge" (gdb) x/x &accept 0xbffff33c: 0x00 (gdb) x/16xw pass_buffer 0xbffff32c: 0x65676f68 0x0029df00 0x08049ff4 0xbffff368 0xbffff33c: 0x00000000 0x00170cbd 0x0029e324 0xbffff368 0xbffff34c: 0x08048507 0xbffff5af 0x0011ea50 0x0804854b 0xbffff35c: 0x0029dff4 0x08048540 0x00000000 0xbffff3e8
pass_bufferがacceptよりも低位アドレスにあります。
通常、関数などの局所変数はスタックセグメントに高位アドレスから低位アドレスにむけて積まれていくので、当然といえば当然の結果なのですが...
ここまでわかれば後はこちらの物です。
(gdb) break 9 Breakpoint 1 at 0x80484c1: file paswd.c, line 9. (gdb) run AAAAAAAAAAAAAAAAAAAAA Starting program: /home/rkx/Programing/C/a.out AAAAAAAAAAAAAAAAAAAAA Breakpoint 1, check (pass=0xbffff59e 'A' <repeats 21 times>) at paswd.c:9 9 return accept; (gdb) x/16xw pass_buffer 0xbffff31c: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff32c: 0x41414141 0x00170041 0x0029e324 0xbffff358 0xbffff33c: 0x08048507 0xbffff59e 0x0011ea50 0x0804854b 0xbffff34c: 0x0029dff4 0x08048540 0x00000000 0xbffff3d8
ここからも分かるようにこれで通ってしまいます。
実際に実行してみても
&./a.out AAAAAAAAAAAAAAAAAAAAA =-=-=-=-=-=-=-=-=-=-= アクセスを許可します =-=-=-=-=-=-=-=-=-=-=
となり、本来の正しいパスワードを入力しなくてもアクセスが許可されてしまいました。
「余談」
このバッファオーバーフローはよく使用される脆弱性です。そのためOSごとにこの脆弱性をカバーするための対策が講じられていることが結構多いです。(ちなみに自分もこの対策に振り回されましたw)
自分が今使用しているOSは
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=11.04
DISTRIB_CODENAME=natty
DISTRIB_DESCRIPTION="Ubuntu 11.04"
なのですが、こいつはちゃんとバッファオーバーフロー対策をしていて例えば先ほどのプログラムを解析すると、
(gdb) run AAAAAAAAAAAA Starting program: /home/rkx/Programing/C/a.out AAAAAAAAAAAA Breakpoint 2, check (pass=0xbffff5a7 'A' <repeats 12 times>) at paswd.c:9 9 return accept; (gdb) x/s pass_buffer 0xbffff31c: 'A' <repeats 12 times> (gdb) x/x &accept 0xbffff318: 0x00 (gdb) x/16xw pass_buffer 0xbffff31c: 0x41414141 0x41414141 0x41414141 0xbffff300 0xbffff32c: 0x8d5d0100 0x0015ecbd 0x0028c324 0xbffff358 0xbffff33c: 0x08048579 0xbffff5a7 0x0011ea50 0x080485cb 0xbffff34c: 0x0028bff4 0x080485c0 0x00000000 0xbffff3d8 (gdb) quit
ソースコードを変えても必ずpass_bufferがacceptよりも高位アドレスになるようになっています。 おそらくこれはバッファオーバーフロー対策なんだろうということで、ちょっとググってみると...
「最新のUbuntuなどではバッファオーバーフローアタックを防御するための機構がある]ということが判明。
やっぱり..orz ということでさらにググっていると..
$ sudo sysctl -w kernel.randomize_va_space=0 $ gcc -g -fno-stack-protector -z execstack test.c
でこの対策を解除できるらしい ということで解除してやっと成功しました
結論: 基本的なセキュリティ対策って案外見えないところでされてるんですね