ELF section修复的一些思考

来源:互联网 发布:linux 延迟函数 select 编辑:程序博客网 时间:2024/05/24 01:42

原帖:http://bbs.pediy.com/showthread.php?t=192874

一、概述

相信各位读者对so分析都采用静态和动态相结合的方式,静态分析常用readelf、objdump、ida等工具,这些工具对so文件的分析都会使用到Section信息。从这篇帖子中http://bbs.pediy.com/showthread.php?t=191649知道,程序并不需要section信息。现很多so文件对section信息都进行了处理,导致常用分析工具无法使用。以下讨论前段时间对section修复的一些思考,若有不足或错误之处,请各位大大指正,小弟感激不尽!

二、仅处理so文件头

在上文提到的帖子中,给出了一种section处理的一种简单方式。这里在罗嗦下,即将Elf32_Ehdr中的e_shoff, e_shnum, e_shstrndx, e_shentsize字段处理。修复公式:e_shstrndx = e_shnum -1;e_shnum  =  (file_size  – e_shoff) / sizeof(Elf32_Shdr)。在那篇帖子中作为修复的数字so文件,并未处理e_shoff字段,故用上式修复可行。那如果都处理掉,则上式中存在两个未知数,无法利用。

一种简单的思路是,手动查找so文件中一些稳定且标志性的数据作为参考来修复。这里,我选择shstrtab表,这样比较简单。因为shshtrtab后面就是section头信息,这样就间接找到e_shoff位置,即可利用上式修复。

手动查找当然可行,毕竟麻烦。作为程序猿,应该通过程序来解决问题。借鉴手动修复的思路,只要程序能找到shstrtab即可实现修复。从观察或e_shstrndx知道,shstrtab section为最后一个section,即处于文件末尾。那直接移动到末尾读取到shstrtab section,则e_shoff  = sh_offset + sh_zize(这里还需对e_shoff  4字节对齐处理)。

三、无section信息

现阶段,我遇到的很多so文件的section修复都采用上述方法,还未遇到无section信息的so,即直接将so文件中的section直接删除,或者替换section内容(比如填充隐藏代码或者垃圾数据之类的)。直接删除section信息,即可节省空间,又可让静态工具“蛋疼”(有点好奇为什么不对so作如此处理)。另外,直接从内存中dump出来的so文件,也是没有section信息的(因为section没有被加载到内存中)。当然,从内存中dump的so文件,由于内存对齐的原因,需要作下简单处理,这里就不赘述。

从内存dump出的so文件已经经过解密,直接拿来分析,能获得事半功倍的效果。但没有section信息,静态分析很是不爽。如果原so文件有section信息,则只需要把原so文件中的section信息复制过来并修复Elf32_Ehdr即可。那如果原so无section信息,我的理解是需要对section信息进行重建(虽然现阶段还没用到,处于问题思考的完整性,讨论这种情况)。

使用readelf –S 查看一个完整的so文件section如下图所示:

图 1

使用readelf –l 如图所示:

图 2

从segment信息可以看出, 对.dynamic和.arm_exidx的section重建很简单,即读取即可。

通过.dynamic,可以对大部分section进行重建,具体如下:

1.  通过DT_SYMTAB,DT_STRTAB,DT_STRSZ,DT_REL,DT_RELSZ,DT_JMPREL, DT_PLTRELSZ,DT_INIT_ARRAY,DT_INIT_ARRAYSZ,DT_FINI_ARRAY,DT_FINI_ARRAYSZ 得到.dynsym,.dynstr,  rel.dyn, rel.plt,  init_array,  fini_array 相应的section vaddr 和 size信息,完成对上述section的重建。这里需要注意,处于load2中的section,offset = vaddr – 0x1000

2.  通过DT_HASH得到hash section的vaddr,然后读入前两项得到nbucket和nchain的值,得到hashsz =(nbucket + nchain + 2) * sizeof(int), 完成对hash表重建

3.  Plt的起始位置即为rel.plt的末尾,通过1中的对rel.plt的处理,即可得到plt的offset和vaddr信息。通过plt的结构知道,plt由固定16字节 + 4字节的__global__offset_table变量和n个需要重定位的函数地址构成,函数地址又与rel.plt中的结构一一对应。故size = (20 + 12 * (rel.plt.size) / sizeof(Elf32_Rel)。

4.  从DT_PLTGOT可以得到__global_offset_table的偏移位置。由got表的结构知道,__global_offset_table前是rel.dyn重定位结构,之后为rel.plt重定位结构,都与rel一一对应。则got表的重建具体为:通过已重建的.dynamic得到got起始位置,通过__global_offset_table 偏移 + 4 * (rel.plt.size) / sizeof(Elf32_Rel)(这里还需要添加2个int的填充位置)得到got的末尾,通过首尾位置得到got的size,完成重建

5.  通过got的末尾,得到data的起始位置,再通过load2_vaddr+ load2_filesz得到load2的末尾(load2即第二个LOAD),即data的末尾位置,计算长度,完成修正。可能读者会问,bss才是load2的最后一个section。的确,但bss为NOBITS,即可把data看作load2最后一个section。

6.  对bss的修正就很简单,offset和vaddr即为load2末尾。由于未NOBITS类型,长度信息无关紧要。

7.  到这里,读者可能已经发现,还没对text和ARM.extab修正。限于本人水平,还没能找到方法区分开这两个section。现处理是将之合并,作为text & ARM.extab节。具体修正:offset和vaddr通过plt末尾得到,长度通过ARM.exidx的起始位置和plt末尾位置计算得到。

至此,绝大部分section信息已经重建完成。最后,在将shstrtab添加,并修正Elf32_Ehdr,完成section重建。虽然未100%重建,但已经能够帮助分析了。重建后的如图所示,图中红色部分即是未分离的test & ARM.extab section。


使用ida也能正常打开,只是会将ARM.extab的数据转换成错误代码,其他均正常。

图 4


0 0