関数の動的書き換え(1)
関数の内容を動的に書き換えることをやってみます。
サンプルソースは、「リンカ・ローダ実践開発テクニック」(p139)より。
/* overwrite.c */ 1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 5 int value; 6 char buffer[100]; 7 8 void func1() { value += 1; } 9 void func2() { value += 2; } 10 void func3() { value += 3; } 11 12 int main() 13 { 14 value = 0; 15 func1(); 16 func2(); 17 func3(); 18 printf("value = %d\n", value); /* 6 */ 19 memcpy(func1, func2, (char *)func3 - (char *)func2); 20 memcpy(buffer, func3, (char *)main - (char *)func3); 21 22 value = 0; 23 func1(); /* 2 */ 24 func2(); /* 4 */ 25 ((void(*)())buffer)(); /* 7 */ 26 printf("value = %d\n", value); 27 28 exit(0); 29 }
上のコードは何をやっているかというと、
- func1()→func2()→func3()を順に実行し、変数valueの値を出力する(関数書き換え前)。
- func1()の領域をfunc2()の内容で上書きする。
- バッファ領域にfunc3()の内容をコピーする。
- func1()→func2()→バッファを順に実行し、変数valueの値を出力する(関数書き換え後)。
想定される結果としては、
関数書き換え前(処理1)では、「value = 6」が表示される。 関数書き換え後(処理4)では、「value = 7」が表示される。
となるはずです。
実行してみると、
$ ./overwrite value = 6 Segmentation fault (コアダンプ)
ありゃ、SIG_SEGVで強制終了されました。
「value = 6」を出力後に落ちたので、ソースコードの19行目(func1()領域の上書き)で終了したのではと推測されます。
そこで、func1()が配置されるセクションの書き込み権限を確認してみます。
まずは、nmコマンドで…
$ nm ./overwrite (中略) 0804847c T func1 0804848e T func2 080484a0 T func3 080484b2 T main
func1()は、.textセクション(0x0804847C)に配置されています。
つぎに、objdumpでセクション情報を表示し、func1()の領域(0x0804847C〜)が本当に.textセクションに配置されたのか確認します。
$ objdump -h ./overwrite ./overwrite: ファイル形式 elf32-i386 セクション: 索引名 サイズ VMA LMA File off Algn 0 .interp 00000013 08048154 08048154 00000154 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .note.ABI-tag 00000020 08048168 08048168 00000168 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .note.gnu.build-id 00000024 08048188 08048188 00000188 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 3 .gnu.hash 00000020 080481ac 080481ac 000001ac 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 4 .dynsym 00000070 080481cc 080481cc 000001cc 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 5 .dynstr 00000058 0804823c 0804823c 0000023c 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 6 .gnu.version 0000000e 08048294 08048294 00000294 2**1 CONTENTS, ALLOC, LOAD, READONLY, DATA 7 .gnu.version_r 00000020 080482a4 080482a4 000002a4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 8 .rel.dyn 00000008 080482c4 080482c4 000002c4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 9 .rel.plt 00000028 080482cc 080482cc 000002cc 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 10 .init 00000023 080482f4 080482f4 000002f4 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 11 .plt 00000060 08048320 08048320 00000320 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 12 .text 00000274 08048380 08048380 00000380 2**4 ★ CONTENTS, ALLOC, LOAD, READONLY, CODE 13 .fini 00000014 080485f4 080485f4 000005f4 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 14 .rodata 00000014 08048608 08048608 00000608 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 15 .eh_frame_hdr 00000044 0804861c 0804861c 0000061c 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 16 .eh_frame 0000010c 08048660 08048660 00000660 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA 17 .init_array 00000004 08049f08 08049f08 00000f08 2**2 CONTENTS, ALLOC, LOAD, DATA 18 .fini_array 00000004 08049f0c 08049f0c 00000f0c 2**2 CONTENTS, ALLOC, LOAD, DATA 19 .jcr 00000004 08049f10 08049f10 00000f10 2**2 CONTENTS, ALLOC, LOAD, DATA 20 .dynamic 000000e8 08049f14 08049f14 00000f14 2**2 CONTENTS, ALLOC, LOAD, DATA 21 .got 00000004 08049ffc 08049ffc 00000ffc 2**2 CONTENTS, ALLOC, LOAD, DATA 22 .got.plt 00000020 0804a000 0804a000 00001000 2**2 CONTENTS, ALLOC, LOAD, DATA 23 .data 00000008 0804a020 0804a020 00001020 2**2 CONTENTS, ALLOC, LOAD, DATA 24 .bss 00000088 0804a040 0804a040 00001028 2**5 ALLOC 25 .comment 0000002a 00000000 00000000 00001028 2**0 CONTENTS, READONLY
★印をつけた12番目の.textセクションに配置されたことが分かります。
LMAが0x08048380、サイズが0x274なので、0x08048380 + 0x274 - 1 = 0x080485F3。つまり、.textセクションはメモリ上「0x08048380〜0x080485F3」の領域に配置されています。これは、func1()のアドレス(0x0804847C)を含む領域となり、.textセクションは「READONLY」フラグが立っているため、func1()は書き込み禁止領域に配置されたことになります。
より正確にウラをとるために、objdumpで機械語コードのダンプを出力してみます。
$ objdump -S -M intel overwrite (中略) 0804847c <func1>: #include <stdlib.h> int value; char buffer[100]; void func1() { value += 1; } 804847c: 55 push ebp 804847d: 89 e5 mov ebp,esp 804847f: a1 c4 a0 04 08 mov eax,ds:0x804a0c4 8048484: 83 c0 01 add eax,0x1 8048487: a3 c4 a0 04 08 mov ds:0x804a0c4,eax 804848c: 5d pop ebp 804848d: c3 ret 0804848e <func2>: void func2() { value += 2; } 804848e: 55 push ebp 804848f: 89 e5 mov ebp,esp 8048491: a1 c4 a0 04 08 mov eax,ds:0x804a0c4 8048496: 83 c0 02 add eax,0x2 8048499: a3 c4 a0 04 08 mov ds:0x804a0c4,eax 804849e: 5d pop ebp 804849f: c3 ret 080484a0 <func3>: void func3() { value += 3; } 80484a0: 55 push ebp 80484a1: 89 e5 mov ebp,esp 80484a3: a1 c4 a0 04 08 mov eax,ds:0x804a0c4 80484a8: 83 c0 03 add eax,0x3 80484ab: a3 c4 a0 04 08 mov ds:0x804a0c4,eax 80484b0: 5d pop ebp 80484b1: c3 ret 080484b2 <main>: int main() { 80484b2: 55 push ebp 80484b3: 89 e5 mov ebp,esp 80484b5: 83 e4 f0 and esp,0xfffffff0 80484b8: 83 ec 10 sub esp,0x10 value = 0; 80484bb: c7 05 c4 a0 04 08 00 mov DWORD PTR ds:0x804a0c4,0x0 80484c2: 00 00 00 func1(); 80484c5: e8 b2 ff ff ff call 804847c <func1> func2(); 80484ca: e8 bf ff ff ff call 804848e <func2> func3(); 80484cf: e8 cc ff ff ff call 80484a0 <func3> printf("value = %d\n", value); /* 6 */ 80484d4: a1 c4 a0 04 08 mov eax,ds:0x804a0c4 80484d9: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 80484dd: c7 04 24 10 86 04 08 mov DWORD PTR [esp],0x8048610 80484e4: e8 47 fe ff ff call 8048330 <printf@plt> memcpy(func1, func2, (char *)func3 - (char *)func2); 80484e9: ba a0 84 04 08 mov edx,0x80484a0 80484ee: b8 8e 84 04 08 mov eax,0x804848e 80484f3: 89 d1 mov ecx,edx 80484f5: 29 c1 sub ecx,eax 80484f7: 89 c8 mov eax,ecx 80484f9: 89 44 24 08 mov DWORD PTR [esp+0x8],eax 80484fd: c7 44 24 04 8e 84 04 mov DWORD PTR [esp+0x4],0x804848e 8048504: 08 8048505: c7 04 24 7c 84 04 08 mov DWORD PTR [esp],0x804847c 804850c: e8 2f fe ff ff call 8048340 <memcpy@plt> memcpy(buffer, func3, (char *)main - (char *)func3); 8048511: ba b2 84 04 08 mov edx,0x80484b2 8048516: b8 a0 84 04 08 mov eax,0x80484a0 804851b: 89 d1 mov ecx,edx 804851d: 29 c1 sub ecx,eax 804851f: 89 c8 mov eax,ecx 8048521: 89 44 24 08 mov DWORD PTR [esp+0x8],eax 8048525: c7 44 24 04 a0 84 04 mov DWORD PTR [esp+0x4],0x80484a0 804852c: 08 804852d: c7 04 24 60 a0 04 08 mov DWORD PTR [esp],0x804a060 8048534: e8 07 fe ff ff call 8048340 <memcpy@plt> value = 0; 8048539: c7 05 c4 a0 04 08 00 mov DWORD PTR ds:0x804a0c4,0x0 8048540: 00 00 00 func1(); /* 2 */ 8048543: e8 34 ff ff ff call 804847c <func1> func2(); /* 4 */ 8048548: e8 41 ff ff ff call 804848e <func2> ((void(*)())buffer)(); /* 7 */ 804854d: b8 60 a0 04 08 mov eax,0x804a060 8048552: ff d0 call eax printf("value = %d\n", value); 8048554: a1 c4 a0 04 08 mov eax,ds:0x804a0c4 8048559: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 804855d: c7 04 24 10 86 04 08 mov DWORD PTR [esp],0x8048610 8048564: e8 c7 fd ff ff call 8048330 <printf@plt> exit(0); 8048569: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0 8048570: e8 eb fd ff ff call 8048360 <exit@plt> 8048575: 66 90 xchg ax,ax 8048577: 66 90 xchg ax,ax 8048579: 66 90 xchg ax,ax 804857b: 66 90 xchg ax,ax 804857d: 66 90 xchg ax,ax 804857f: 90 nop
ダンプを見ると、func1()は、0x0804847c〜0804848dの領域に配置されていることが分かります。やはり、.textセクション(0x08048380〜0x080485F3)内に配置されていることが分かりました。
というわけで、プログラムがSIG_SEGVで終了した原因としては、書き込み禁止領域のコードセグメントに対して書き込みを行ったから、と特定されます(つづく)。