わらばんし仄聞記

南の国で引きこもってるWeb屋さん

ELFファイルを作る part5

ELFファイルを作るpart4で、ライブラリとのリンクについて詳細を追ってみる手順の案に書いていた

ELFファイルを分解するプログラム、また、その分解したものから再生成するプログラム」

これについて考えてみる。
まずは先だって分解をしてみる。

目標

ELFファイルをsection単位に分解し、それを保持する。

実装

分解するファイル

今回対象とするコードはこんな感じ

システムコールではなく、libcから呼び出すputcharを使っている。
内容としては以前にやっていた、コード42でexitするのに加え、せっかくなのでputcharを使って"T"という文字を出力させている。実際に試してみると

$ nasm -f elf64 test.asm
$ gcc -s -nostartfiles -o test.out test.o
$ ./test.out
T
$ echo $?
42

という出力を得られる。先に述べてる様に、libcが必要な状態にしているので、当然実行可能ファイルはライブラリとリンクする。このtest.outが分解対象。

オブジェクトファイルを見てみる

実行可能ファイルを作成する前、test.oというオブジェクトファイルが作られるが、このファイルがリンクする前の状態であることはいろんな所に情報があるので特には触れない。
readelfコマンドでこのファイルのセクションを見てみると

$ readelf -S test.o
There are 6 section headers, starting at offset 0x40:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  000001c0
       0000000000000014  0000000000000000  AX       0     0     16
  [ 2] .shstrtab         STRTAB           0000000000000000  000001e0
       000000000000002c  0000000000000000           0     0     1
  [ 3] .symtab           SYMTAB           0000000000000000  00000210
       0000000000000090  0000000000000018           4     3     4
  [ 4] .strtab           STRTAB           0000000000000000  000002a0
       000000000000001e  0000000000000000           0     0     1
  [ 5] .rela.text        RELA             0000000000000000  000002c0
       0000000000000030  0000000000000018           3     1     4

という結果を得られる。つまり、リンクも行った後にあるセクションで、この中に無いものがリンク時に追加されたセクションである。

リンク済みのファイルを分解

実行可能ファイルとして先に作成したtest.out。これを各セクションに分解してみる。分解していじった後は、セクションとセグメントの対応 part2で得られた対応関係を元にセグメントを作成、その後にELFヘッダーを作成してやれば元の実行ファイルに戻るはず。

分解に使うコードは以前にELFファイルを作る part4等で使ったものを流用する。主にヘッダー類の読み書きとか。

やってみた

コードはこんな感じ

パッケージで使ってるファイル達はこちら。各ファイルの階層は

/
|- teardown.py
-- elf/
   |- Utils.py
   -- components/
      |- SectionAggregator.py
      |- Section.py
      -- headers/
         |- Eh.py
         |- Header.py
         |- Ph.py
         -- Sh.py

という具合。gistで階層わかる様に置ける方法無いかな・・・。githubの方だとあとで書き換えちゃう可能性があるしな・・・。

teardown.py

ELFファイルからsectionを取り出してるこの処理の説明をば。

  • 8~9行目

見ての通り、今回の処理対象であるtest.outを読み込んでます。そして、処理しやすいようにバイト毎の値を各要素として持つリスト、byteListへ格納。

  • 12~13行目

ELF header用クラスのインスタンスを作成し、byteListの先頭から64byte相当のデータを読み込ませる。この処理でインスタンスに、ヘッダ内の各要素が持つべき値を振り分ける。

  • 15~17行目

セクションサイズ、セクション数、セクションヘッダ開始位置オフセットの取得

  • 20~27行目

shstrtabセクションのデータを取得し、セクション名テーブルを作成(各セクション名を\0区切りにした文字列となる)。

  • 30~44行目

nullセクションとshstrtabセクション以外の全セクションについて、ヘッダ、名前、データを取得してSectionクラスのインスタンスを作成。それをSectionAggregatorへ追加。

比較してみる

readelfで得られる情報と、今回作成した処理で得られる結果を比較してみる

  • readelf -S test.out
$ readelf -S test.out
There are 15 section headers, starting at offset 0x1090:

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         00000000004001c8  000001c8
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.gnu.build-i NOTE             00000000004001e4  000001e4
       0000000000000024  0000000000000000   A       0     0     4
  [ 3] .hash             HASH             0000000000400208  00000208
       0000000000000018  0000000000000004   A       5     0     8
  [ 4] .gnu.hash         GNU_HASH         0000000000400220  00000220
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           0000000000400240  00000240
       0000000000000048  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400288  00000288
       0000000000000024  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           00000000004002ac  000002ac
       0000000000000006  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          00000000004002b8  000002b8
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.plt         RELA             00000000004002d8  000002d8
       0000000000000030  0000000000000018   A       5    10     8
  [10] .plt              PROGBITS         0000000000400308  00000308
       0000000000000030  0000000000000010  AX       0     0     4
  [11] .text             PROGBITS         0000000000400340  00000340
       0000000000000014  0000000000000000  AX       0     0     16
  [12] .dynamic          DYNAMIC          0000000000600e98  00000e98
       0000000000000150  0000000000000010  WA       6     0     8
  [13] .got.plt          PROGBITS         0000000000600fe8  00000fe8
       0000000000000028  0000000000000008  WA       0     0     8
  [14] .shstrtab         STRTAB           0000000000000000  00001010
       000000000000007e  0000000000000000           0     0     1
  • teardown.py
$ python teardown.py

====== Section Header(.interp) ======
name_index:      11
type:            1
flag:            2
address:         4194760(0x4001c8)
offset:          456(0x1c8)
size:            28(0x1c)
link:            0
info:            0
address_align:   1
entry_table_size:0
====== Section Header(.note.gnu.build-id) ======
name_index:      19
type:            7
flag:            2
address:         4194788(0x4001e4)
offset:          484(0x1e4)
size:            36(0x24)
link:            0
info:            0
address_align:   4
entry_table_size:0
====== Section Header(.hash) ======
name_index:      42
type:            5
flag:            2
address:         4194824(0x400208)
offset:          520(0x208)
size:            24(0x18)
link:            5
info:            0
address_align:   8
entry_table_size:4

... 以下略

良さそうな感じだ。