実行できない配列
例として、x86_64 で第1引数と第2引数の値の和を返すプログラムを用意する。
89 f8 mov eax, edi
01 f0 add eax, esi
c3 ret
これを呼び出すプログラムを用意する。
このようにデータへのポインタを関数ポインタに変換するのはC言語の規格には違反するみたいだけど、目をつぶってね…
#include <stdio.h>
unsigned char program[] = {0x89, 0xf8, 0x01, 0xf0, 0xc3};
int main(void) {
int (*add)(int, int) = (void*)program;
printf("%d\n", add(40, 2));
return 0;
}
しかし、これを実行するとエラーになってしまった。
yUZ7Mw - Online C Compiler & Debugging Tool - Ideone.com
これは、メモリ上でデータを格納する領域とプログラムを格納する領域が分かれており、データを格納する領域にあるデータをプログラムとして実行しようとするとエラーとして落とす設定になっているからであると考えられる。
実行できる配列
GCC では、section
属性を用いることで、変数を配置するセクションを指定できる。
Common Variable Attributes (Using the GNU Compiler Collection (GCC))
これを用いてプログラムを格納した配列をプログラムを格納する領域 (.text
セクション) に配置することで、配列に格納したプログラムを実行できるようになる (ことがある)。
#include <stdio.h>
__attribute__ ((section (".text")))
unsigned char program[] = {0x89, 0xf8, 0x01, 0xf0, 0xc3};
int main(void) {
int (*add)(int, int) = (void*)program;
printf("%d\n", add(40, 2));
return 0;
}
XUDB3j - Online C Compiler & Debugging Tool - Ideone.com
ただし、プログラムを格納する領域への書き込みは許可されておらず、プログラムを格納する領域に配置した配列の要素への代入はエラーの原因となる。
#include <stdio.h>
__attribute__ ((section (".text")))
unsigned char program[] = {0x89, 0xf8, 0x01, 0xf0, 0x83, 0xc0, 0x01, 0xc3};
int main(void) {
int (*add)(int, int) = (void*)program;
program[6] = 0x2; /* エラー */
printf("%d\n", add(40, 2));
return 0;
}
さらに、この方法を応用し、main
関数を配列として記述することも可能である。
__attribute__ ((section (".text")))
char main[] = "H\xb8orld\n\0\0\0PH\xb8hello, wP\xb8\x01\0\0\0\x89\xc7H\x89\xe6\xba\r\0\0\0\x0f\x05H\x83\xc4\0201\xc0\xc3";
L54eSJ - Online C Compiler & Debugging Tool - Ideone.com
アセンブリソース
bits 64
mov rax, 0x0a646c726f
push rax
mov rax, 0x77202c6f6c6c6568
push rax
mov eax, 1
mov edi, eax
mov rsi, rsp
mov edx, 13
syscall
add rsp, 16
xor eax, eax
ret