Chinaunix首页 | 论坛 | 博客
  • 博客访问: 59244
  • 博文数量: 27
  • 博客积分: 2000
  • 博客等级: 大尉
  • 技术积分: 300
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-24 17:31
文章分类
文章存档

2011年(1)

2010年(8)

2009年(18)

我的朋友

分类: LINUX

2009-06-28 11:01:06

===================================================================

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

1

进入PLT[4]的时候 执行指令 push $0x18 $0x18printf .rel.plt section中对应的重定位表项的偏移量。即reloc_offset=0x18

 

2* reloc 为重定位表项目

printf符号的重定位表项地址为JMPREL+$0x18    // elf32_Rel * reloc = JMPREL + reloc_offset

 

Dynamic Segment中记载着.rel.plt section的起始地址:JMPREL 0x8048278,供动态加载器重定位时使用。我们来看看在.rel.plt section中偏移量为0x18处的内容:(也可以用 readelf a test 直接查看 .rel.plt section 中各个表项的内容)

(gdb) x/8x 0x8048278+0x18

0x8048290: 0x08049488 0x00000407 0x53e58955 0x000000e8

0x80482a0 <_init+8>: 0xc3815b00 0x000011cf 0x001cbb83 0x74000000

 

 

也就是说printf对应的重定位表项内容为:

printf_retloc.r_offset=0x08049488;   # 0x08049488 是重定位得到printf的真实内存地址后,要修改填写的位置。

printf_retloc.r_info=0x00000407;     # 0x00000407 右移8位等4,表示printf .dynsym符号表中的索引值为4

# ELF32_R_SYM((0x00000407)>>8) = 2进制数100 0000 0111 右移8

= 2进制数100 = 4

再看看0x08049488是什么地方

(gdb) x 0x08049488

0x8049488 <_GLOBAL_OFFSET_TABLE_+24>: 0x4006804c    //0x08049488也就是GOT[6]的地址。

 

3:

void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);  # rel_addr: 重定位获取真实地址后,填写修改的目的地地址。对一个可执行文件而言,rel_addr=reloc->r_offset=0x08049488=GOT[6]

 

l代表link_map类型对象;其成员:l_addr;  /* Base address shared object is loaded at.  */

 

4

*reloc_addr = value;

修正了rel_addr也就等于修正GOT[6]

至于value是如何计算的,请参考后面的:glibc中动态解析符号的源代码(glibc 2.1.3的实现)

 

同时r_info 和动态符号表 .dynsym section 中的1个符号相关联:

elf32_Sym * sym = &SYMTAB[ elf32_R_SYM (reloc->r_info) ];

也即:sym = &SYMTAB[ elf32_R_SYM (0x00000407) ] = &SYMTAB[4]  // 对应动态符号表 .dynsym section的第5个表项

 

readelf a ./test 查看印证,符号printf 确实是.dynsym的第5项:

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.

重定位表 .rel.plt section中的每个entry的结构定义如下

typedef struct {

elf32_Addr r_offset;

elf32_Word r_info;

} elf32_Rel;

 

 

r_offset

此成员给出了重定位动作所修改的位置。对于一个可重定位文件而言,此值是从节区头部开始到将被重定位影响的存储单位之间的字节偏移。对于可执行文件或者共享目标文件而言,其取值是被重定位影响到的存储单元的虚拟地址。

r_info

此成员给出要进行重定位的符号对应的符号表索引,以及将实施的重定位类型。例如一个调用指令的重定位项将包含被调用函数的符号表索引。如果索引是 STN_UNDEF,那么重定位使用 0 作为符号值。重定位类型是和处理器相关的。当程序代码引用一个重定位项的重定位类型或者符号表索引,则表示对表项的 r_info 成员应用 ELF32_R_TYPE 或者 ELF32_R_SYM 的结果。

#define ELF32_R_SYM(i) ((i)>>8)

#define ELF32_R_TYPE(i) ((unsigned char)(i))

#define ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t))

 

.rel.plt section:存放需要重定位的项目。 变量/函数是一个符号,重定位涉及2方面信息。1。符号的信息记录在一个符号表里,2。符号所涉及的地址对应的节区。

.rel.plt section sh_link属性:符号在哪个符号表里 (比如.dynsym secion)   sh_info: 符号的地址所涉及的节区(.plt section)。需要重定位操作的节区的节区号。表示该节区的内容(变量/或函数符号地址)不明确,需要进行重定位操作。

.rel.plt的每个重定位项的属性:r_offset 表示符号重定位完毕,结果要修改填写到哪里。r_info 表示符号在符号表里的偏移量。

 

======================================================================

glibc的代码参考

1.

glibc-2.2.4-18.7.0.6 link_map的定义如下

/usr/include/link.h  link_map定义如下

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.  */

  };

 

glibc 2.1.3 link_map的定义有所不同。仅供参考。

 

 

2.

.dynamic 节区:如果一个目标文件参与动态链接,它的程序头部表将包含类型为 PT_DYNAMIC 的段。此“段”包含 .dynamic 节区。该节区采用一个特殊符号_DYNAMIC 来标记,其中包含下列结构的数组。

 

 typedef struct {

    Elf32_Sword d_tag;

    union {

        Elf32_Word  d_val;

        Elf32_Addr  d_ptr;

    } d_un;

 } Elf32_Dyn;

 

 extern Elf32_Dyn  _DYNAMIC[];

 

 

对每个这种类型的对象,d_tag 控制 d_un 的解释含义:DT_ dynamic type 的缩写

d_val : Elf32_Word 对象表示一个整数值,可以有多种解释。

d_ptr : Elf32_Addr 对象代表程序的虚拟地址。

 

 

动态数组标记:d_tag 类型表

名称     数值   d_un   可执行   共享目标  说明

DT_NULL     0   忽略   必需     必需      标记为 DT_NULL 的项目标注了整个 _DYNAMIC 数组的末端。

DT_STRTAB   5   d_ptr  必需     必需      此元素包含字符串表的地址,符号名、库名、和其他字符串都包含在此表中。

DT_SYMTAB   6   d_ptr  必需     必需      此元素包含符号表的地址。对 32 位的文件而言,这个符号表中的条目是 Elf32_Sym 类型。

readelf a ./test 可以查看:.dynamic section :

Dynamic segment at offset 0x53c contains 20 entries:

  Tag        Type                         Name/Value

 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

 0x0000000c (INIT)                       0x8048298

 0x0000000d (FINI)                       0x804841c

 0x00000004 (HASH)                       0x8048128

 0x00000005 (STRTAB)                     0x80481c8

 0x00000006 (SYMTAB)                     0x8048158    # 指向 .dynsym section

因此:symtab = (const void *) l->l_info[DT_SYMTAB]->d_un.d_ptr;   计算获得是.dynsym section 的首地址

 

 

 

★★★ glibc中动态解析符号的的<_dl_runtime_resolve>函数源代码(glibc 2.1.3的实现)

 

.text

.globl _dl_runtime_resolve       // 可以用 readelf -a /lib/ld-2.2.4.so 查看_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   #参数116(%esp)就是在PLT[4]push0x18

movl 12(%esp), %eax   # that fixup' takes its parameters in regs.  #参数212(%esp)就是在PLT[0]pushlGOT[1]

call fixup            # Call resolver.

popl %edx             # Get register content back.

popl %ecx

xchgl %eax, (%esp)    # Get %eax contents and store function address. #fixup解析出函数真实内存地址放在%eax

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)  // *l = GOT[1]; reloc_offset = 0x18

{

/*  *l 是一个link_map结构。l_info 拷贝了 .dynamic section内容;详见《Linux 动态链接机制研究及应用.pdf*/

const elfW(Sym) *const symtab = (const void *) l->l_info[DT_SYMTAB]->d_un.d_ptr; /* 计算获得.dynsym section首地址 */

const char *strtab = (const void *) l->l_info[DT_STRTAB]->d_un.d_ptr; /* 计算获得.dynstr section首地址 */

const PLTREL *const reloc = (const void *) (l->l_info[DT_JMPREL]->d_un.d_ptr + reloc_offset);  /* 计算获得的reloc printf符号在.rel.plt section 中对应的重定位表项  */

const elfW(Sym) *sym = &symtab[elfW(R_SYM) (reloc->r_info)];  /* 计算获得printf符号在.dynsym section中的对应的符号表项索引;R_SYM宏计算重定位表项对应的符号表项索引 */

void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);  /* rel_addr: 重定位获取真实地址后,填写修改的目的地地址。对一个可执行文件而言,rel_addr=reloc->r_offset=0x08049488=GOT[6] */

 

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); /*健壮性检查; R_TYPE宏计算重定位表项的重定位类型,.rel.plt重定位项的重定位类型都是R_386_JUMP_SLOT类型 */

 

/* Look up the target symbol. */

switch (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL)

{ /* DT_VERSYM 对应 .gnu.version section, 用readelf a ./test 可以找到此类 section,《elf规范》没有提及 */

/* .gnu.version 里的每一项都对应.dynsym 中的一个符号;每个表项的内容为:hash值、glibc版本或global/local绑定属性 */

 

default:

{

const elfW(Half) *vernum = (const void *) l->l_info[VERSYMIDX (DT_VERSYM)]->d_un.d_ptr;  /* 计算获得glibc版本号?? */

elfW(Half) ndx = vernum[elfW(R_SYM) (reloc->r_info)];    /* 计算获得printf符号在.gnu.version中对应hash表项的索引ndx,此表项放着printf符号对应的glibc版本号?? R_SYM宏计算需要重定位的符号所对应的符号表项索引*/

const struct r_found_version *version = &l->l_versions[ndx];  /* 计算获得printf符号对应的glibc版本号?? */

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);   /* 真正的符号地址数值解析函数在此!value是重定位出printf的最终内存地址。最终将保存到rel_addr里面 */

break;

}

}

 

case 0:

value = _dl_lookup_symbol (strtab + sym->st_name, &sym, l->l_scope, l->l_name, elf_MACHINE_JMP_SLOT);   /* 真正的符号地址数值解析函数在此! */

}

/*此时valueobject装载内存的基地址*/

/* 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,一般来说rel_addr=GOT[N]*/

 

return value;  /* value最终等于printf的真实内存地址,返回时存放在%eax */

}

 

 

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;

}

 

 

# 上面可以看到 fixup还不是最终的 符号解析函数,应该是_dl_lookup_symbol_dl_lookup_versioned_symbol,现在我们来看看这2个函数在哪个文件里:

 

# grep -r -s dl_lookup_symbol /usr/lib

Binary file /usr/lib/libc.a matches

Binary file /usr/lib/libc_p.a matches

 

# grep -r -s dl_lookup_symbol /lib

Binary file /lib/ld-linux.so.2 matches

Binary file /lib/libc.so.6 matches

Binary file /lib/ld-2.2.4.so matches

Binary file /lib/libc-2.2.4.so matches

 

# ls -l /lib/ld*

-rwxr-xr-x    1 root     root       457261 Aug  8  2002 /lib/ld-2.2.4.so

lrwxrwxrwx    1 root     root           11 Oct 23  2002 /lib/ld-linux.so.2 -> ld-2.2.4.so

 

# ldd ./test

        libc.so.6 => /lib/libc.so.6 (0x40025000)

        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

 

# readelf -a /lib/libc.so.6|grep dl_lookup_symbol

  001315c4  62f07 R_386_JUMP_SLOT       00000000  _dl_lookup_symbol_skip  

  0013169c  70507 R_386_JUMP_SLOT       00000000  _dl_lookup_symbol       

  1583: 00000000   634 FUNC    GLOBAL DEFAULT  UND _dl_lookup_symbol_skip@GLIBC_2.0 (13)

  1797: 00000000  1347 FUNC    GLOBAL DEFAULT  UND _dl_lookup_symbol@GLIBC_2.0 (13)

  4831: 00000000   634 FUNC    GLOBAL DEFAULT  UND _dl_lookup_symbol_skip@@GLIBC_2.0

 

// UNDEFINE 表示_dl_lookup_symbol符号不是在libc.so.6内部定义的

 

# readelf -a /lib/ld-2.2.4.so |grep dl_look_symbol

  00015a3c  03c07 R_386_JUMP_SLOT       00008254  _dl_lookup_symbol       

    48: 00008798   634 FUNC    GLOBAL DEFAULT   10 _dl_lookup_symbol_skip@@GLIBC_2.0

    60: 00008254  1347 FUNC    GLOBAL DEFAULT   10 _dl_lookup_symbol@@GLIBC_2.0

   323: 00008798   634 FUNC    GLOBAL DEFAULT   10 _dl_lookup_symbol_skip

 

// GLOBAL 表示_dl_lookup_symbol符号定义在ld-2.2.4.so里,是个全局符号。

 

# ldd /lib/libc.so.6

        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

# ldd /lib/ld-2.2.4.so

        statically linked     # 动态连接器是静态连接的,无需依赖其他任何库文件。

# gdb -q /lib/ld-2.2.4.so

(gdb) disass _dl_lookup_symbol  # 此函数 位于 /lib/ld-2.2.4.so里面,大概有2000行汇编代码。

 

# readelf -a /lib/ld-2.2.4.so |grep _dl_lookup_versioned_symbol

 

 

参考资料:

 

1.glibc 2.1.3 src

2.<文件格式>>

3.<> write by the grugq

4.Linux动态链接技术

5.p58-0x04 by Nergal

<< The advanced return-into-lib(c) exploits >>

 

 

WSS(Whitecell Security Systems),一个非营利性民间技术组织,致力于各种系统安全技术的研究。坚持传统的hacker精神,追求技术的精纯。

WSS 主页:

WSS 论坛:forum/

 

 
 
 
 
阅读(2233) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~