.

 

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
WebMoney ぷちカンパ

スタックとバッファーオーバーフローについて 

今回はスタックとバッファーオーバーフローについて解説します。

~ステップアップ~
TIFF画像バッファーフロー
⇒PSP TIFFバッファーフロー
~Advanced(発展)~
・環境
Intel x86 CPU
(システムのマシンコード,アドレス値,システムコール仕様などとは異なるところがあります。
このサンプルではC言語のソースコード中で宣言されたのと同じ順にローカル変数がスタック上に確保されているが,処理系によってはその順序が異なる場合もあります。
操作手順が違う場合でも、やる内容には変わりません。)


スタックバッファーオーバーフロー
スタックはプロセスのメモリ空間に配置された主要な作業用領域の一つである。
以下図1
b06_01_1.png


データがスタックに保存されるとき,その領域の大きさはメモリ空間の先頭側(メモリアドレスの小さい方)に向かって拡張され,拡張された部分にデータが配置される。
以下図2
b06_01_2.png

またスタック上のデータが不要になった際には常にスタックの先頭から順に取り除かれる。
(=メモリの開放。)
もし、開放されなかった場合、使用出来るメモリ空間が少なくなり、フリーズしてしまう。
古いパソコンでフリーズしやすいのは、他にもありますが、この原因が考えられる。
定期的に、メモリのクリーニングをすると良い。

スタックに積まれるのは次のようなデータやアドレスである。

* ローカル変数
* 関数呼び出し時の引数
* 関数呼び出し時のリターンアドレス
* ebp レジスタの保存値


スタックの積み方の簡単な一例

リスト1はファイル「file.txt」の内容を読み込みその内容を表示する簡単なサンプルプログラム。
このプログラムを利用して,スタックがどのように使われているか説明していきます。
(ソースコードは下に添付)

(a)main()関数に入った直後
図3a main()関数に入った直後
b06_01_3a.png
プログラムは12行目のmain( )関数からスタートする。
関数の先頭では,スタック上に配置されたローカル変数にアクセスする際の基準アドレス(ベースアドレス)を保持するebpレジスタに新しい値が設定されるのだが,その直前にそれまでのebpレジスタの値がスタックに待避される。そして,14~16行目で宣言されているローカル変数領域がこの順序でスタック上に確保される(注5)。スタックはアドレスが大きいほうから使用され,順次アドレスが小さい方へ拡張されるので,図3(a)のような配置となる。

(b)vuln()関数呼び出し直前
図3b vuln()関数呼び出し直前
b06_01_3b.png
19 ~ 21 行目でローカル変数バッファlinebuf にファイル「msg_file.txt」の内容を読み込んでいる。読み込んだ内容はvuln( )関数が表示するようになっており,23 行目でvuln( )関数を呼び出し,linebuf を引数として渡している。関数呼び出しの際に,まず引数がスタックに積まれ,次にリターンアドレスが積まれる。リターンアドレスとは,呼び出した関数(この場合はvuln( ))から戻って呼び出し側の処理を再開する地点を示すアドレスのことである。大まかに言うと,リスト1の25行目の先頭あたりを指している。このときスタックは図3(b)のような配置となる。

(c)vuln()関数に入った直後
図3c vuln()関数に入った直後
b06_01_3c.png
23 行目のvuln( )関数呼び出しにより,実行が28 行目に移る。関数の先頭でebp レジスタに新しい値が設定されるのに先立ちそれまでのebp レジスタの値がスタックに待避される。30 ~ 31 行目でローカル変数msg とmark2 がスタックに積まれる。
このときのスタックは図3(c)のような配置となる。

34行目では,引数で受け取った文字列をローカル変数バッファmsg にコピーする。36行目でstack_dump( )関数を呼び出して,スタックに積まれている実際のデータを表示させる。そして38 行目のprintf( )関数によりローカル変数バッファmsgの内容を表示する。その後39行目でこの関数からリターンし,25行目へ処理が戻る。25 行目のprintf( )関数により次の1 行が表示されプログラムは終了する。

41 ~ 63 行目のstack_dump( )関数は与えられたアドレスからcounts × 4 バイト分のデータを見やすいダンプ形式で表示する

bof_test.c


1 #include
2 #include
3 #include
4
5 #define FILEPATH "file.txt"
6
7 int main();
8 void vuln(const char* line);
9 void stack_dump(void* ptr, int counts);
10 void hello();
11
12 int main()
13 {
14 char linebuf[1024];
15 FILE *fp;
16 long mark1 = 0x11111111;
17 memset(linebuf, 0, sizeof(linebuf));
18
19 fp = fopen(FILEPATH, "r");
20 fgets(linebuf, sizeof(linebuf)-1, fp);
21 fclose(fp);
22
23 vuln(linebuf);
24
25 printf("------------- end of main() -------------\n");
26 }
27
28 void vuln(const char* line)
29 {
30 char msg[20];
31 long mark2 = 0x22222222;
32 memset(msg, 0, sizeof(msg));
33
34 strcpy(msg, line);
35
36 stack_dump(&mark2, 13);
37
38 printf("INPUT[%s]\n", msg);
39 }
40
41 void stack_dump(void* ptr, int counts)
42 {
43 int i;
44 unsigned long *ulong_ptr = (unsigned long *)ptr;
45 unsigned char uchar_buf[4];
46
47 printf("-----------------------------------------\n");
48 printf(" address | long var | +0 +1 +2 +3 | 0123\n");
49 printf("-----------------------------------------\n");
50 for(i=0; i 51 printf(" %08x| %08x", &ulong_ptr[i], ulong_ptr[i]);
52 memcpy(uchar_buf, &ulong_ptr[i], sizeof(uchar_buf));
53 printf(" | %02x %02x %02x %02x",
54 uchar_buf[0], uchar_buf[1], uchar_buf[2], uchar_buf[3]);
55 if(uchar_buf[0]<32 || uchar_buf[0]>126) uchar_buf[0] = '.';
56 if(uchar_buf[1]<32 || uchar_buf[1]>126) uchar_buf[1] = '.';
57 if(uchar_buf[2]<32 || uchar_buf[2]>126) uchar_buf[2] = '.';
58 if(uchar_buf[3]<32 || uchar_buf[3]>126) uchar_buf[3] = '.';
59 printf(" | %c%c%c%c\n",
60 uchar_buf[0], uchar_buf[1], uchar_buf[2], uchar_buf[3]);
61 }
62 printf("-----------------------------------------\n");
63 }
64
65 void hello()
66 {
67 printf("+----------+\n");
68 printf("| HELLO! |\n");
69 printf("+----------+\n");
70 exit(0);
71 }



file.txtの内容
address
00000000 41 42 43 44 45 46 ABCEDF


この入力を処理した結果が実行例1


1 $ ./bof_test
2 -----------------------------------------
3 address | long var | +0 +1 +2 +3 | 0123
4 -----------------------------------------
5 bffff72c| 22222222 | 22 22 22 22 | """" ← mark2
6 bffff730| 44434241 | 41 42 43 44 | ABCD ← msg[0-3]
7 bffff734| 00004645 | 45 46 00 00 | EF.. ← msg[4-7]
8 bffff738| 00000000 | 00 00 00 00 | .... ← msg[8-11]
9 bffff73c| 00000000 | 00 00 00 00 | .... ← msg[12-15]
10 bffff740| 00000000 | 00 00 00 00 | .... ← msg[16-19]
11 bffff744| bffffb58 | 58 fb ff bf | X... ← ebp の保存値
12 bffff748| 080485d9 | d9 85 04 08 | .... ← リターンアドレス
13 bffff74c| bffff758 | 58 f7 ff bf | X... ← vuln()への第1 引数(linebuf)
14 bffff750| 11111111 | 11 11 11 11 | .... ← mark1
15 bffff754| 08049a58 | 58 9a 04 08 | X... ← fp
16 bffff758| 44434241 | 41 42 43 44 | ABCD ← linebuf[0-3]
17 bffff75c| 00004645 | 45 46 00 00 | EF.. ← linebuf[4-7]
18 -----------------------------------------
19 INPUT[ABCDEF]
20 ------------- end of main() -------------
21 $




2~18行目はvuln( ) 関数から呼び出されたstack_dump( )関数による表示で,これらは実際にスタックに積まれたデータである。目印に用意したリスト1の16行目のmark1変数の値(0x11111111)と31 行目のmark2 変数の値(0x22222222)がそれぞれ実行例1の14 行目と5 行目で見つかるので,これを手がかりに内容を解読する。この時点のスタックは図3(c)のように積まれているはずなので,ここに表示されているデータは実行例1の右側に「← mark2」のように追記した項目のものであると分かる。

12行目のリターンアドレスに注目していただきたい。long var列に080485d9と記されているが,これはvuln( )関数から呼び出し元へ戻るときの戻り先アドレスが0x080485d9 だということである。
 
このアドレスが何に該当するのか確かめるために,リスト1のサンプルプログラムbof_testを逆アセンブルしてマシンコードの並びを表示させたリスト2と照合してみる。リスト2の15行目がまさにこのアドレスの地点だ。当然ながら,この場所はvuln( )関数呼び出し(リスト2,14 行目)の直後にあたる。


サンプルプログラムbof_test の逆アセンブル


1 $ objdump -d bof_test
2
3 bof_test: file format elf32-i386
4
5       ~~~~ 省略 以下は抜粋 ~~~~
6
7 08048560
:
8 8048560: 55 push %ebp
9 8048561: 89 e5 mov %esp,%ebp
10 8048563: 81 ec 08 04 00 00 sub $0x408,%esp
11       ~~~~~~~ 省略 ~~~~~~~
12 80485cd: 8d 85 00 fc ff ff lea 0xfffffc00(%ebp),%eax
13 80485d3: 50 push %eax
14 80485d4: e8 13 00 00 00 call 80485ec vuln()関数呼び出し
15 80485d9: 83 c4 04 add $0x4,%esp ← リターンアドレス
16 80485dc: 68 40 88 04 08 push $0x8048840
17 80485e1: e8 4e fe ff ff call 8048434 <_init+0x80>
18 80485e6: 83 c4 04 add $0x4,%esp
19 80485e9: c9 leave
20 80485ea: c3 ret
21 80485eb: 90 nop
22
23 080485ec :
24 80485ec: 55 push %ebp
25 80485ed: 89 e5 mov %esp,%ebp
26 80485ef: 83 ec 18 sub $0x18,%esp
27 80485f2: c7 45 e8 22 22 22 22 movl $0x22222222,0xffffffe8(%ebp)
28 80485f9: 6a 14 push $0x14
29
30       ~~~~~~~ 省略 ~~~~~~~
31
32 0804863c :
33 804863c: 55 push %ebp
34 804863d: 89 e5 mov %esp,%ebp
36 804863f: 83 ec 0c sub $0xc,%esp
37 8048642: 8b 45 08 mov 0x8(%ebp),%eax
38 8048645: 89 45 f8 mov %eax,0xfffffff8(%ebp)
39
40       ~~~~~~~ 省略 ~~~~~~~
41
42 08048774 :
43 8048774: 55 push %ebp
44 8048775: 89 e5 mov %esp,%ebp
45 8048777: 68 1a 89 04 08 push $0x804891a
46 804877c: e8 b3 fc ff ff call 8048434 <_init+0x80>
47
48       ~~~~~~~ 省略 ~~~~~~~



リスト1のmain( )関数のローカルバッファ変数linebufは1024バイトの領域を確保しているので,最大1023バイトのmsg_file.txtファイルの内容を保持することができる。しかしvuln( )関数のローカルバッファ変数msgは20 バイトしか領域を確保していない。実行例1の6 ~ 10 行目にmsg の20 バイトの領域が表示されている。
 
もしバッファmsg に20 バイトより大きいデータを書き込もうとしてしまうと,リスト1,34行目のstrcpy( )関数呼び出しのところでバッファ領域を越えて後続のデータ領域を上書きしてしまう。このあふれ現象がバッファオーバーラン(またはバッファオーバーフロー)である。バッファmsg の後続データ領域には実行例1の12行目にあるリターンアドレスも含まれる。領域あふれを発生させ,リターンアドレスを書き換えてしまったらどうなるだろうか?
 
以下がリターンアドレスを書き換えてしまうmsg_file.txtの内容だ。
adress
0x00000000 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 ABCDEFGHIJKLMNOP
0x00000001 51 52 53 54 58 FB FF BF 74 87 04 08 QRSTQX・ソt・
・・・ebp ・・・hello関数のアドレス(main関数へのリターンアドレスを変更)

実行例1のスタックの配置から注意深く作成したものである。バッファmsgは20バイト分の領域を占めるので,まずmst_file.txtの先頭20バイトを適当にアルファベットで埋めている。
その直後の4バイトはebp に対応するので,実行例1,11 行目の値を58 fb ff bf と並べている。

この次の4 バイトがリターンアドレスに相当し, 74 87 04 08 を設定している。このアドレス0x08048774 はhello( )関数のアドレス(リスト2,42 行目)である。正常な場合であればvuln( )関数が終了するとmain( )関数の25 行目へ処理が戻るべきところを,リターンアドレスを書き換えてしまうことにより,main( )関数へ戻らずhello( )関数へ処理を移してしまおうという試みだ。
 
その実行結果を実行例2に示す。20~22行目に表示されているメッセージはhello( )関数が実行されたことを示している。狙いどおり,12 行目のリターンアドレスも0x08048774 に上書きされているのが見て取れる。


WebMoney ぷちカンパ

この記事へのコメント

コメントをお寄せ下さい

 必須
 必須
 必須
       
 必須
(コメント編集・削除に必要)
(管理者にだけ表示を許可する)

トラックバック

この記事のトラックバックURL
http://psp0kaizou.blog36.fc2.com/tb.php/555-1114fd7c

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。