网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 局域网DIY >> 局域网教程 >> 网管专区 >> [网络基础] >> 文章正文
  elf动态解析符号过程(修订版)          【字体:
elf动态解析符号过程(修订版)
作者:佚名    文章来源:不详    点击数:    更新时间:2005-10-1    
  
★★ 前言

本篇文章以linux为平台为例,演示elf动态解析符号的过程。
不正之处,还请斧正。

通常,elf解析符号方式称为lazy mode装载的。这种装载技术是elf平台上
默认的方式。在不同的体系平台在实现这种机制也是不同的。但是i386和sparc
在大部分上是相同的。

动态连接器(rtld)提供符号的动态连接,装载共享objects和解析标号的引用。
通常是ld.so,它可以是一个共享object也可以是个可执行的文件。


★★ 符号表(symbol table)

每个object要想使它对其他的elf文件可用,就要用到符号表(symbol table)中
symbol entry.事实上,一个symbol entry 是个symbol结构,它描述了这个
symbol的名字和该symbol的value.symbol name被编码作为dynamic string
table的索引(index). the value of a symbol是在elf object文件内该
symbol的地址。该地址通常需要被重新定位(加上该object装载到内存的基地址
(base load address)). 从而构成该symbol在内存中的绝对地址。
一个符号表入口有如下的格式:
typedef struct
{
? elf32_wordst_name; /* symbol name (string tbl index) */
? elf32_addrst_value;/* symbol value */
? elf32_wordst_size; /* symbol size */
? unsigned char st_info; /* symbol type and binding */
? unsigned char st_other;/* no defined meaning, 0 */
? elf32_section st_shndx;/* section index */
} elf32_sym;

可执行文件他们知道运行时刻他们的地址,所以他们内部的引用符号在编译时候就已
经被重定位了。


★★ got(global offset table)

got是一个数组,存在elf image的数据段中,他们是一些指向objects的指针(通常
是数据objects).动态连接器将重新修改那些编译时还没有确定下来地址的符号的
got入口。所以说got在i386动态连接中扮演着重要的角色。


★★ plt(procedure linkage table)

plt是一个这样的结构,它的entries包含了一些代码片段用来传输控制到外部的过程。
在i386体系下,plt和他的代码片段entries有如下格式:

plt0:
? push got[1] ; word of identifying information
? jmp got[2] ; pointer to rtld function nop
? ...
pltn:
? jmp got[x + n] ; got offset of symbol address
? push n ; relocation offset of symbol
? jmp plt0 ; call the rtld
pltn + 1
? jmp got[x +n +1]; got offset of symbol address
? push n +1 ; relocation offset of symbol
? jmp plt0 ; call the rtld

当传输控制到一个外部的函数时,它传输执行到plt 中跟该symbol相关的那个entry
(是在编译时候连接器安装的)。在plt entry中第一条指令将jump到一个存储在got
中的一个指针地址;假如符号还没有被解析,该got中存放着的是该plt entry中的
下一条指令地址。该指令push一个在重定位表中的偏移量到stack,然后下一条指令
传输控制到plt[0]入口。该plt[0]包含了调用rtld解析符号的函数代码。该
解析符号函数地址由程序装载器已经插入到got[2]中了。

动态连接器将展开stack并且获取需要解析符号在重定位表地址信息。重定位入口、
符号表和字符串表共同决定着plt entry引用的那个符号和在进程内存中符号应该
存放的地址。假如可能的话,该符号将被解析出来,它的地址将被存放在被该
plt entry使用的got entry中。下一次该符号被请求时,与之对应的got已经包
含了该符号的地址了。所以,所有后来的调用将直接通过got传输控制。动态连接器
只解析第一次被二进制文件所引用的符号;这种引用方式就是我们上面所说的
lazy mode。


★★ 哈希表和链(hash table and chain)

除了符号表(symbol table),got(global offset table),plt(procedure
linkage table),字符串表(string table),elf objects还可以包含一个
hash table和chain(用来使动态连接器解析符号更加容易)。hash table和chain
通常被用来迅速判定在符号表中哪个entry可能符合所请求的符号名。hash table(总
是伴随着chain的)被作为整型数组存放。在hash表中,一半位置是留给那些buckets的,
另一半是留给在chain中的元素(element)的. hash table直接反映了symbol table
的元素数目和他们的次序。

动态连接器结构提供了所有动态连接的执行是以透明方式访问动态连接器.
然而,明确访问也是可用的。动态连接(装载共享objects和解析符号),
可以通过直接访问rtld的那些函数来完成:dlopen() , dlsym() and
dlclose() .这些函数被包含在动态连接器本身中。为了访问那些函数,
连接时需要把动态连接函数库(libdl)连接进去。该库包含了一些stub函数
允许编译时候连接器解析那些函数的引用;然而那些stub函数只简单的返回0。
因为事实上函数驻留在动态连接器中,假如从静态连接的elf文件中调用
那些函数,共享object的装载将会失败。

对于执行动态连接器所必须的是:hash table,hash table元素的数目,
chain,dynamic string table和dynamic symbol talbe。满足了
这些条件,下面算法适用任何symbol的地址计算:

1. hn = elf_hash(sym_name) % nbuckets;
2. for (ndx = hash[ hn ]; ndx; ndx = chain[ ndx ]) {
3. symbol = sym_tab + ndx;
4. if (strcmp(sym_name, str_tab + symbol->st_name) == 0)
5. return (load_addr + symbol->st_value); }

hash号是elf_hash()的返回值,在elf规范的第4部分有定义,以hash table中元素
个数取模。该号被用来做hash table的下表索引,求得hash值,找出与之匹配的符号
名的chain的索引(line 3)。使用该索引,符号从符号表中获得(line 3).比较获得
的符号名和请求的符号名是否相同(line 5).使用这个算法,就可以简单解析任何符号了。


★★ 演示

#include
int main(int argc, char *argv[])
{
? printf("hello, world\n");
? return 0;
}


relocation section '.rel.plt' at offset 0x278 contains 4 entries:
? offsetinfotypesymbol's valuesymbol's name
? 0804947c00107 r_386_jump_slot 080482d8__register_frame_info
? 0804948000207 r_386_jump_slot 080482e8__deregister_frame_info
? 0804948400307 r_386_jump_slot 080482f8__libc_start_main
? 0804948800407 r_386_jump_slot 08048308printf
只有r_386_jump_slot的才会出现在got中

symbol table '.dynsym' contains 7 entries:
? num:valuesize typebind otndx name
? 0:0 0 notypelocal 0und
? 1:80482d8 116 funcweak0und __register_frame_info@glibc_2.0 (2)
? 2:80482e8 162 funcweak0und __deregister_frame_info@glibc_2.0 (
2)
? 3:80482f8 261 funcglobal0und __libc_start_main@glibc_2.0 (2)
? 4:804830841 funcglobal0und printf@glibc_2.0 (2)
? 5:804843c 4 objectglobal0 14 _io_stdin_used
? 6:0 0 notypeweak0und __gmon_start__


[alert7@redhat]$ gcc -o test test.c
[alert7@redhat]$ ./test
hello, world
[alert7@redhat]$ objdump -x test
...
dynamic section:
? neededlibc.so.6
? init0x8048298
? fini0x804841c
? hash0x8048128
? strtab0x80481c8
? symtab0x8048158
? strsz 0x70
? syment0x10
? debug 0x0
? pltgot0x8049470
? pltrelsz0x20
? pltrel0x11
? jmprel0x8048278
? rel 0x8048270
? relsz 0x8
? relent0x8
? verneed 0x8048250
? verneednum0x1
? versym0x8048242
...
? 7 .rel.got000000080804827008048270000002702**2
? contents, alloc, load, readonly, data
? 8 .rel.plt000000200804827808048278000002782**2
? contents, alloc, load, readonly, data
? 9 .init 0000002f0804829808048298000002982**2
? contents, alloc, load, readonly, code
10 .plt00000050080482c8080482c8000002c82**2
? contents, alloc, load, readonly, code
11 .text 000000fc0804832008048320000003202**4
? contents, alloc, load, readonly, code
12 .fini 0000001a0804841c0804841c0000041c2**2
? contents, alloc, load, readonly, code
13 .rodata 000000160804843808048438000004382**2
? contents, alloc, load, readonly, data
14 .data 0000000c0804945008049450000004502**2
? contents, alloc, load, data
15 .eh_frame 000000040804945c0804945c0000045c2**2
? contents, alloc, load, data
16 .ctors000000080804946008049460000004602**2
? contents, alloc, load, data
17 .dtors000000080804946808049468000004682**2
? contents, alloc, load, data
18 .got000000200804947008049470000004702**2
? contents, alloc, load, data
19 .dynamic000000a00804949008049490000004902**2
? contents, alloc, load, data
...
[alert7@redhat]$ gdb -q test
(gdb) disass main
dump of assembler code for function main:
0x80483d0
: push %ebp
0x80483d1 : mov%esp,%ebp
0x80483d3 : push $0x8048440
0x80483d8 : call 0x8048308
0x80483dd :add$0x4,%esp
0x80483e0 :xor%eax,%eax
0x80483e2 :jmp0x80483e4
0x80483e4 :leave
0x80483e5 :ret
...
0x80483ef :nop
end of assembler dump.
(gdb) b * 0x80483d8
breakpoint 1 at 0x80483d8
(gdb) r
starting program: /home/alert7/test

breakpoint 1, 0x80483d8 in main ()
(gdb) disass 0x8048308①⑴
dump of assembler code for function printf:
/****************************************///plt4:
0x8048308 : jmp*0x8049488 //jmp got[6]
? //此时,got[6]中存在的是0x804830e
0x804830e : push $0x18//$0x18为printf重定位入口在jmprel section中的偏移量
0x8048313 :jmp0x80482c8 <_init+48> //jmp plt0
? //plt0处存放着调用rtld函数的指令
? //当函数返回时候,把got[6]修改为真正的
? //printf函数地址,然后直接跳到printf函数
? //执行。
该部分为plt的一部分
/****************************************/
end of assembler dump.
(gdb) x 0x8049488
0x8049488 <_global_offset_table_+24>: 0x0804830e
080482c8 <.plt>:②//plt0:
80482c8: ff 35 74 94 04 08 pushl0x8049474//pushl got[1]地址
? //got[1]是一个鉴别信息,是link_map类型的一个指针

80482ce: ff 25 78 94 04 08 jmp*0x8049478//jmp got[2]
? //跳到动态连接器解析函数执行
80482d4: 00 00 add%al,(%eax)
80482d6: 00 00 add%al,(%eax)

80482d8: ff 25 7c 94 04 08 jmp*0x804947c//plt1:
80482de: 68 00 00 00 00push $0x0
80482e3: e9 e0 ff ff ffjmp80482c8 <_init+0x30>

80482e8: ff 25 80 94 04 08 jmp*0x8049480//plt2:
80482ee: 68 08 00 00 00push $0x8
80482f3: e9 d0 ff ff ffjmp80482c8 <_init+0x30>

80482f8: ff 25 84 94 04 08 jmp*0x8049484//plt3:
80482fe: 68 10 00 00 00push $0x10
8048303: e9 c0 ff ff ffjmp80482c8 <_init+0x30>

8048308: ff 25 88 94 04 08 jmp*0x8049488//plt4:
804830e: 68 18 00 00 00push $0x18
8048313: e9 b0 ff ff ffjmp80482c8 <_init+0x30>

(gdb) b * 0x80482c8
breakpoint 2 at 0x80482c8
(gdb) c
continuing.

breakpoint 2, 0x80482c8 in _init ()
(gdb) x/8x 0x8049470
0x8049470 <_global_offset_table_>:0x080494900x40013ed00x4000a9600x400fa550
0x8049480 <_global_offset_table_+16>: 0x080482ee0x400328cc0x0804830e0x00000000
(gdb) x/50x 0x40013ed0 ( * link_map类型)
0x40013ed0: 0x000000000x40010c270x080494900x400143e0
0x40013ee0: 0x000000000x400141000x000000000x08049490
0x40013ef0: 0x080494e00x080494d80x080494a80x080494b0
0x40013f00: 0x080494b80x000000000x000000000x00000000
0x40013f10: 0x080494c00x080494c80x080494980x080494a0
0x40013f20: 0x000000000x000000000x000000000x080494f8
0x40013f30: 0x080495000x080495080x080494e80x080494d0
0x40013f40: 0x000000000x080494f00x000000000x00000000
0x40013f50: 0x000000000x000000000x000000000x00000000
0x40013f60: 0x000000000x000000000x000000000x00000000
(gdb) disass 0x4000a960③
dump of assembler code for function _dl_runtime_resolve:
0x4000a960 <_dl_runtime_resolve>: push %eax
0x4000a961 <_dl_runtime_resolve+1>: push %ecx
0x4000a962 <_dl_runtime_resolve+2>: push %edx
0x4000a963 <_dl_runtime_resolve+3>: mov0x10(%esp,1),%edx
0x4000a967 <_dl_runtime_resolve+7>: mov0xc(%esp,1),%eax
0x4000a96b <_dl_runtime_resolve+11>:call 0x4000a740
? //调用真正的解析函数fixup(),修正got[6],使它指向真正的printf函数地址
0x4000a970 <_dl_runtime_resolve+16>:pop%edx
0x4000a971 <_dl_runtime_resolve+17>:pop%ecx
0x4000a972 <_dl_runtime_resolve+18>:xchg %eax,(%esp,1)
0x4000a975 <_dl_runtime_resolve+21>:ret$0x8//跳到printf函数地址执行
0x4000a978 <_dl_runtime_resolve+24>:nop
0x4000a979 <_dl_runtime_resolve+25>:lea0x0(%esi,1),%esi
end of assembler dump.
(gdb) b * 0x4000a972
breakpoint 4 at 0x4000a972: file dl-runtime.c, line 182.
(gdb) c
continuing.

breakpoint 4, 0x4000a972 in _dl_runtime_resolve () at dl-runtime.c:182
182 in dl-runtime.c
(gdb) i reg $eax $esp
eax0x4006804c 1074167884
esp0xbffffb64 -1073743004
(gdb) b *0x4000a975
breakpoint 5 at 0x4000a975: file dl-runtime.c, line 182.
(gdb) c
continuing.

breakpoint 5, 0x4000a975 in _dl_runtime_resolve () at dl-runtime.c:182
182 in dl-runtime.c
(gdb) si
printf (format=0x1
) at printf.c:26
26printf.c: no such file or directory.
(gdb) disass④⑵
dump of assembler code for function printf:
0x4006804c :push %ebp
0x4006804d :mov%esp,%ebp
0x4006804f :push %ebx
0x40068050 :call 0x40068055
0x40068055 :pop%ebx
0x40068056 : add$0xa2197,%ebx
0x4006805c : lea0xc(%ebp),%eax
0x4006805f : push %eax
0x40068060 : pushl0x8(%ebp)
0x40068063 : mov0x81c(%ebx),%eax
0x40068069 : pushl(%eax)
0x4006806b : call 0x400325b4
0x40068070 : mov0xfffffffc(%ebp),%ebx
0x40068073 : leave
0x40068074 : ret
end of assembler dump.
(gdb) x/8x 0x8049470
0x8049470 <_global_offset_table_>:0x080494900x40013ed00x4000a9600x400fa550
0x8049480 <_global_offset_table_+16>: 0x080482ee0x400328cc0x4006804c0x00000000

got[6]已经被修正为0x4006804c了

第一次调用printf()的时候需要经过①->②->③->④
以后调用printf()的时候就不需要这么复杂了,只要经过⑴->⑵就可以了

我们来看看到底是如何修正got[6]的,也是就说如何找到要修正的地址的
(以前我在这点理解上发生了一些比较大的误解,误导各位的地方还请包涵:) )

1:
进入plt4的时候 push $0x18 ,该$0x18为printf重定位入口在jmprel section中的偏移量
2:
printf重定位地址为jmprel+$0x18/* elf32_rel * reloc = jmprel + reloc_offset; */
(gdb) x/8x 0x8048278+0x18
0x8048290:0x080494880x000004070x53e589550x000000e8
0x80482a0 <_init+8>:0xc3815b000x000011cf0x001cbb830x74000000
typedef struct {
? elf32_addrr_offset;
? elf32_wordr_info;
? } elf32_rel;
也就是说printf重定位printf_retloc.r_offset=0x08049488;
? printf_retloc.r_info=0x00000407;
再看看0x08049488是什么地方
(gdb) x 0x08049488
0x8049488 <_global_offset_table_+24>: 0x4006804c
也就是got[6]
3:
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
对一个可执行文件 或一个共享目标而言,rel_addr就等于reloc->r_offset
所以rel_addr=0x08049488=got[6];
4:
*reloc_addr = value;
修正了rel_addr也就是got[6]
至于value是如何计算的,请参考下面的源代码

同时r_info又关联着一个符号
elf32_sym * sym = &symtab[ elf32_r_sym (reloc->r_info) ];
sym=0x8048158+0x00000407;
? typedef struct {
? elf32_wordst_name;
? elf32_addrst_value;
? elf32_wordst_size;
? unsigned charst_info;
? unsigned charst_other;
? elf32_halfst_shndx;
? } elf32_sym;
(gdb) x/10x 0x8048158+0x00000407
0x804855f:0x00003a000x000080000x000000000x00006900
0x804856f:0x000080000x000000000x000083000x00008000
0x804857f:0x000000000x0000b700

link_map结构说明如下:
/* structure describing a loaded shared object.the `l_next' and `l_prev'
?members form a chain of all the shared objects loaded at startup.

?these data structures exist in space used by the run-time dynamic linker;
?modifying them may have disastrous results.

?this data structure might change in future, if necessary.user-level
?programs must avoid defining objects of this type.*/


★★ glibc中动态解析符号的源代码(glibc 2.1.3的实现)

? .text
? .globl _dl_runtime_resolve
? .type _dl_runtime_resolve, @function
? .align 16
_dl_runtime_resolve:
? pushl %eax# preserve registers otherwise clobbered.
? pushl %ecx
? pushl %edx
? movl 16(%esp), %edx# copy args pushed by plt in register.note
? movl 12(%esp), %eax# that `fixup' takes its parameters in regs.
? call fixup# call resolver.
? popl %edx# get register content back.
? popl %ecx
? xchgl %eax, (%esp)# get %eax contents end store function address.
? ret $8# jump to function address.

static elfw(addr) __attribute__ ((unused))
fixup (
# ifdef elf_machine_runtime_fixup_args
? elf_machine_runtime_fixup_args,
# endif
?struct link_map *l, elfw(word) reloc_offset)
{
? const elfw(sym) *const symtab
? = (const void *) l->l_info[dt_symtab]->d_un.d_ptr;
? const char *strtab = (const void *) l->l_info[dt_strtab]->d_un.d_ptr;

? const pltrel *const reloc/*计算函数重定位人口*/
? = (const void *) (l->l_info[dt_jmprel]->d_un.d_ptr + reloc_offset);
? /*l->l_info[dt_jmprel]->d_un.d_ptr 为jmprel section的地址*/

? const elfw(sym) *sym = &symtab[elfw(r_sym) (reloc->r_info)];/*计算函数symtab入口*/
? void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);/*重定向符号的绝对地址*/
?
? elfw(addr) value;

? /* the use of `alloca' here looks ridiculous but it helps.the goal is
?to prevent the function from being inlined and thus optimized out.
?there is no official way to do this so we use this trick.gcc never
?inlines functions which use `alloca'.*/
? alloca (sizeof (int));

? /* sanity check that we're really looking at a plt relocation.*/
? assert (elfw(r_type)(reloc->r_info) == elf_machine_jmp_slot);/*健壮性检查*/

?/* look up the target symbol.*/
? switch (l->l_info[versymidx (dt_versym)] != null)
? {
? default:
? {
? const elfw(half) *vernum =
? (const void *) l->l_info[versymidx (dt_versym)]->d_un.d_ptr;
? elfw(half) ndx = vernum[elfw(r_sym) (reloc->r_info)];
? const struct r_found_version *version = &l->l_versions[ndx];

? if (version->hash != 0)
? {
? value = _dl_lookup_versioned_symbol(strtab + sym->st_name,
? &sym, l->l_scope, l->l_name,
? version, elf_machine_jmp_slot);
? break;
? }
? }
? case 0:
? value = _dl_lookup_symbol (strtab + sym->st_name, &sym, l->l_scope,
?l->l_name, elf_machine_jmp_slot);
? }
?/*此时value为object装载的基地址*/
? /* currently value contains the base load address of the object
?that defines sym.now add in the symbol offset.*/

? value = (sym ? value + sym->st_value : 0);/*函数的绝对地址*/

? /* and now perhaps the relocation addend.*/
? value = elf_machine_plt_value (l, reloc, value);/*可能还需要一下重定位*/

? /* finally, fix up the plt itself.*/
? elf_machine_fixup_plt (l, reloc, rel_addr, value);/*修正rel_addr,一般来说是got[n]*/

? return value;
}


static inline elf32_addr
elf_machine_plt_value (struct link_map *map, const elf32_rela *reloc,
?elf32_addr value)
{
? return value + reloc->r_addend;
}


/* fixup a plt entry to bounce directly to the function at value.*/
static inline void
elf_machine_fixup_plt (struct link_map *map, const elf32_rel *reloc,
?elf32_addr *reloc_addr, elf32_addr value)
{
? *reloc_addr = value;
}


参考资料:

1.glibc 2.1.3 src
2.<>
3.<> write by the grugq
4.linux动态链接技术
? http://www.linuxforum.net/forum/showflat.php?cat=&;board=kstudy&number=102793&page=1&view=collapsed&sb=5&o=31&part=
5.p58-0x04by nergal
? << the advanced return-into-lib(c) exploits >>



文章录入:晨光    责任编辑:晨光 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
    4000 2950 Etherchannel配置
    Summit Switch & Cisco Ethe
    交换机配置之Telnet方式
    EtherChannel不一致检测
    串口线引起的NOVELL网远程通
    你真的了解telnet吗
    使用cpanel轻松备份和恢复你
    用delphi实现读取foxmail的地
    不需要任何工具,远程屏蔽te
    telnet简介
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)

    局域网DIY】.版权所有 客服QQ:5820031 站长:晨光
    Copyright © 2000-2020 www.LANDIY.net All Rights Reserved
     


    粤ICP备05009256号