関数の動的書き換え(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 }

上のコードは何をやっているかというと、

  1. func1()→func2()→func3()を順に実行し、変数valueの値を出力する(関数書き換え前)。
  2. func1()の領域をfunc2()の内容で上書きする。
  3. バッファ領域にfunc3()の内容をコピーする。
  4. 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で終了した原因としては、書き込み禁止領域のコードセグメントに対して書き込みを行ったから、と特定されます(つづく)。