elf文件格式细节记录

更新时间:2023-06-29 19:41:15 阅读: 评论:0

elf⽂件格式细节记录
ELF⽂件是Linux系统中发明的很重要的⽂件,Linux系统中的可执⾏⽂件、Object⽂件、动态库⽂件都是ELF格式⽂件,它的地位就相当于Windows中的PE格式⽂件,不过整体上ELF⽂件⽐PE⽂件要设计地精简。ELF⽂件中⼤致分为⽂件头、段头表、结头表,剩下的就是段和结所指向的数据。
⼀. elf⽂件头
/* ELF Header */
typedef struct elfhdr {
unsigned char e_ident[EI_NIDENT]; /* ELF Identification */
Elf32_Half e_type;  /* object file type */
Elf32_Half e_machine; /* machine */
Elf32_Word e_version; /* object file version */
Elf32_Addr e_entry; /* virtual entry point */
Elf32_Off e_phoff; /* program header table offt */
Elf32_Off e_shoff; /* ction header table offt */
Elf32_Word e_flags; /* processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size */
Elf32_Half e_phentsize; /* program header entry size */
Elf32_Half e_phnum; /* number of program header entries */
Elf32_Half e_shentsize; /* ction header entry size */
Elf32_Half e_shnum; /* number of ction header entries */
Elf32_Half e_shstrndx; /* ction header table's "ction
header string table" entry offt */
} Elf32_Ehdr;
⽂件头⾥⾯记录着elf⽂件类型(可执⾏⽂件、动态库⽂件、*.o 被链接加载的⽂件)、是否arm、X86、程序头和节头信息。对于可执⾏⽂件和动态库⽂件节表可选,程序表是⼀定有,*.o 被链接加载的⽂件是程序头表可选,节头表必须有(如下图⼀)。在对安卓native函数做处理时⽤到的都是elf格式的动态库so⽂件,它⾥⾯是程序头表必有,节头表可选(如下图⼆、图三),下⾯讨论的是针对这种情形。
什么时候有流星
⼆. elf⽂件的程序头表
海蛎子豆腐汤
对于可执⾏⽂件和动态库⽂件,如果把节表信息抹掉,该⽂件还是有效的,⽽如果将程序头表信息抹掉,则⽂件会失效(注意:这⾥说的抹掉,只是抹掉表的信息,表指向的内容并没有动)。⼀般elf⽂件的程序头表中会有以下类型表:
⽅框中四种类型的程序头:Interpreter Path、(R_X)Loadable Segment、(RW_)Loadable Segment、Dynamic Segment。
Interpreter Path段记录的是链接器linker的路径,这个在可执⾏elf⽂件⽂件才有,在so⽂件中不会有,因为可执⾏⽂件中⼀般都是会调⽤其他的动态库的,那么这个时候系统就需要在将执⾏位置转到可执⾏⽂件的⼊⼝地址前,先将各种动态库加载到内存,这个操作是有linker来完成的,之后才将执⾏位置转到可执⾏⽂件的⼊⼝地址。
Loadable Segment指向的位置是需要加载到内存中的,两个Loadable Segment段,分别是可读可执
⾏权限和可读可写权限。这两个段的作⽤和PE⽂件中节的作⽤是⼀样的,都是会记录内存虚拟地址偏移,⽂件偏移的,通过虚拟地址计算⽂件偏移时对所有的 Loadable Segment 中虚拟地址范围计算即可。其他程序头(⾮ Loadable Segment)中记录的⽂件偏移是可以抹掉的,因为系统在加载⽂件之后会通过虚拟地址来计算出⽂件偏移,⽽不是直接⽤程序头中的⽂件偏移。当然,如果抹掉 Loadable Segment中⽂件偏移就会使⽂件执⾏失败。(如果想尝试,通过010⼯具改动对应值,然后尝试即可,010中有⾃带elf⽂件格式解析模板,很快就能找到相应位置。)
Dynamic Segment 是很重要的⼀个程序头,⾥⾯存储着函数名、使⽤过的动态库名、重定位表、函数代码偏移等重要信息,不过不是直接记录,⽽是通过⼀定的⽅法查询得到,这个查询过程是elf设计中巧妙且关键的核⼼所在。Dynamic Segment指向的⽂件内容是以下结构体的对象数组:
/* Dynamic structure */
typedef struct {
Elf32_Sword d_tag;  /* controls meaning of d_val */
union {
Elf32_Word d_val; /* Multiple meanings - e d_tag */
Elf32_Addr d_ptr; /* program virtual address */
} d_un;
} Elf32_Dyn;
/*d_tag 的取值*/
/* Dynamic Array Tags - d_tag */
#define DT_NULL  0  /* marks end of _DYNAMIC array */
#define DT_NEEDED 1  /* string table offt of needed lib */
#define DT_PLTRELSZ 2  /* size of relocation entries in PLT */
#define DT_PLTGOT 3  /* address PLT/GOT */
#define DT_HASH  4  /* address of symbol hash table */
#define DT_STRTAB 5  /* address of string table */
#define DT_SYMTAB 6  /* address of symbol table */
#define DT_RELA  7  /* address of relocation table */
#define DT_RELASZ 8  /* size of relocation table */
#define DT_RELAENT 9  /* size of relocation entry */
#define DT_STRSZ 10  /* size of string table */
#define DT_SYMENT 11  /* size of symbol table entry */
#define DT_INIT  12  /* address of initialization func. */
#define DT_FINI  13  /* address of termination function */
#define DT_SONAME 14  /* string table offt of shared obj */
#define DT_RPATH 15  /* string table offt of library
金钩如意草arch path */
#define DT_SYMBOLIC 16  /* start sym arch in shared obj. */
#define DT_REL  17  /* address of rel. tbl. w addends */
#define DT_RELSZ 18  /* size of DT_REL relocation table */
#define DT_RELENT 19  /* size of DT_REL relocation entry */
#define DT_PLTREL 20  /* PLT referenced relocation entry */
#define DT_DEBUG 21  /* bugger */
#define DT_TEXTREL 22  /* Allow rel. mod. to unwritable g */
#define DT_JMPREL 23  /* add. of PLT's relocation entries */
#define DT_BIND_NOW 24  /* Bind now regardless of env tting */
#define DT_NUM  25  /* Number ud. */
#define DT_LOPROC 0x70000000 /* rerved range for processor */
单位工作证明范本
#define DT_HIPROC 0x7fffffff /*  specific dynamic array tags */
对于以上d_tag,有的记录的是⼀块区域,对应的d_un取d_ptr的值,有的是记录某⼀块区域的⼤⼩,如:DT_STRSZ,则对应的d_un 取d_val的值。
安卓中对so中native函数加密时,需要通过native函数名知道该native函数名对应的函数代码所在位置以及代码所占字节数,这就得需要通过Dynamic Segment来查找的,通过节表中动态符号表也是可以查询到,但节表可以抹掉,所以最好通过Dynamic Segment查找native函数的函数体。函数名在elf中是⼀个动态符号,DT_SYMTAB对应的偏移处记录着所有的动态符号对应的信息,是⼀个同结构体的数组,想要查找的函数名信息就在这个数组中,现在需要知道的是查找的函数名所对应的数组下标,步骤如下:1. 通过Dynamic Segment 找到DT_STRTAB、DT_SYMTAB、DT_HASH对应的表信息,分别记录着动态符号名称字符串、符号表信息(符号对应的代码偏移和字节数就在这个表中)、哈希表;2. 通过函数
unsigned int elf_hash(const char *_name)
{
const unsigned char *name = (const unsigned char *)_name;
unsigned h = 0, g;
while (*name) {
h = (h << 4) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
计算出函数名对应的哈希值;3. 通过上⼀步中的哈希值在哈希表中查找对应符号在符号表中下标,⽅法为: 给定⼀个符号名字,返回⼀个哈希值 x,然后由 bucket[x%nbucket] 得到⼀个符号表索引 y,如果索引 y 对应的符号表项不是想要的符号(通过符号表项对应符号名和给定符号名⽐对就⾏),则
由 chain[y] 得到下⼀个符号表索引 z,如果仍不是想要的符号,继续 chain[z]…,直到匹配到,或者最后得出下标是0,则说明该符号不存在。bucket、nbucket、chain参考哈希表结构:
要红包的图片可参考以下代码,另外解析elf的⼯程也会给出下载链接。
int ElfFile32::getTargetFuncInfo(const char *funcName, funcInfo32 *info) {
char flag = -1, *dynstr;
int i;
Elf32_Off dyn_vaddr;
Elf32_Word dyn_size, dyn_strsz;
Elf32_Dyn *dyn;
Elf32_Addr dyn_symtab, dyn_strtab, dyn_hash;
Elf32_Sym *funSym;
unsigned funHash, nbucket;
unsigned *bucket, *chain;
int mod;
Elf32_Phdr* phdr = pProgmHdr;
//    __android_log_print(ANDROID_LOG_INFO, "JNITag", "phdr =  0x%p, size = 0x%x\n", phdr, ehdr->e_phnum);
for (i = 0; i < m_dwPhNum; ++i) {
//  __android_log_print(ANDROID_LOG_INFO, "JNITag", "phdr =  0x%p\n", phdr);
if (phdr->p_type == PT_DYNAMIC) {
flag = 0;
printf("Find .dynamic gment");
break;
}
phdr++;
}
if (flag)
return -1;
int nOfft = phdr->p_offt;
nOfft = Rva2Fa(phdr->p_vaddr);
dyn_vaddr = (Elf32_Addr)(nOfft + baAddr);
dyn_vaddr = (Elf32_Addr)(nOfft + baAddr);音成语
dyn_size = phdr->p_filesz;
flag = 0;
for (dyn = (Elf32_Dyn*)dyn_vaddr; (Elf32_Addr)dyn < dyn_vaddr + dyn_size; dyn++){
if (dyn->d_tag == DT_HASH){
flag += 1;
dyn_hash = dyn->d_un.d_ptr;
}
el if (dyn->d_tag == DT_STRTAB){
flag += 2;
dyn_strtab = dyn->d_un.d_ptr;
}
el if (dyn->d_tag == DT_SYMTAB){
flag += 4;
dyn_symtab = dyn->d_un.d_ptr;
}
el if (dyn->d_tag == DT_STRSZ) {
flag += 8;
dyn_strsz = dyn->d_un.d_val;
}
}
if (flag & 0x0f != 0x0f){
printf("Find needed .ction failed\n");
return -1;
}
dyn_hash = Rva2Fa(dyn_hash) + (Elf32_Addr)baAddr;
dyn_strtab = Rva2Fa(dyn_strtab) + (Elf32_Addr)baAddr;
dyn_symtab = Rva2Fa(dyn_symtab) + (Elf32_Addr)baAddr;
funHash = elf_hash(funcName);
funSym = (Elf32_Sym *)dyn_symtab;
dynstr = (char*)dyn_strtab;
nbucket = *(unsigned*)dyn_hash;
bucket = (unsigned*)(dyn_hash + 8);
chain = bucket + nbucket;
flag = -1;
mod = (funHash % nbucket);我的拼图
for (int i = bucket[mod]; i != 0; i = chain[i]){
if (strcmp(funSym[i].st_name + dynstr, funcName) == 0) {
flag = 0;
break;
}
}
if (flag != 0) {
return -1;
}
info->st_value = funSym[i].st_value;
info->st_size = funSym[i].st_size;
return 0;
}
三. elf⽂件的节头表
elf格式的可执⾏程序⽂件和动态库⽂件⾥⾯节头表是可选的,即抹掉之后也不影响系统对该⽂件的执⾏,但是节头表中对应的内容还是存在的,并且在elf的程序头表中指向的内容或者查找的内容在节表中都会有记录,例如:在上⼀节中通过Dynamic Segment找到
DT_STRTAB、DT_SYMTAB、DT_HASH对应的表的内容分别对应节表中.dynstr节、.dynsym节、.hash节,如下图⼀(elf⽂件中获取的节表中偏移)、图⼆(动态解析出的对应DT_HASH、DT_STRTAB、DT_SYMTAB的内容的在⽂件中偏移)。
节表中常见的符串的节:.dynstr、.shdrstr,前者是动态符号字符串所在的节,后者是节头表名称所在的节,.shdrstr所在的节在整个节表数组中下标记录在⽂件头中。
四.可执⾏⽂件和共享库⽂件中动态符号
ELF⽂件中动态符号包含导⼊函数名和导出函数名等。elf⽂件的函数内容都存储在.text节中。可执⾏⽂件的函数默认不导出,函数执⾏时,直接通过elf⼊⼝地址执⾏,通过动态符号可解析的函数也⽐较少(带有调试信息的可执⾏⽂件的导出表中也包含开发者写的函数名,但⽤解析动态符号的⽅式没有获取对应函数名的函数体)。共享库的函数默认导出,导出函数的函数体可通过查找动态符号获取。
鲁智深的外号
获取elf⽂件信息的⼯具有、⽂件等,这些⽂件存在于ndk⽬录中,如 ..\ndk-
bundle\toolchains\llvm\prebuilt\windows-x86_64\bin⽬录下的 、、、。功能是解析elf⽂件格式,各个版本之间课通⽤,各版本如果涉及到反编译,必须使⽤兼容版本(arm-64可兼容arm-32,x86-64可兼容x86)。
#获取所有信息
readelf -a elf-file
#获取段信息
readelf -l elf-file
#获取节信息
readelf -S elf-file
#获取动态符号
readelf -s elf-file
#获取反编译代码
objdump -S elf-file
#获取所有节的内容
objdump -s elf-file
#获取所有动态符号
objdump -T elf-file
#更过命令,可查看readelf和objdump帮助⽂档

本文发布于:2023-06-29 19:41:15,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1060534.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:函数   符号   对应   动态   信息
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图