elf文件格式_初识ELF格式文件(下)
“前言:初学逆向 请多多指教”为了加深ELF文件格式,自己用C来实现ELF文件结构的解析自己写的代码大家可以参考一下,地址:https://github.com/adezz/ParseElfELF Header—typedef struct elf32_hdr{unsigned chare_ident[EI_NIDENT];Elf32_Halfe_type;E...
“ 前言:初学逆向 请多多指教”
为了加深ELF文件格式,自己用C来实现ELF文件结构的解析
自己写的代码大家可以参考一下,地址:
https://github.com/adezz/ParseElf
ELF Header
—
typedef struct elf32_hdr{ unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; /* Entry point */ Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx;} Elf32_Ehdr;
通过如下代码将硬盘上的so文件加载进内存:
// 文件读取到内存void MyReadFile(PVOID* pSoFile){ DWORD dwSoBufferSize = 0; fstream fs(soFileName, ios::in | ios::binary); if(fs.fail()) return; fs.seekg(0, ios::end); int filesize = fs.tellg(); fs.seekg(0, ios::beg); *pSoFile = new byte[filesize]; fs.read((char*)*pSoFile, filesize); fs.close();}
有了上面关于ELF header头的结构体,通过结构体指针就很方便进行打印了
void getElfHeader(PVOID pSoFile){ elf32_hdr* pElf32_hdr = NULL; pElf32_hdr = (elf32_hdr*)pSoFile; printf("Magic: "); for (DWORD i = 0;i<16;i++){ printf("%02X ", *(PBYTE)&pElf32_hdr->e_ident[i]); } printf("\n"); printf("e_type: %x\n", pElf32_hdr->e_type); printf("e_machine: %x\n", pElf32_hdr->e_machine); printf("e_version: %x\n", pElf32_hdr->e_version); printf("e_entry: %x\n", pElf32_hdr->e_entry); printf("e_phoff: %x\n", pElf32_hdr->e_phoff); printf("e_shoff: %x\n", pElf32_hdr->e_shoff); printf("e_flags: %x\n", pElf32_hdr->e_flags); printf("e_ehsize: %x\n", pElf32_hdr->e_ehsize); printf("e_phentsize: %x\n", pElf32_hdr->e_phentsize); printf("e_phnum: %x\n", pElf32_hdr->e_phnum); printf("e_shentsize: %x\n", pElf32_hdr->e_shentsize); printf("e_shnum: %x\n", pElf32_hdr->e_shnum); printf("e_shstrndx: %x\n", pElf32_hdr->e_shstrndx);}
Section Header 符号表
—
.symtab中还保存了可执行文件的本地符号,如全局变量,或者代码中定义的本地函数 等。因此, .symtab保存了所有的符号,而.dynsym只保存动态/全局符号。 .dynsym符号表对于动态链接可执行文件的执行来说是必需的,而.symtab符号表只是用来进行调试和链接的,有时候为了节省空间,会将.symtab符号表从生产二进制文件中删掉。readelf.exe -S libhello-jni.so
发现也只能看到.dynsym符号表
如下命令,打印的就是.dynsym符号表中的内容
readelf.exe -s libhello-jni.so
先解析符号表的位置,先知道Section Headers中有许多个section header的结构体,而符号表就是其中的一个,那么就需要先知道section headers的首地址,然后通过我之前发的几篇文章可以知道,每个section header的第一个成员sh_name则是说明相关节的名称,然后遍历每个section header来进行判断哪个是符号表就可以了
这里还需要注意一个问题,section header的sh_name是一个结构体,其中有个偏移值是指向的是.shstrtab表中的地址,该地址有对应的section header的名称,所以这里也需要先找到.shstrtab节的地址
那么先找到Section Headers的首地址,通过Elf Header中找到相关的偏移地址,也就是e_phoff成员,通过上面的代码可以看到如下
Section Header的结构体如下:
typedef struct elf32_shdr { Elf32_Word sh_name; // 节区名称,如果是SHN_UNDEF标记未定义的 Elf32_Word sh_type; // 节区的内容和语义进行分类 Elf32_Word sh_flags; // 节区支持 1 位形式的标志,这些标志描述了多种属性。 Elf32_Addr sh_addr; // 如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应处的位置。 Elf32_Off sh_offset; // 偏移地址,也就是基地址+偏移地址指向的地址就是实际当前节区 Elf32_Word sh_size; // 当前实际节区的长度(字节数) Elf32_Word sh_link; // 节区头部表索引链接 Elf32_Word sh_info; // 附加信息 Elf32_Word sh_addralign; // 对齐约束 Elf32_Word sh_entsize; // 当前节区中每个成员的固定大小字节,如果没用固定则默认为0} Elf32_Shdr;
获取每个节的sh_name的代码:
elf32_hdr* pElf32_hdr = NULL; elf32_shdr* pElf32_shdr = NULL; elf32_shdr* pElf32_shdr_shstrtab = NULL; elf32_shdr* pElf32_shdr_dynsym = NULL; elf32_sym* pElf32_dynsym = NULL; DWORD dwCount; pElf32_hdr = (elf32_hdr*)pSoFile; pElf32_shdr = (elf32_shdr*)((DWORD)pSoFile + (DWORD)pElf32_hdr->e_shoff); pElf32_shdr_shstrtab = (elf32_shdr*)&pElf32_shdr[pElf32_hdr->e_shnum - 1]; for (DWORD i = 0; ie_shnum; i++){ printf("%s \n", (char*)((DWORD)pSoFile + (DWORD)pElf32_shdr_shstrtab->sh_offset + pElf32_shdr[i].sh_name)); }
到这里之后就开始解析符号表的内容了,关于符号表的结构体如下
typedef struct elf32_sym{ Elf32_Word st_name; //符号表项名称。如果该值非0,则表示符号名的字符串表索引(offset),否则符号表项没有名称。 Elf32_Addr st_value; //符号的取值。依赖于具体的上下文,可能是一个绝对值、一个地址等等。 Elf32_Word st_size; //符号的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。 unsigned char st_info; //符号的类型和绑定属性。 unsigned char st_other; //未定义。 Elf32_Half st_shndx; //每个符号表项都以和其他节区的关系的方式给出定义。此成员给出相关的节区头部表索引。} Elf32_Sym;
可以发现,这样的结构体不止是只有一个,每个结构体都是如上的结构,并且
第一个成员st_name中是符号表项的名称,如果想要得到相关符号表的名称的话就需要先拿到第一个成员st_name的name off的偏移,然后再去.dynstr这个表中的首地址加偏移值获得相关符号表的名称
那也就是如果想要完全解析符号表,.dynstr和.dynsym这两个表缺一不可
解析符号表索引找到对应的字符串的实现代码:
void getSectionHeaders_dynsym(PVOID pSoFile){ elf32_hdr* pElf32_hdr = NULL; elf32_shdr* pElf32_shdr = NULL; elf32_shdr* pElf32_shdr_shstrtab = NULL; elf32_shdr* pElf32_shdr_dynsym = NULL; elf32_sym* pElf32_dynsym = NULL; PVOID pElf32_dymstr = NULL; DWORD dwIndexDynstr = 0; DWORD dwCount = 0; pElf32_hdr = (elf32_hdr*)pSoFile; pElf32_shdr = (elf32_shdr*)((DWORD)pSoFile + (DWORD)pElf32_hdr->e_shoff); pElf32_shdr_shstrtab = (elf32_shdr*)&pElf32_shdr[pElf32_hdr->e_shnum - 1]; for (DWORD i = 0; ie_shnum; i++){ // printf("%s \n", (char*)((DWORD)pSoFile + (DWORD)pElf32_shdr_shstrtab->sh_offset + pElf32_shdr[i].sh_name)); if (strcmp((char*)((DWORD)pSoFile + (DWORD)pElf32_shdr_shstrtab->sh_offset + pElf32_shdr[i].sh_name), ".dynsym") == 0){ dwCount = pElf32_shdr[i].sh_size / pElf32_shdr[i].sh_entsize; //dwCount表示符号表dynsym中的项目数量 printf("Symbol table '.dynsym' contains %d entries \n", dwCount); //还得获取符号表字符串.synstr的表的首地址 for (DWORD m = 0; me_shnum; m++){ if (strcmp((char*)((DWORD)pSoFile + (DWORD)pElf32_shdr_shstrtab->sh_offset + pElf32_shdr[m].sh_name), ".dynstr") == 0){ dwIndexDynstr = m; break; } } pElf32_dymstr = (PVOID)((DWORD)pSoFile + (DWORD)pElf32_shdr[dwIndexDynstr].sh_offset); //确认符号表字符串获取成功 for (DWORD j = 0; j < dwCount; j++){ pElf32_dynsym = (elf32_sym*)((DWORD)pSoFile + pElf32_shdr[i].sh_offset); //printf("%x\n", pElf32_dynsym[j].st_name); printf("%s \n", (PCHAR)((DWORD)pElf32_dymstr + (DWORD)pElf32_dynsym[j].st_name)); } break; } } }
到这里,其实Section Headers都应该了解了,其他的section header解析跟这个都一样的,并且比这个还简单,因为其他表的解析都是单一的!
Program Headers
—
接下来继续看Program Headers
如何拿到Program Headers的首地址,这个跟上面Section Headers一样的,都是从Elf Header中的成员获取的,这里是e_phoff成员
typedef struct { Elf32_Word p_type; // 描述的当前段的类型 Elf32_Off p_offset; // 当前段区的偏移地址 Elf32_Addr p_vaddr; // 虚拟地址 Elf32_Addr p_paddr; // 物理地址 Elf32_Word p_filesz;// 在文件映像中所占的字节数 Elf32_Word p_memsz; // 在内存映像中占用的字节数 Elf32_Word p_flags; // 段相关的标志 Elf32_Word p_align; // 文件对齐相关约定} Elf32_phdr;
p_type的类型有许多,这里如果想要进一步识别的,可以通过switch来进行分支判断,自己这里就不实现了,单纯的解析下字段就好了
代码实现:
void getProgramHeader(PVOID pSoFile){ elf32_hdr* pElf32_hdr = NULL; elf32_phdr* pElf32_phdr = NULL; pElf32_hdr = (elf32_hdr*)pSoFile; pElf32_phdr = (elf32_phdr*)((DWORD)pSoFile + (DWORD)pElf32_hdr->e_phoff); for (DWORD i = 0; i < pElf32_hdr->e_phnum; i++){ printf("p_type: 0x%x\n", pElf32_phdr[i].p_type); printf("p_offset: 0x%x\n", pElf32_phdr[i].p_offset); printf("p_vaddr: 0x%x\n", pElf32_phdr[i].p_vaddr); printf("p_paddr: 0x%x\n", pElf32_phdr[i].p_paddr); printf("p_filesz: 0x%x\n", pElf32_phdr[i].p_filesz); printf("p_memsz: 0x%x\n", pElf32_phdr[i].p_memsz); printf("p_flags: 0x%x\n", pElf32_phdr[i].p_flags); printf("p_filesz: 0x%x\n", pElf32_phdr[i].p_filesz); printf("\n"); }}
最后实现的代码中还可以参考一下实现的新增节的玩法!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)