一步一步走进Linux HOOK API(七)
我又来啦,今天是本系列介绍ELF文件的最后一篇教程.跟随大家一起了解了ELF文件的大致结构.整个结构其实是很明朗的,根据ELF头,程序头,节头.从节头里提取不同的类型,到不同的节表内去获取不同的信息,今天这里主要介绍重定位表.也是常用的节表之一.
有了符号名和动态库的名字操作系统就可以为我们引入函数了,但在我们的程序中是谁会用这些外部函数呢,系统解析出来的函数地址应该给谁呢?这就是重定位表的功劳了!
重定位表的类型有两种SHT_REL和SHT_RELA,我们只谈SHT_REL.
typedef struct { Elf32_Addr r_offset; /* Address */ Elf32_Word r_info; /* Relocation type and symbol index */ } Elf32_Rel;
重定位表其实很简单,它的r_offset成员给出了需要重定位内容的地址,而它的r_info字段给出了两条信息,一条是与此重定位内容相关的符号,一条是重定位的类型,在elf.h中分别有
#define ELF32_R_SYM(val) ((val) >> 8) #define ELF32_R_TYPE(val) ((val) & 0xff) #define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff))
从成员r_info中获取符号信息和重定位信息,符号信息就是一个符号表的索引,32位下占用这个成员的高24位,剩余的8位就是重定位类型了.符号信息就是一个符号表中的索引.
在i386上从外部引入的动态函数重定位类型是R_386_JMP_SLOT.由这个类型的名称可以看出动态连接在Linux上是处理器密切相关的东西.刚说r_info字段包含了一个符号表中的索引,对于从外部引入的动态函数来说那个符号表就是“动态符号表”,它在节头结构中的类型值为SHT_DYNSYM。r_offset字段是一个地址,加载之初被r_offset指向的内容——也就是一个外部符号的地址——并不正确,操作系统就根据重定位信息引用的符号找到那个地址,然后修改r_offset所指向的内容。这部分我们在第五讲中已经得到证实,发现GOT表内的地址并不是真正的函数地址入口.这就是因为linux的"懒模式"机制.
下面给出代码:
#include "readRel.h" void display_rel(Elf32_Ehdr *ehdr,Elf32_Shdr *shdr) { Elf32_Rel *dyn = (Elf32_Rel *)((char*)ehdr + shdr->sh_offset); int relSize = shdr->sh_size / shdr->sh_entsize; char *symName = (char*)(((Elf32_Shdr *)((char*)ehdr + ehdr->e_shoff + shdr->sh_link * sizeof(Elf32_Shdr)))->sh_offset + (char*)ehdr); printf("rel = 0x%x\n",(char*)dyn); printf("relSize = 0x%x\n",(char*)relSize); printf("symName = 0x%x\n",(char*)symName); printf("Relocation section '.rel.dyn' at offset 0x%x contains %d entries:\n",dyn,relSize); int i = 0; printf("%-10s%-10s%-10s%s\n","Offset","Info","Type","Sym.Name"); for(i = 0;i < relSize;i++){ printf("%-10x",dyn->r_offset); printf("%-10x",dyn->r_info); printf("%-10d",ELF32_R_TYPE(dyn->r_info)); printf("%d",ELF32_R_SYM(dyn->r_info)); //注意此处并非字符串的名字,而是指向符号表的索引 printf("\n"); dyn++; } } void displayRel(Elf32_Ehdr *ehdr,Elf32_Shdr *shdr) { int py = ehdr->e_shstrndx * sizeof(Elf32_Shdr); Elf32_Shdr *symtab = (Elf32_Shdr *)((char*)shdr + py); char *szShdrName = (char*)(symtab->sh_offset + (char*)ehdr); int i = 0; for(i = 0; i < ehdr->e_shnum; i++){ if(shdr->sh_type == SHT_REL){ display_rel(ehdr,shdr); } shdr++; } }
前面我们讲了,怎么手动通过GDB拦截printf参数,下一节,我们将通过程序来拦截printf的参数,使之永远都输入我们给定的字符串.
尽情期待...谢谢~~~~
我也是菜鸟.一起努力学习.
再见