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 にするための空間です。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h