ELF64でQuine

2019-03-06
ELF64を自前で生成する方法がわかったので、それを使って[クワイン](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AF%E3%82%A4%E3%83%B3_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%29)をさせてみたら面白かろう、と思ってやってみた。

結論からいうと、まるで面白味のないものになった。

プログラミング言語でのクワインの場合、プログラムのソースコードをデータとして持っておいてそれ自身を埋め込んだ文字列を出力するという方法を取るみたいなんだけど、 ソースコードを文字列データとして持つときにクォーテーションをエスケープする必要があって、そこがちょっとトリッキーになる。

だけどELF、バイナリの場合にはプログラム=データ=バイト列なのでそもそもクォーテーションがないのでエスケープする必要もなく、データをそのまま出力できてしまう。

さらにLinuxのELF読み込み時にヘッダもろともメモリに読み込んでいるので、データを別に持つ必要がなくて実行しているプログラムそのものがデータとして直接使用できる。 なので自分自身を埋め込むために2回出力する必要もない。 (コード自体の参照を無効としても、データとして保持して2回出力するだけなので難しさは変わらない。)

というわけで、生成するためのCコードは”Hello, world!”とあまり変わらない内容だった:

#include <elf.h>
#include <unistd.h> // write

#define PAGE_ALIGN(adr) ((adr) & ~(0x1000 - 1))
#define LOAD_ADDRESS PAGE_ALIGN(0x12345678)

__asm__ (
"start_: \n"
// Write
" mov $1, %eax \n" // eax: system call number (__NR_write)
" mov $1, %rdi \n" // rdi: fd (stdout)
" lea start_-120(%rip), %rsi \n" // rsi: addr (120 = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr))
" mov $end_ - start_ + 120, %rdx \n" // rdx: len
" syscall \n"
// Exit
" mov $60, %eax \n" // eax: system call number (__NR_exit)
" mov $0, %rdi \n" // rdi: exit code
" syscall \n"
"end_:\n");
extern char *start_, *end_;

void out_elf_header(void) {
Elf64_Ehdr ehdr = {
.e_ident = { ELFMAG0, ELFMAG1, ELFMAG2 ,ELFMAG3,
ELFCLASS64, ELFDATA2LSB, EV_CURRENT, ELFOSABI_SYSV },
.e_type = ET_EXEC,
.e_machine = EM_X86_64,
.e_version = EV_CURRENT,
.e_entry = LOAD_ADDRESS + sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr),
.e_phoff = sizeof(Elf64_Ehdr),
.e_shoff = 0, // dummy
.e_flags = 0x0,
.e_ehsize = sizeof(Elf64_Ehdr),
.e_phentsize = sizeof(Elf64_Phdr),
.e_phnum = 1,
.e_shentsize = 0, // dummy
.e_shnum = 0,
.e_shstrndx = 0, // dummy
};

write(1, &ehdr, sizeof(Elf64_Ehdr));
}

void out_program_header(size_t code_len) {
Elf64_Phdr phdr = {
.p_type = PT_LOAD,
.p_offset = 0x0,
.p_vaddr = LOAD_ADDRESS,
.p_paddr = 0, // dummy
.p_filesz = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr) + code_len,
.p_memsz = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr) + code_len,
.p_flags = PF_R | PF_X,
.p_align = 0x1000,
};

write(1, &phdr, sizeof(Elf64_Phdr));
}

void out_code(const void *code, size_t code_len) {
write(1, code, code_len);
}

int main() {
size_t code_len = (uintptr_t)&end_ - (uintptr_t)&start_;

out_elf_header();
out_program_header(code_len);
out_code(&start_, code_len);

return 0;
}

実行結果:

$ gcc -m64 -Wall -o elf_quine elf_quine.c
$ ./elf_quine > elf && chmod +x elf
$ ./elf > elf2
$ diff -b elf elf2
$ ls -l elf elf2
-rwxr-xr-x 1 tyfkda tyfkda 162 Mar 6 12:54 elf
-rw-r--r-- 1 tyfkda tyfkda 162 Mar 6 12:54 elf2
$ xxd elf
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0200 3e00 0100 0000 7850 3412 0000 0000 ..>.....xP4.....
00000020: 4000 0000 0000 0000 0000 0000 0000 0000 @...............
00000030: 0000 0000 4000 3800 0100 0000 0000 0000 ....@.8.........
00000040: 0100 0000 0500 0000 0000 0000 0000 0000 ................
00000050: 0050 3412 0000 0000 0000 0000 0000 0000 .P4.............
00000060: a200 0000 0000 0000 a200 0000 0000 0000 ................
00000070: 0010 0000 0000 0000 b801 0000 0048 c7c7 .............H..
00000080: 0100 0000 488d 3575 ffff ff48 c7c2 a200 ....H.5u...H....
00000090: 0000 0f05 b83c 0000 0048 c7c7 0000 0000 .....<...H......
000000a0: 0f05 ..

参照