一覧OS-04-3 · 起動と実行権限の境界

ELFのロードと再配置

ELFのバイトと表を対応させ、ロード位置の計算と動的リンクによる参照先の解決を理解します。

説明

Linuxではプログラムや共有ライブラリにELFという形式を使います。ファイルの中には命令やデータのほか、どこへ配置するか、何を外部から参照するか、どこを書き換えるかを示す表があります。

外部変数totalを足す短いC関数から始め、オブジェクトの未解決シンボルを確認します。その後、教材用に配置を固定した共有オブジェクトを読み、セグメントの位置、依存ライブラリ、GOTへのアドレス書込みを順に確かめます。ELFのファイル位置、実行時アドレス、変数の値を対応させる演習です。

1リンク前の未解決シンボル

sample.cは引数に外部変数totalを足す関数です。AArch64向けにコンパイルしたsample.oのシンボル表で、定義のある名前と外部参照を調べます。

cat sample.c関数のソース; llvm-readelf -sシンボル表 sample.oコンパイル後のオブジェクト

addは20バイトの関数として定義されています。totalのNdxはUNDで、このファイル内には定義がありません。コンパイルだけで外部変数の配置先がすべて決まるわけではなく、リンクやロードの段階で参照先を結び付けます。

2ELFの先頭をバイトで確認

以降は配置を小さく固定した教材用共有オブジェクトlibsample.soを調べます。先頭24バイトには識別子、形式、バイト順、ファイル種別、CPU種別が入ります。

od -An -tx11バイトずつ16進表示 -N24先頭24バイト libsample.so共有オブジェクト

7f 45 4c 46がELFの識別子、続く02が64ビット、01がリトルエンディアンです。オフセット16の03 00はET_DYN、18のb7 00はAArch64の番号183です。sample.oのET_RELとは用途が異なります。

3ロード対象とメモリ上の大きさ

プログラムヘッダーはローダーが配置するセグメントを表します。LOADを探し、ファイル位置Offset、配置先VirtAddr、ファイルの長さFileSiz、メモリの長さMemSizを対応させます。

llvm-readelf -lプログラムヘッダー libsample.so配置するファイル

第2のLOADはファイル0x1000から0x400バイトを仮想アドレス0x2000に配置し、メモリ上では0x500バイト確保します。差の0x100バイトはゼロになります。R Eは読取り・実行、RWは読取り・書込みで、セクション名ではなくこのセグメントの属性がロード時の手掛かりです。

4ファイルの位置から実行時アドレスへ

ロードバイアスを0x400000とすると、セグメントのVirtAddrにその値を加えた場所へ配置されます。第2セグメント内のファイルオフセット0x1200がどこへ来るか計算します。

python3 -cアドレス式を実行 'bias=0x400000; offset=0x1200; address=bias+0x2000+(offset-0x1000); print(hex(address)); print(hex(0x500-0x400))'セグメント内の差とゼロ領域

0x402200へ対応します。ファイル位置0x1200をそのままアドレスへ足すのではなく、所属するセグメント内の差0x200を使います。末尾の0x100バイトはファイルから読む領域ではなく、メモリ上でゼロ初期化する部分です。

5必要なライブラリと外部変数

動的セクションのNEEDEDと動的シンボル表を表示します。NEEDEDは依存ライブラリの名前であり、totalの実アドレスではありません。

llvm-readelf -d --dyn-syms動的情報と公開シンボル libsample.so依存関係の入力

依存先はlibdata.so、totalは動的シンボル表でもUNDです。addはこの共有オブジェクトの0x300にあります。ローダーは依存先の探索と読込みを行い、シンボル名に対応する定義を解決します。探索順やシンボルの可視性なども実際の選択に影響します。

6どこを書き換えるか

再配置表のOffsetは書換え対象、Typeは計算方法、Symbolは参照する名前です。GLOB_DATでtotalのアドレスをGOTへ入れる例を読みます。

llvm-readelf -r libsample.so再配置項目; od -An -tx1 -j4096 -N8 libsample.soGOTの初期8バイト

Offset=0x2000が書換え対象の仮想アドレスです。第2LOADからファイル位置は0x1000と分かり、まだ8バイトとも0です。R_AARCH64_GLOB_DATは解決したシンボルのアドレスSに加数Aを足す方式で、この項目のAは0です。

7参照先のアドレスをGOTへ反映

library.jsonは依存先のロード位置とtotalの定義を表した資料です。ロードバイアス0x700000、totalの値0x3000を足し、GOTの8バイトへリトルエンディアンで書き込みます。

cat library.json依存先の定義; python3 -c再配置の計算 'import json,struct; from pathlib import Path; lib=json.loads(Path("library.json").read_text()); address=lib["load_bias"]+lib["symbols"]["total"]["value"]; data=bytearray(Path("libsample.so").read_bytes()); struct.pack_into("<Q",data,0x1000,address); Path("resolved.bin").write_bytes(data); print(hex(address))'S+Aを計算してGOT相当の位置へ保存

GOTへ入る値は0x703000です。書換え場所の実行時アドレス0x402000と、そこに保存する参照先0x703000は別です。ここでは計算結果をファイルのコピーへ反映して確認しています。動的ローダーはロードしたメモリ側へ再配置を適用します。

8アドレスと変数の値

書換えた8バイトを読み、totalの値40と引数2の加算結果も計算します。GOTはこの例ではtotalの値そのものではなく、totalのアドレスを保持します。

od -An -tx1 -j4096 -N8 resolved.bin再配置後のバイト; python3 -c変数の値を使う計算 'import json; from pathlib import Path; lib=json.loads(Path("library.json").read_text()); print(2+lib["symbols"]["total"]["data"])'定義にある値40と引数2

00 30 70 00 00 00 00 00を64ビットのリトルエンディアンで読むと0x703000です。関数はGOTからこのアドレスを読み、さらにその先の変数40を読んで、引数2との和42を返す構造です。

まとめ

プログラムヘッダーはロード領域を示し、FileSizよりMemSizが大きい部分はゼロ初期化されます。

動的シンボルの参照先はロード位置も含めて解決され、再配置方式に従って指定箇所へ反映されます。

書換え箇所、そこに入れるアドレス、その参照先にある値を別々に確認すると、間接参照の処理を追えます。

操作履歴と解説を振り返れます。

シェル / ELF64 AArch64

1
cat sample.c; llvm-readelf -s sample.o
extern long total;
long add(long value) { return value + total; }

Symbol table '.symtab' contains 8 entries:
   Num:    Value          Size Type    Bind   Vis       Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT   UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT   ABS sample.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT     2 .text
     3: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT     2 $x
     4: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT     4 $d
     5: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT     6 $d
     6: 0000000000000000    20 FUNC    GLOBAL DEFAULT     2 add
     7: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT   UND total
2
od -An -tx1 -N24 libsample.so
           7f  45  4c  46  02  01  01  00  00  00  00  00  00  00  00  00
           03  00  b7  00  01  00  00  00                                
3
llvm-readelf -l libsample.so
Elf file type is DYN (Shared object file)
Entry point 0x0
There are 3 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x001000 0x001000 R E 0x1000
  LOAD           0x001000 0x0000000000002000 0x0000000000002000 0x000400 0x000500 RW  0x1000
  DYNAMIC        0x001100 0x0000000000002100 0x0000000000002100 0x0000b0 0x0000b0 RW  0x8

 Section to Segment mapping:
  Segment Sections...
   00     .text 
   01     .got .dynamic .dynstr .dynsym .rela.dyn .hash 
   02     .dynamic 
   None   .shstrtab 
4
python3 -c 'bias=0x400000; offset=0x1200; address=bias+0x2000+(offset-0x1000); print(hex(address)); print(hex(0x500-0x400))'
0x402200
0x100
5
llvm-readelf -d --dyn-syms libsample.so
Dynamic section at offset 0x1100 contains 10 entries:
  Tag                Type      Name/Value
  0x0000000000000001 (NEEDED)  Shared library: [libdata.so]
  0x0000000000000005 (STRTAB)  0x2200
  0x000000000000000a (STRSZ)   22 (bytes)
  0x0000000000000006 (SYMTAB)  0x2240
  0x000000000000000b (SYMENT)  24 (bytes)
  0x0000000000000007 (RELA)    0x22a0
  0x0000000000000008 (RELASZ)  24 (bytes)
  0x0000000000000009 (RELAENT) 24 (bytes)
  0x0000000000000004 (HASH)    0x22c0
  0x0000000000000000 (NULL)    0x0

Symbol table '.dynsym' contains 3 entries:
   Num:    Value          Size Type    Bind   Vis       Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT   UND 
     1: 0000000000000300    20 FUNC    GLOBAL DEFAULT     1 add
     2: 0000000000000000     0 OBJECT  GLOBAL DEFAULT   UND total
6
llvm-readelf -r libsample.so; od -An -tx1 -j4096 -N8 libsample.so
Relocation section '.rela.dyn' at offset 0x12a0 contains 1 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
0000000000002000  0000000200000401 R_AARCH64_GLOB_DAT     0000000000000000 total + 0
           00  00  00  00  00  00  00  00                                
7
cat library.json; python3 -c 'import json,struct; from pathlib import Path; lib=json.loads(Path("library.json").read_text()); address=lib["load_bias"]+lib["symbols"]["total"]["value"]; data=bytearray(Path("libsample.so").read_bytes()); struct.pack_into("<Q",data,0x1000,address); Path("resolved.bin").write_bytes(data); print(hex(address))'
{"name":"libdata.so","load_bias":7340032,"symbols":{"total":{"value":12288,"data":40}}}
0x703000
8
od -An -tx1 -j4096 -N8 resolved.bin; python3 -c 'import json; from pathlib import Path; lib=json.loads(Path("library.json").read_text()); print(2+lib["symbols"]["total"]["data"])'
           00  30  70  00  00  00  00  00                                

42

演習完了