Weitere ähnliche Inhalte Ähnlich wie ELFの動的リンク (20) ELFの動的リンク4. 自己紹介
• 池袋バイナリ勉強会を主催(後述)
• 歴史的発展に興味があり、UNIX V1からNetBSDへの
系譜を追い掛けようとしている
• V1 → V6 → V7 → 32V → 4BSD → 386BSD → NetBSD
• NetBSDをOS学習の定番にしたいですね!
• 落ち着いたらMulticsに進みたい
• 手始めのV6で格闘中
• せっかくなので調べたことを教材にまとめたい
• PDP-11や8進数が障害になっていると感じたため、8086移
植を計画中(後述)
8. ELFファイルとは
• 実行可能バイナリのファイル形式
• 歴史的発展: a.out → COFF → ELF
• NetBSDのネイティブサポートはCOFFを経由せずに
a.out → ELF
• WindowsのPEはCOFFを拡張したもの
• Mac OS XではMach-Oという形式
• 読んだことがないので、詳しく知りません
• NetBSDではCOFFもPEもMach-Oもバイナリエミュレー
ションとして読み込むことはできる
• 参照するライブラリが揃わないと実行できないけど
9. a.outファイルの構造
• UNIX V6のものを示す
• a.outファイルは独自拡張された
亜種が多い
• セクションは.textと.data固定
• .bssはすべてゼロのため、ヘッダ
にサイズだけ定義されている
• 後ろに再配置情報やシンボル
情報が付けられるが、実行に
は必須ではないため省略
• stripコマンドによってそれらを
削った状態が右図
a.outヘッダ
.text
.data
12. セクションとセグメント (1)
• セグメントという用語は色々な用法があるため混乱を
招きやすい
• x86のセグメントはCPUの機能を指しているため、ELFのセ
グメントと同じ概念ではない
• 前者(x86の機能)で後者(ELFのセグメント)を実装できる
ため、まったく別物というわけではない
• しかし現在そのような実装は主流ではない
• 64bitではセグメント自体が廃止された
• セグメントのことはあまり深く考えないで、セクション
中心に見ていけば良いと思う
• ここから先はその方針で進める
13. セクションとセグメント (2)
• UNIX V6にもセグメントの概念は
存在した
• メモリを用途別にMMUで分割し
た単位がセグメント
1. .text
2. .data + .bss + ヒープ + スタック
• ヒープとスタックの間の空きは割り
当てないで物理メモリを節約
• これが発展して抽象化されたの
がELFのセグメント
• と考えれば良いのではないか
メモリ配置
.text
.data
.bss
ヒープ
(空き)
スタック
15. 静的リンク
• まずは静的リンクから
$ i386-elf-gcc –nostdlib -s ret.s
$ i386-elf-readelf -S a.out
There are 3 section headers, ...
Section Headers:
[Nr] Name ...
[ 0] ...
[ 1] .text ...
[ 2] .shstrtab ...
ret.s
.globl _start
_start:
ret
ELFヘッダ
プログラムヘッダ
.text
.shstrtab
セクションヘッダ
17. 共有ライブラリの作成
• 動的リンク対象として準備
• 今回は呼び出し側を追うため、共
有ライブラリ側は追わない
• セクションとか凄いことになっている
$ i386-elf-gcc -c test.s
$ i386-elf-ld -shared -s -o libtest.so test.o
$ file libtest.so
libtest.so: ELF 32-bit LSB shared object, Intel
80386, version 1 (SYSV), dynamically linked,
stripped
test.s
.globl test
test:
ret
18. 動的リンク
• 共有ライブラリの中のコードは
PLT(Procedure Linkage Table)経
由で呼び出す(後述)
• いきなりセクションが爆発!
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
start.s
.globl _start
_start:
call test@PLT
$ i386-elf-gcc -nostdlib -s
start.s libtest.so
19. セクション概要
セクション 説明
.interp 動的リンクを処理するインタプリタ
.hash シンボル名のハッシュテーブル
.dynsym シンボルテーブル
.dynstr シンボル名の文字列テーブル
.rel.plt
動的リンクのために書き替えが必要なアド
レスのリスト
.plt
動的リンクされた関数などを.got.pltからア
ドレスを取得して呼び出すコード
.text
C言語などで書いたプログラムをコンパイ
ルしたコード
.dynamic 動的リンクに必要な情報を集めたテーブル
.got.plt
動的リンクされた関数などのアドレステー
ブル
.shstrtab セクション名の文字列テーブル
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
20. ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
ELFヘッダ
• 32bit/64bit、エンディアン、CPU
• 後続のヘッダへのポインタ
$ i386-elf-readelf -h a.out
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x80481b4
Start of program headers: 52 (bytes into file)
Start of section headers: 672 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 5
Size of section headers: 40 (bytes)
Number of section headers: 11
Section header string table index: 10
21. プログラムヘッダ
• ローダのための情報
• セグメント、動的リンク関係
$ i386-elf-readelf -l a.out
Elf file type is EXEC (Executable file)
Entry point 0x80481b4
There are 5 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000a0 0x000a0 R E 0x4
INTERP 0x0000d4 0x080480d4 0x080480d4 0x00013 0x00013 R 0x1
[Requesting program interpreter: /usr/lib/libc.so.1]
LOAD 0x000000 0x08048000 0x08048000 0x001b9 0x001b9 R E 0x1000
LOAD 0x0001bc 0x080491bc 0x080491bc 0x00098 0x00098 RW 0x1000
DYNAMIC 0x0001bc 0x080491bc 0x080491bc 0x00088 0x00088 RW 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .hash .dynsym .dynstr .rel.plt .plt .text
03 .dynamic .got.plt
04 .dynamic
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
22. .interp
• 文字列が格納されている
• 動的リンクを行う外部プログラム
• インタプリタと呼ばれる
• NetBSDではld.elf_so
• シーケンス
1. execveシステムコール
2. カーネルのローダが指定された
ELFファイルを読み込み
3. インタプリタが動的リンクを処理
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
$ i386-elf-readelf -p .interp a.out
String dump of section '.interp':
[ 0] /usr/lib/libc.so.1
24. .dynsym
• シンボルのテーブル
• .hashで名前がハッシュ化
• 共有ライブラリの関数への参照
• Valueがゼロ、NdxがUND
• 自身が持つシンボル
• Valueにアドレス入っている
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
$ i386-elf-readelf -s a.out
Symbol table '.dynsym' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE GLOBAL DEFAULT UND test
2: 08049254 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
3: 08049254 0 NOTYPE GLOBAL DEFAULT ABS _edata
4: 08049254 0 NOTYPE GLOBAL DEFAULT ABS _end
25. .dynstr
• 文字列が格納されている
• シンボル名
• .dynsymから参照
• 共有ライブラリのファイル名
• .dynamicから参照
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
$ i386-elf-readelf -p .dynstr a.out
String dump of section '.dynstr':
[ 1] libtest.so
[ c] test
[ 11] _edata
[ 18] __bss_start
[ 24] _end
26. .rel.plt
• 動的リンクのために書き替えが
必要なアドレスのリスト
• アドレスとシンボルのペア
• インタプリタによって共有ライブラ
リ内のアドレスが書き込まれる
• 動的リンクの根幹メカニズム
• 下の例では08049250にtestのアド
レスを書き込むことで動的リンクが
実現される
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
$ i386-elf-readelf -r a.out
Relocation section '.rel.plt' at offset 0x18c contains 1 entries:
Offset Info Type Sym.Value Sym. Name
08049250 00000107 R_386_JUMP_SLOT 00000000 test
27. .plt
• リンカが自動生成する定型コード
• 動的リンクされた関数を.got.pltから
アドレスを取得して飛ぶラッパー
• それ以外のコードは遅延リンク(後述)
• サンプルコードのtest@PLTがこれ
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
$ i386-elf-objdump -d -M intel -j .plt a.out
Disassembly of section .plt:
08048194 <test@plt-0x10>:
8048194: ff 35 48 92 04 08 push DWORD PTR ds:0x8049248
804819a: ff 25 4c 92 04 08 jmp DWORD PTR ds:0x804924c
80481a0: 00 00 add BYTE PTR [eax],al
080481a4 <test@plt>:
80481a4: ff 25 50 92 04 08 jmp DWORD PTR ds:0x8049250
80481aa: 68 00 00 00 00 push 0x0
80481af: e9 e0 ff ff ff jmp 8048194 <test@plt-0x10>
start.s
.globl _start
_start:
call test@PLT
31. 08048194 <test@plt-0x10>:
8048194: ff 35 48 92 04 08 push DWORD PTR ds:0x8049248
804819a: ff 25 4c 92 04 08 jmp DWORD PTR ds:0x804924c
80481a0: 00 00 add BYTE PTR [eax],al
080481a4 <test@plt>:
80481a4: ff 25 50 92 04 08 jmp DWORD PTR ds:0x8049250
80481aa: 68 00 00 00 00 push 0x0
80481af: e9 e0 ff ff ff jmp 8048194 <test@plt-0x10>
.got.plt
• 動的リンクされた関数などのアド
レステーブル
• ここをインタプリタが書き替える
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
$ i386-elf-readelf -x .got.plt a.out
Hex dump of section '.got.plt':
0x08049244 bc910408 00000000 00000000 aa810408
.dynamic 遅延リンク関係 書替対象
.pltが参照しているのはココ!
32. .shstrtab
• セクション名の文字列が格納さ
れている
• 名前を任意長で取るため
• COFF/PEではセクションヘッダに名
前が固定長8文字で格納
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
$ i386-elf-readelf -p .shstrtab a.out
String dump of section '.shstrtab':
[ 1] .shstrtab
[ b] .interp
[ 13] .hash
[ 19] .dynsym
[ 21] .dynstr
[ 29] .rel.plt
[ 32] .text
[ 38] .dynamic
[ 41] .got.plt
文字列の途中から
参照することで兼ね
ている
(NULL終端による)
33. セクションヘッダ
• セクションの情報
• 位置やセクション数はELFヘッダ
で定義されている
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
$ i386-elf-readelf -S a.out
There are 11 section headers, starting at offset 0x2a0:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 080480d4 0000d4 000013 00 A 0 0 1
[ 2] .hash HASH 080480e8 0000e8 000028 04 A 3 0 4
[ 3] .dynsym DYNSYM 08048110 000110 000050 10 A 4 1 4
[ 4] .dynstr STRTAB 08048160 000160 000029 00 A 0 0 1
[ 5] .rel.plt REL 0804818c 00018c 000008 08 A 3 6 4
[ 6] .plt PROGBITS 08048194 000194 000020 04 AX 0 0 4
[ 7] .text PROGBITS 080481b4 0001b4 000005 00 AX 0 0 4
[ 8] .dynamic DYNAMIC 080491bc 0001bc 000088 08 WA 4 0 4
[ 9] .got.plt PROGBITS 08049244 000244 000010 04 WA 0 0 4
[10] .shstrtab STRTAB 00000000 000254 00004a 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
34. ローダとセクション
• ローダはセクションヘッダを見ないということ
にはなっているけど、NetBSDではLinuxバイ
ナリかどうかの判定で見ている
• NetBSD 5.1でバイナリをいじっていたとき、
縮めるためセクションヘッダと.shstrtabを削
除したのにELFヘッダの修正を忘れて不正な
参照が残りカーネルパニックになったことが
ある(NetBSD 6で修正済み)
• その問題を追った時に作ったパッチ
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
--- sys/compat/linux/common/linux_exec_elf32.c.orig
+++ sys/compat/linux/common/linux_exec_elf32.c
@@ -111,7 +111,7 @@
* Now let's find the string table. If it does not exists, give up.
*/
strndx = (int)(eh->e_shstrndx);
- if (strndx == SHN_UNDEF) {
+ if (strndx == SHN_UNDEF || strndx >= eh->e_shnum) {
error = ENOEXEC;
goto out;
}
38. ファイルの読み込み
• 指定されたファイルを読み込む
• 省略した場合は a.out
• 単純化のため逐次読み込みしな
いで、全体を一度に読み込む
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
from sys import argv
aout = "a.out" if len(argv) < 2 else argv[1]
with open(aout, "rb") as f:
elf = f.read()
39. ELFヘッダ (1)
• ELFファイルかどうかをチェック
• ファイルサイズ
• ファイル全体がヘッダより小さいという
ことはあり得ない
• ゴルフと言ってヘッダの中にセクション
を入れる遊びはあるけど・・・
• シグネチャ
• アーキテクチャのチェック
• 32bit
• リトルエンディアン
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
assert len(elf) >= 52, "not found: ELF32 header"
assert elf[0:4] == "¥x7fELF", "not found: ELF signature"
assert ord(elf[4]) == 1, "not 32bit"
assert ord(elf[5]) == 1, "not little endian"
40. ELFヘッダ (2)
• ヘッダ本体の読み込み
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
(e_type,
e_machine,
e_version,
e_entry,
e_phoff,
e_shoff,
e_flags,
e_ehsize,
e_phentsize,
e_phnum,
e_shentsize,
e_shnum,
e_shstrndx) = unpack(
"<HHLLLLLHHHHHH", elf[16:52])
42. プログラムヘッダ (1)
• プログラムヘッダは複数ある
• 1つずつインスタンスとして読み込
むためのクラスを定義
class Elf32_Phdr:
def __init__(self, data, pos):
(self.p_type,
self.p_offset,
self.p_vaddr,
self.p_paddr,
self.p_filesz,
self.p_memsz,
self.p_flags,
self.p_align) = unpack(
"<LLLLLLLL", data[pos : pos + 32])
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
43. プログラムヘッダ (2)
• ELFヘッダの情報を基に、プログラ
ムヘッダを読み込む
phs = [Elf32_Phdr(elf, e_phoff + i * e_phentsize)
for i in range(e_phnum)]
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
• 内包表記でリストを作成
項目 説明
e_phoff プログラムヘッダのファイル中の位置
e_phentsize プログラムヘッダ1個のサイズ
e_phnum プログラムヘッダの個数
44. セグメントのロード
• メモリサイズの算出
memlen = max([ph.p_vaddr + ph.p_memsz for ph in phs])
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
• 実行可能ページを確保
mem = VirtualAlloc(0, memlen,
MEM_COMMIT, PAGE_EXECUTE_READWRITE)
• プログラムヘッダを見てロード
pelf = cast(elf, c_void_p).value
for ph in phs:
addr = mem + ph.p_vaddr
if ph.p_type == 1: # PT_LOAD
o, sz = ph.p_offset, ph.p_memsz
memmove(addr, pelf + o, sz)
print "LOAD: %08x-%08x => %08x-%08x" % (
o, o + sz - 1, addr, addr + sz - 1)
45. .dynamic
• プログラムヘッダに.dynamicが指
定されていれば読み込む
• 最低限必要なものだけ
• type = 0 は終端
elif ph.p_type == 2: # PT_DYNAMIC
while True:
type = read32(addr)
val = read32(addr + 4)
if type == 0: break
elif type == 5: strtab = mem + val
elif type == 6: symtab = mem + val
elif type == 11: syment = val
elif type == 23: jmprel = mem + val
elif type == 2: pltrelsz = val
addr += 8
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
46. .rel.plt
• アドレスとシンボルを読み込み
• .dynsymと.dynstrをたどることで、
シンボルを解釈
if jmprel != None:
print
print ".rel.plt(DT_JMPREL):"
for reladdr in range(jmprel, jmprel + pltrelsz, 8):
offset = read32(reladdr)
info = read32(reladdr + 4)
stroff = read32(symtab + (info >> 8) * syment)
name = string_at(strtab + stroff)
print "[%08x]offset: %08x, info: %08x; %s" % (
reladdr, offset, info, name)
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
47. リンク
• シンボル名に合致する関数を探し
てアドレスを書き込む
• 単純化のため共有ライブラリは読
み込まないでPythonで定義した関
数へのサンクを書き込んでいる
assert libc.has_key(name), "undefined reference: " + name
addr = mem + offset
faddr = cast(libc[name], c_void_p).value
print "linking: %s -> [%08x]%08x" % (name, addr, faddr)
write32(addr, faddr)
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
Hex dump of section '.got.plt':
0x08049244 bc910408 00000000 00000000 aa810408
書替対象
080481a4 <test@plt>:
80481a4: ff 25 50 92 04 08 jmp DWORD PTR ds:0x8049250
参
照
50. 遅延リンク
1. 最初に呼び出されたときにインタ
プリタで受け取る
2. .got.pltを書き替えて対象に飛ぶ
3. 次回からはそのまま飛べる
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
08048194 <test@plt-0x10>:
8048194: ff 35 48 92 04 08 push DWORD PTR ds:0x8049248
804819a: ff 25 4c 92 04 08 jmp DWORD PTR ds:0x804924c
80481a0: 00 00 add BYTE PTR [eax],al
080481a4 <test@plt>:
80481a4: ff 25 50 92 04 08 jmp DWORD PTR ds:0x8049250
80481aa: 68 00 00 00 00 push 0x0
80481af: e9 e0 ff ff ff jmp 8048194 <test@plt-0x10>
Hex dump of section '.got.plt':
0x08049244 bc910408 00000000 00000000 aa810408
8049248 8049250804924c
jmp [0x08049250] → jmp 0x080481aa
.plt.text .text
インタプリタ
3
1 2
実行ファイル 共有ライブラリ
51. Pythonで遅延リンク
• Python内のインタプリタを呼ぶた
めのサンクを.got.pltに登録
def interp(id, offset):
print "delayed link: id=%08x, offset=%08x" % (id, offset)
return link(jmprel + offset)
thunk_interp = CFUNCTYPE(c_void_p, c_void_p, c_uint32)(interp)
call_interp = JIT([
0xff, 0x14, 0x24, # call [esp]
0x83, 0xc4, 8, # add esp, 8
0x85, 0xc0, # test eax, eax
0x74, 2, # jz 0f
0xff, 0xe0, # jmp eax
0xc3 ]) # 0: ret
if pltgot != None:
writeptr(pltgot + 4, thunk_interp)
writeptr(pltgot + 8, call_interp)
ELFヘッダ
プログラムヘッダ
.interp
.hash
.dynsym
.dynstr
.rel.plt
.plt
.text
.dynamic
.got.plt
.shstrtab
セクションヘッダ
Hex dump of section '.got.plt':
0x08049244 bc910408 00000000 00000000 aa810408
08048194: push thunk_interp
0804819a: jmp call_interp
test@plt:
080481a4: jmp [0x08049250]
080481aa: push 0x0
080481af: jmp 0x08048194