unless’s blog

日々のちょっとした技術的なことの羅列

オブジェクトファイルと戯れてみる Pert.1

echoコマンドのオブジェクトファイルと戯れてみる

ELF

ELFとは実行可能バイナリやオブジェクトファイル等のフォーマットを規定したのもだそう

ja.wikipedia.org

echo コマンドのELFヘッダを見てみる

ELFヘッダとは

ファイルの先頭に存在し、ELF識別子、アーキテクチャ情報および、他の2つのヘッダへの情報を持つ。

ヘッダは下記で確認できる

# readelf -h /bin/echo
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x40192a
  Start of program headers:          64 (bytes into file)
  Start of section headers:          31144 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 30

ehco コマンドのプログラムヘッダを見てみる

プログラムヘッダとは

ファイル上のどの部分(セグメント)がどのような属性で何処に読み込まれるかを保持するヘッダであり、ファイルローダによって扱われる。
ELFヘッダに続いて実行時ディスクから何らかの形で読み込まれるセグメントの数だけ存在する。

プログラムヘッダは下記でみれる

# readelf -l /bin/echo

Elf file type is EXEC (Executable file)
Entry point 0x40192a
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000005ecc 0x0000000000005ecc  R E    200000
  LOAD           0x0000000000006da8 0x0000000000606da8 0x0000000000606da8
                 0x0000000000000478 0x00000000000005d8  RW     200000
  DYNAMIC        0x0000000000006e08 0x0000000000606e08 0x0000000000606e08
                 0x00000000000001d0 0x00000000000001d0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x00000000000051cc 0x00000000004051cc 0x00000000004051cc
                 0x000000000000023c 0x000000000000023c  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000006da8 0x0000000000606da8 0x0000000000606da8
                 0x0000000000000258 0x0000000000000258  R      1

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
   03     .init_array .fini_array .jcr .data.rel.ro .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag .note.gnu.build-id
   06     .eh_frame_hdr
   07
   08     .init_array .fini_array .jcr .data.rel.ro .dynamic .got

ELFファイルタイプはEXEC(実行可能)でエントリポイントが0x40192a等の情報がわかる
また

Section to Segment mapping

以降の情報から最初のプログラムヘッダで示されるセグメントはPHDRでそれに含まれるセクションはなく
次がINTRTP。この中には.interpが含まれてることがわかる

セクションヘッダを見てみる

セクションヘッダとは

オブジェクトファイルの論理的な構造を記述する部分で、ヘッダと名前がついているが実際にはファイルの最後あたりに置かれていることが多い。
ここはリンカやデバッガによって参照されることがある。セクションはセクション名があるが、それは特殊なセクションに置かれ何番目の文字列かという指し方を行う。
このことによって、エントリそのものは固定長にしつつセクション名の長さ制限を取り払っている。

下記で確認できる

# readelf -S /bin/echo
There are 31 section headers, starting at offset 0x79a8:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8
       0000000000000558  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400810  00000810
       000000000000025d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400a6e  00000a6e
       0000000000000072  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400ae0  00000ae0
       0000000000000060  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400b40  00000b40
       0000000000000078  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400bb8  00000bb8
       0000000000000498  0000000000000018  AI       5    25     8
  [11] .init             PROGBITS         0000000000401050  00001050
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000401070  00001070
       0000000000000320  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         0000000000401390  00001390
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000004013a0  000013a0
       000000000000308a  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         000000000040442c  0000442c
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         0000000000404440  00004440
       0000000000000d8b  0000000000000000   A       0     0     32
  [17] .eh_frame_hdr     PROGBITS         00000000004051cc  000051cc
       000000000000023c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000405408  00005408
       0000000000000ac4  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000606da8  00006da8
       0000000000000008  0000000000000008  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000606db0  00006db0
       0000000000000008  0000000000000008  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000606db8  00006db8
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .data.rel.ro      PROGBITS         0000000000606dc0  00006dc0
       0000000000000048  0000000000000000  WA       0     0     32
  [23] .dynamic          DYNAMIC          0000000000606e08  00006e08
       00000000000001d0  0000000000000010  WA       6     0     8
  [24] .got              PROGBITS         0000000000606fd8  00006fd8
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .got.plt          PROGBITS         0000000000607000  00007000
       00000000000001a0  0000000000000008  WA       0     0     8
  [26] .data             PROGBITS         00000000006071a0  000071a0
       0000000000000080  0000000000000000  WA       0     0     32
  [27] .bss              NOBITS           0000000000607220  00007220
       0000000000000160  0000000000000000  WA       0     0     32
  [28] .gnu_debuglink    PROGBITS         0000000000000000  00007220
       0000000000000010  0000000000000000           0     0     4
  [29] .gnu_debugdata    PROGBITS         0000000000000000  00007230
       0000000000000658  0000000000000000           0     0     1
  [30] .shstrtab         STRTAB           0000000000000000  00007888
       000000000000011e  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

echoはELFヘッダを確認した時にストリングテーブルのindexが30だったので30番目のセクションヘッダがそのストリングテーブルを保持していることがわかる

[30] .shstrtab STRTAB 0000000000000000 00007888 000000000000011e 0000000000000000 0 0 1

これでsh_offsetが0x00007888でサイズが0x000000000000011eということがわかる

ストリングテーブルを見てみる

下記で見れる

# od --skip-bytes 0x00007888 --read-bytes 0x11e -t x1z /bin/echo
0074210 00 2e 73 68 73 74 72 74 61 62 00 2e 69 6e 74 65  >..shstrtab..inte<
0074230 72 70 00 2e 6e 6f 74 65 2e 41 42 49 2d 74 61 67  >rp..note.ABI-tag<
0074250 00 2e 6e 6f 74 65 2e 67 6e 75 2e 62 75 69 6c 64  >..note.gnu.build<
0074270 2d 69 64 00 2e 67 6e 75 2e 68 61 73 68 00 2e 64  >-id..gnu.hash..d<
0074310 79 6e 73 79 6d 00 2e 64 79 6e 73 74 72 00 2e 67  >ynsym..dynstr..g<
0074330 6e 75 2e 76 65 72 73 69 6f 6e 00 2e 67 6e 75 2e  >nu.version..gnu.<
0074350 76 65 72 73 69 6f 6e 5f 72 00 2e 72 65 6c 61 2e  >version_r..rela.<
0074370 64 79 6e 00 2e 72 65 6c 61 2e 70 6c 74 00 2e 69  >dyn..rela.plt..i<
0074410 6e 69 74 00 2e 70 6c 74 2e 67 6f 74 00 2e 74 65  >nit..plt.got..te<
0074430 78 74 00 2e 66 69 6e 69 00 2e 72 6f 64 61 74 61  >xt..fini..rodata<
0074450 00 2e 65 68 5f 66 72 61 6d 65 5f 68 64 72 00 2e  >..eh_frame_hdr..<
0074470 65 68 5f 66 72 61 6d 65 00 2e 69 6e 69 74 5f 61  >eh_frame..init_a<
0074510 72 72 61 79 00 2e 66 69 6e 69 5f 61 72 72 61 79  >rray..fini_array<
0074530 00 2e 6a 63 72 00 2e 64 61 74 61 2e 72 65 6c 2e  >..jcr..data.rel.<
0074550 72 6f 00 2e 64 79 6e 61 6d 69 63 00 2e 67 6f 74  >ro..dynamic..got<
0074570 2e 70 6c 74 00 2e 64 61 74 61 00 2e 62 73 73 00  >.plt..data..bss.<
0074610 2e 67 6e 75 5f 64 65 62 75 67 6c 69 6e 6b 00 2e  >.gnu_debuglink..<
0074630 67 6e 75 5f 64 65 62 75 67 64 61 74 61 00        >gnu_debugdata.<
0074646

.shstrtabの先頭からのoffsetがストリングテーブルでのindexとなる

シンボルテーブルを見てみる

シンブルテーブルはシンボルとその値を対応させるためのテーブル

ja.wikipedia.org

下記でみることができる

# readelf -s /bin/echo

Symbol table '.dynsym' contains 57 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __uflow@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getenv@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND __progname@GLIBC_2.2.5 (2)
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.2.5 (2)
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND abort@GLIBC_2.2.5 (2)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __errno_location@GLIBC_2.2.5 (2)
     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strncmp@GLIBC_2.2.5 (2)
     8: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND stdout@GLIBC_2.2.5 (2)
     9: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _exit@GLIBC_2.2.5 (2)
    10: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strcpy@GLIBC_2.2.5 (2)
    11: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __fpending@GLIBC_2.2.5 (2)
    12: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND textdomain@GLIBC_2.2.5 (2)
    13: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fclose@GLIBC_2.2.5 (2)
    14: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND bindtextdomain@GLIBC_2.2.5 (2)
    15: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND dcgettext@GLIBC_2.2.5 (2)
    16: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_get_mb_cur_max@GLIBC_2.2.5 (2)
    17: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strlen@GLIBC_2.2.5 (2)
    18: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (3)
    19: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND mbrtowc@GLIBC_2.2.5 (2)
    20: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __overflow@GLIBC_2.2.5 (2)
    21: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strrchr@GLIBC_2.2.5 (2)
    22: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND lseek@GLIBC_2.2.5 (2)
    23: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memset@GLIBC_2.2.5 (2)
    24: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fscanf@GLIBC_2.2.5 (2)
    25: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND close@GLIBC_2.2.5 (2)
    26: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
    27: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcmp@GLIBC_2.2.5 (2)
    28: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fputs_unlocked@GLIBC_2.2.5 (2)
    29: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND calloc@GLIBC_2.2.5 (2)
    30: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strcmp@GLIBC_2.2.5 (2)
    31: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    32: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@GLIBC_2.14 (4)
    33: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND program_invocation_name@GLIBC_2.2.5 (2)
    34: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fileno@GLIBC_2.2.5 (2)
    35: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND malloc@GLIBC_2.2.5 (2)
    36: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fflush@GLIBC_2.2.5 (2)
    37: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND nl_langinfo@GLIBC_2.2.5 (2)
    38: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND ungetc@GLIBC_2.2.5 (2)
    39: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __freading@GLIBC_2.2.5 (2)
    40: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND realloc@GLIBC_2.2.5 (2)
    41: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fdopen@GLIBC_2.2.5 (2)
    42: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND setlocale@GLIBC_2.2.5 (2)
    43: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __printf_chk@GLIBC_2.3.4 (5)
    44: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND error@GLIBC_2.2.5 (2)
    45: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND __progname_full@GLIBC_2.2.5 (2)
    46: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND open@GLIBC_2.2.5 (2)
    47: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fseeko@GLIBC_2.2.5 (2)
    48: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@GLIBC_2.2.5 (2)
    49: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.2.5 (2)
    50: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fwrite@GLIBC_2.2.5 (2)
    51: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __fprintf_chk@GLIBC_2.3.4 (5)
    52: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND mbsinit@GLIBC_2.2.5 (2)
    53: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND program_invocation_short_@GLIBC_2.2.5 (2)
    54: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND iswprint@GLIBC_2.2.5 (2)
    55: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_b_loc@GLIBC_2.3 (6)
    56: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  UND stderr@GLIBC_2.2.5 (2)

セクションヘッダを見るとシンボルテーブルは下記.dynsmであることがわかる

[ 5] .dynsym           DYNSYM           00000000004002b8  000002b8
       0000000000000558  0000000000000018   A       6     1     8

これをdumpしてみると

# od --skip-bytes 0x2b8 --read-bytes 0x558 -t x1z /bin/echo
0001270 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0001310 00 00 00 00 00 00 00 00 21 01 00 00 12 00 00 00  >........!.......<
0001330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0001350 43 01 00 00 12 00 00 00 00 00 00 00 00 00 00 00  >C...............<
0001370 00 00 00 00 00 00 00 00 ef 01 00 00 11 00 00 00  >................<
0001410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0001430 ea 01 00 00 12 00 00 00 00 00 00 00 00 00 00 00  >................<
0001450 00 00 00 00 00 00 00 00 7a 00 00 00 12 00 00 00  >........z.......<
0001470 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0001510 d1 00 00 00 12 00 00 00 00 00 00 00 00 00 00 00  >................<
0001530 00 00 00 00 00 00 00 00 38 00 00 00 12 00 00 00  >........8.......<
0001550 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  >................<
0001570 f7 00 00 00 11 00 00 00 00 00 00 00 00 00 00 00  >................<
~~~(ry~~~~~

シンボルテーブルの構造はこのようになっている

64bit

uint32_t          st_name;
unsigned char     st_info;
unsigned char     st_other;
uint16_t          st_shndx;
Elf64_Addr          st_value;
uint64_t          st_size;

0番目のsymble情報は空

0001270 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<

進んでいってここの情報は

0001350 43 01 00 00 12 00 00 00 00 00 00 00 00 00 00 00 >C...............<

これはアドレス0001350から始まるこのバイト列は最初の43 01 00 00
つまりst_name=0x143に対応する
逆順になっているのは今回のアーキテクチャx86でリトルインディアンだから

.dynstrセクションを見るとストリングテーブルは先頭から0x810バイト目から始まっている

  [ 6] .dynstr           STRTAB           0000000000400810  00000810
       000000000000025d  0000000000000000   A       0     0     1

そこで0x810に0x143を足した0x953から始まる文字列を見てみる

# od --skip-bytes 0x953 --read-bytes 32 -t x1z /bin/echo
0004523 67 65 74 65 6e 76 00 5f 5f 66 72 65 61 64 69 6e  >getenv.__freadin<
0004543 67 00 73 74 64 65 72 72 00 66 73 63 61 6e 66 00  >g.stderr.fscanf.<
0004563

見た通り0x143に対応するシンボルはgetenvであることがわかった
これはreadelfのここに対応している

     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getenv@GLIBC_2.2.5 (2)

次回に続く

参考

www.oreilly.co.jp