阅读目录
【Android】ELF格式及动态加载过程
发布于:2017-7-14 09:48 | 148114次阅读 作者: 管理员 | 原作者: TP | 来自: 原创
目的 外网出现通过抹除ELF静态视图属性进行加固SO,本文对linker动态加载进行分析整理,方便更好的理解ELF文件格式及加载过程。
知识小纲 1、 ELF文件 2、 静态视图 a) 节区头 b) STRTAB节 c) DYNSYM节 d) REL节 e) DYNAMIC节 3、 动态视图 a) 程序表 b) 段说明 4、 linke动态加载 a) 装载 b) 链接 5、 小结
ELF文件 ELF文件是可执行链接格式(Executable and Linking Format)文件的简称,TIS(Tool Interface Standards)有相应的标准说明。我们常接触的ELF文件有三类,可以简单理解为*.o之类的可重定位文件、*.so之类的共享目标文件,以及可执行文件。图1给出了ELF文件的格式描述图(全篇绘图均以32位为例)。 从图中,可以看出一个ELF文件,包含了ELF头、程序头表、节区、节头表四部分。其中ELF头(包含两个表位置)和包含了ELF文件核心内容的节区是必不可少的,程序头表和节头表根据使用场景可选。 静态视图的使用场景有如IDA、readelf等工具查看ELF文件,这类场景通过SHT(Section Header Table)得到ELF文件布局,解析出各节内容来展示给用户。 动态视图的使用场景有如linker(后续用linker统一代表所有解析器)加载ELF文件,这类场景通过PHT(Program Header Table)得到ELF文件布局,解析出动态加载需要的内容来使用。 通过上面的描述可知SHT和PHT中包含的内容是有区分的。PHT中包含了动态加载的内容(该加载哪些东西进内存等)索引,SHT包含的是静态的ELF文件内容展示。相对来说,静态视图包含的内容为全量丰富内容,而动态视图只包含linker加载需要的。 需要提及的是PHT中涉及到的Segment段概念,可以这么理解:段是动态视图索引的小单元(静态视图直接是使用节描述),其中N个节构成一个段,节可以不属于任何段。后续将有具体描述。
静态视图 一、 节区头 静态视图是从ELF头中获取到SHT在文件中的偏移(e_shoff),进而遍历SHT中的每个节区头来获取到展示信息。从图1的ELF文件描述中可知SHT中的项为节区信息,如图3所示,每个节区头中包含的节区信息有节区名、节区类型、节区位置等信息。 从节区头的结构信息可以了解到,节是ELF文件内容存放的一个较小单元,其存放着一类同样特性的文件信息,如STRTAB类型节中存放字符串信息、DYNAMIC类型节中存放动态链接信息等。每个节的解析方式,按照节类型不同,有不同的解析方式。 根据图3中的节区头描述,可知道节区头的基本描述信息,这里需要提及到的是地址属性。如图4所示,节区头中包含了两个地址:sh_addr和sh_offset。可以先这么理解,sh_offset为文件偏移,既是常规文件读写方式的文件偏移地址。而sh_addr则是虚拟地址,可以理解为加载进入内存后,相对于该ELF模块基址的偏移地址(既是实际内存地址=虚拟地址+模块基址)。虚拟地址这个概念涉及到动态加载的内存对齐,在后面描述动态视图中讲解。
二、 STRTAB节 该类型节是用来存放字符串信息,如图5所示,该节内容就是包含了一堆以NULL结尾的字符序列。用来提供给其它节的名字索引使用。ELF文件中用一个word描述的名字属性,均是在相应STRTAB节(可能存在多个STRTAB类型节,具体用哪个由节区头的link中属性确定)中的索引值。 如图4中可以看出一个ELF文件中可以包含多个STRTAB类型的节,如.dynstr(动态链接需要的符号)、.shstrtab(节区名称需要的符号)、.strtab(符号表符号)等节。
三、 DYNSYM节 该类型节存放着动态链接中需要的符号表信息,如图6所示,该节存放着系列的符号信息(Elf32_Sym)。每个符号信息中包含了符号名字(link属性中对应的STRTAB节中的索引)、值(虚拟地址或者绝对地址,取决于符号类型)等属性。 如图6中选中的蓝色区域,对应的符号信息为图7中红框内容,表示该项是个全局可见的函数符号,函数名为“lua_pushstring”,其虚拟地址为0x56c2c(如图8所示,对应为该函数代码地址),函数大小为0x84字节。 ELF文件动态加载过程的符号地址查询则是在DYNSYM类型节中查找,期间涉及到HASH节,该节可以直接理解为符号哈希表,用于快速查找到符号在DYNSYM节中的索引。
四、 REL节 该类型节中包含重定位信息,如图9所示,每个重定位项(Elf32_Rel)中包含了重定位生效的地址及重定位项的类型符号信息(info中保存着在对应DYNSYM节中的索引)。 涉及到的重定位类型,在ARM中有一百多种,具体可参见ELF for the ARM® Architecture文件。这里提及常见的几种:R_ARM_ABS32(如全局函数指针的方式调用外部函数)、R_ARM_GLOB_DAT(如局部函数指针方式调用外部函数)、R_ARM_JUMP_SLOT(如直接调用外部函数)、R_ARM_RELATIVE(基址重置)。具体可以通过自己编写DEMO程序,利用readelf查看。 如图10所示,查看.rel.plt节(为REL类型),得知lua_pushstring为R_ARM_JUMP_SLOT类型,其生效位置是在.got中,如图11所示。 五、 DYNAMIC节 该类型节是个很重要但是很好理解的节,如图12所示,该节包含一系列动态链接中需要用到的信息索引。相应的类型如图13所示。 利用readelf可以容易得到如图14所示。其中需要关注的是其中涉及到的地址,均是虚拟地址。(动态使用的地址,均是虚拟地址。)还有FINIT_ARRAY和INIT_ARRAY这两类可选属性,在上述表格没有列出。 动态视图 一、 程序表 前面从静态视图角度介绍了ELF文件,这里从动态视图过一遍。动态视图是根据linker等解析器动态加载的时候解析的流程来看ELF文件。从ELF头中获取到PHT后,直接遍历每项内容进行ELF文件解析加载。如图15和图16所示程序头包含了各个段的信息,如类型、位置、大小、对齐信息等。 利用readelf工具快速得到图17所示展现,就是ELF程序头中常包含的内容,有两个LOAD段、一个DYNAMIC段等。同一个段有着相同的页属性,如权限、对齐大小等。
二、 段说明 从图15中可以看出,只有LOAD段的节会加载进内存进行进一步使用,其它不相关段只有临时解析用途或者不用。另外,动态视图中,段会包含多个节,便于linker一次性映射进入内存。但是由于节的权限不同,所以一般需要划分两个LOAD。具体是可写和可执行对立。
linker动态加载 本文描述的linker加载,是基于5.1.1_r14 AOSP代码来分享,7.x等新版本可能变动较大,会存在冲突,但这不妨碍理解动态加载的核心原理。 这里先提下前面静态视图中预埋的虚拟地址和文件偏移不一致问题,从动态视图中可以看出由于权限等问题每个段独立分开加载到内存。因此需要进行对齐操作,所以中间可能出现间隙,进一步导致两个地址不一致。 另外,如.bss节也会导致加载内存和文件占用不一致情况(.bss节文件不占用大小)。故而除了将ELF文件映射到内存操作用到文件偏移,其余动态加载均是使用虚拟地址。
一、 装载 linker在确认需要加载到内存后(之前没加载过),通过ElfReader对ELF文件进行反序列化。 验证ELF头信息无误后,会根据LOAD段的总长度(所有LOAD中最高和最低虚拟地址的差值,页对齐)预分配一段空间。然后按照LOAD中地址映射,如图18所示。 其中文件大小不能超过内存大小。当内存大小大于文件大小的时候,将分配出来的多余空间清零,如.bss节。
二、 链接 将ELF装载到内存后,需要通过DYNAMIC段信息来实现链接。判断是否有库依赖(DT_NEEDSO),有的话进行递归加载。然后进行重定位操作。 重定位的核心是把调用指令都跳转到合适的目标地址。如图19所示,linker中对DT_JMPREL(.rel.plt)和DT_REL(.rel.dyn)进行了重定位操作。 通过前面对REL节的介绍,基本可以推测到linker的重定位实现,如图20所示。既是去遍历REL节里的重定位项,通过索引到DYNSYM节中获取符号信息。进而去获取该符号的位置(比如通过Elf32_Sym.st_name名字遍历其它SO符号信息得到地址),根据重定位类型填充进生效地址中。 常接触到的重定位类型如R_ARM_ABS32、R_ARM_GLOB_DAT、R_ARM_JUMP_SLOT均是通过“符号地址+补齐大小|Thumb指令标志”公式计算。 重定位完,则是初始化函数的调用,既是DT_INIT和DT_INIT_ARRAY处理。
小结 从上面的讲解中可以基本了解ELF文件格式,及动态加载过程。 面对抹除ELF文件静态视图的ELF加固,可以尝试利用这块知识进行修复(有修改偏移但信息还存在情况跟)。如果是完全抹除静态SHT,那么修复难度相对较大。但每个Android版本linker变动较大,其解析内容的不确定性导致此类完全抹除安全方案无法在兼容性要求较高的产品上使用。 |
最新评论
发表评论