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と引数200 30 70 00 00 00 00 00を64ビットのリトルエンディアンで読むと0x703000です。関数はGOTからこのアドレスを読み、さらにその先の変数40を読んで、引数2との和42を返す構造です。
まとめ
プログラムヘッダーはロード領域を示し、FileSizよりMemSizが大きい部分はゼロ初期化されます。
動的シンボルの参照先はロード位置も含めて解決され、再配置方式に従って指定箇所へ反映されます。
書換え箇所、そこに入れるアドレス、その参照先にある値を別々に確認すると、間接参照の処理を追えます。
操作履歴と解説を振り返れます。
シェル / ELF64 AArch64
cat sample.c; llvm-readelf -s sample.oextern 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 totalod -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 llvm-readelf -l libsample.soElf 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
python3 -c 'bias=0x400000; offset=0x1200; address=bias+0x2000+(offset-0x1000); print(hex(address)); print(hex(0x500-0x400))'0x402200 0x100
llvm-readelf -d --dyn-syms libsample.soDynamic 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 totalllvm-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 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}}}
0x703000od -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
演習完了