76Byte の Hello world
小さな Hello world を目指してみることにしました。
http://www.muppetlabs.com/~breadbox/software/tiny/
では 59Byte の Hello world が公開されているのですが、これはどうも古い linux 用らしく、動きません。というわけで自分で作ってみることにしました。
私が調べた限り、エルフヘッダとプログラムヘッダ1つは必要です。エルフヘッダは52Byteあり、プログラムヘッダは32Byteありました。そのうちいくつかは変な値を入れていても怒られないようでした。
typedef struct { unsigned char e_ident[EI_NIDENT]; // 最初の4文字は"\0x7fELF"、後は任意 Elf32_Half e_type; // ET_EXEC Elf32_Half e_machine; // EM_386 Elf32_Word e_version; // 任意 Elf32_Addr e_entry; // エントリーポイントをちゃんと入れる Elf32_Off e_phoff; // プログラムヘッダの開始位置 Elf32_Off e_shoff; // 任意 Elf32_Word e_flags; // 任意 Elf32_Half e_ehsize; // 任意 Elf32_Half e_phentsize; // 32 Elf32_Half e_phnum; // 1 Elf32_Half e_shentsize; // 任意 Elf32_Half e_shnum; // 任意 Elf32_Half e_shstrndx; // 任意 } Elf32_Ehdr; typedef struct { Elf32_Word p_type; // PT_LOAD(1) Elf32_Off p_offset; // 0 Elf32_Addr p_vaddr; // 0 Elf32_Addr p_paddr; // 任意 Elf32_Word p_filesz; // ファイルサイズ Elf32_Word p_memsz; // 76以上ならある程度適当 Elf32_Word p_flags; // 任意 Elf32_Word p_align; // 任意 } Elf32_Phdr;
と、任意の空間がやたら多いのです。とりあえず "Hello world\n" は 12Byte なので最初の ELF の直後に埋めることにしました。また、 PT_LOAD が偶然 1 であることと、 e_phnum より下のエルフヘッダが任意であることを利用して、プログラムヘッダの数である、 e_phnum の 1 と共有して、エルフヘッダとプログラムヘッダを重ねてしまうことにします。これで重なってる部分である 8Byte 節約できたため、 52+32-8=76Byte が最低のプログラムサイズになりました。
任意の空間は 22Byte 余っています。埋め込むコードは以下のような 14Byte のものにしました。
0: 40 inc %eax 1: 8d 51 0c lea 0xc(%ecx),%edx 4: 8d 59 04 lea 0x4(%ecx),%ebx 7: 89 d9 mov %ebx,%ecx 00000009 <l>: 9: 93 xchg %eax,%ebx a: cd 80 int $0x80 c: eb fb jmp 9 <l>
exit で 12 が帰ってしまうのが難点ですが、まぁそれなりに短いはずです。 Hello world は 4Byte 目以降に置いたので、 write のシステムコール番号である 4 と "Hello world" のアドレスを共有できてるのがほんの少し嬉しいところだったりします。
あとは適当に jmp で飛びながら上記コードを実行するようにしてやれば、ちゃんと実行可能な、 76Byte の Hello world が完成です。
0000000 7f 45 4c 46 48 65 6c 6c 6f 20 77 6f 72 6c 64 0a >.ELFHello world.< 0000020 02 00 03 00 40 eb 09 00 14 00 00 00 2c 00 00 00 >....@.......,...< 0000040 8d 51 0c 8d 59 04 eb 10 34 00 20 00 01 00 00 00 >.Q..Y...4. .....< 0000060 00 00 00 00 00 00 00 00 89 d9 eb 08 4c 00 00 00 >............L...< 0000100 4c 00 00 00 93 cd 80 eb fb 00 00 00 >L...........<
76Byteの実行ファイル。
http://shinh.skr.jp/binary/hello.bin
上記を生成したコード。
http://shinh.skr.jp/binary/genhello.c
とか書いてるうちにもっと短くできそうな気がしてきました。それはまた今度やってみます。
追記: うまく短くできませんでした…要するにもっと激しく Ehdr と Phdr をオーバーラップさせまくる作戦だったのですが、なにやらどのパターンでもうまく行かなさそうな気がしました。
となると 76Byte で Hello world というのは割とまだ余裕があるので、空いたスペースを使ってちゃんと 0 を返して終了するようにしておきました。
http://shinh.skr.jp/binary/hello.bin
http://shinh.skr.jp/binary/genhello.c
使ったコードは以下のような 17Byte のもの。
0: 43 inc %ebx 1: 8d 51 0c lea 0xc(%ecx),%edx 4: 8d 41 04 lea 0x4(%ecx),%eax 7: 89 c1 mov %eax,%ecx 9: cd 80 int $0x80 b: 97 xchg %eax,%edi c: 40 inc %eax d: 31 db xor %ebx,%ebx f: cd 80 int $0x80
もいっこ追記:
Hello world なのに最後の ! が無いというのは、連続で空いた空間が 12Byte しか無いという悲しい事情から諦めていたのですが、実行時に ! を埋めてやればいいじゃんねーと思って修正しました。使ったコードはこんなの。
0: c6 47 10 0a movb $0xa,0x10(%edi) 4: 8d 51 0d lea 0xd(%ecx),%edx 7: 8d 41 04 lea 0x4(%ecx),%eax a: 89 c1 mov %eax,%ecx c: 43 inc %ebx d: cd 80 int $0x80 f: 93 xchg %eax,%ebx 10: 31 db xor %ebx,%ebx 12: cd 80 int $0x80
データ用の領域で偶然 jmp せずに通過できる場所があったおかげで 3Byte の増量に耐えられました。あと buf[0] = 0xff は .text セクションを writable にするための空間です。