背景
在So动态链接后,读取ELF文件,发现无法读取Section Header中的名称列表。即,无法在EShdr
中根据e_shstrndx
找到Section对应的名字。
可是在readelf -a lib.so
中,可以根据Section Header读取到Section对应的名称。
而读取出来的结果如下:
其中,shstrtab
和strtab
的类型都是STRTAB
,但是shstrtab
仅仅只保存Section Name的字符串表,而strtab
则包括其他的变量名、符号名等的字符串表。
问题
-
为什么在运行时无法通过
Section Header
中的sh_name
来找到对应的名称?
Section Header的 为什么要分为
strtab
和shstrtab
两个字符串表?
原因
从运行时的日志来看
- 根据ELF头中的
e_shoff
找到ELF_SHDR
-
libart.so
加载后的位置在753042b000
-7530a14000
- 加载后的基址为:
753042b000
,PHDR
的地址为:753042b040
- 计算得到的
SHDR
的地址为7530b55520
-
运行日志:
libs line :753042b000-7530a14000 r-xp 00000000 fd:00 3439 /system/lib64/libart.so
base_addr:753042b000 PHDR :753042b040 shdr_offset:72a520
SHDR :7530b55520 shstr:7530b55ba0
运行时日志是通过
/proc/self/maps
中获取libart.so
得到的基址,与上面通过readelf
读取的ELF文件并不是同一个!该日志的基址和偏移量与上面的图无关。
运行后的结果可知:
通过e_shoff
所计算的出来的SHDR
的地址已经超出So加载的地址了。
SHDR
- libart.so的页结束地址
= 7530b55520
- 7530a14000
= 141520
。
而在运行时候的动态链接是根据Segment来加载So中的文件,原因是希望尽可能小的使用内存页面,并且提升加载速度。
于是查看程序头部分,发现LOAD
类型的段中,仅仅只有.dynstr
这个字符串表会被加载到内存中。
也就是说:
在So动态链接到内存中时,.shstrtab
和.strtab
这两个Table是并没有加载到内存中的。ld仅仅只会加载.dynstr
这个Table就够用了。
并且从执行视图来看,ELF也不一定会有Section Header Table。
而且单个Section
可能属于多个Segment
:
- 例如
got
这个Section
就属于LOAD
以及GNU_RELRO
这两个Segment
。
从IDA Pro打开的角度来看
从Section Headers中可以看到:
-
strtab
的offset
为395ac
,大小为170c
。 -
shstrtab
的offset
为3acb8
,并且大小为194
。
可以看到strtab
之后紧接着就是shstrtab
:
395ac
+170c
= 3acb8
打开IDA Pro可以看到在文件的395ac
处就是strtab
的字符串表:
而在文件的3acb8
处,可以看到是Section Header Name的字符串:
结论
shstrtab
与strtab
这两个表仅仅只是链接后保存在So文件中的,而在链接之后的执行视图层面,这两个字符串表不会被加载到内存中。
在readelf
这个程序中,会在文件中根据shstrtab
表的偏移量来查找Section对应的名称,然后输出文案。
并且,在执行视图中,可能没有SHDR
,所以在链接完的文件中可以根据SHDR
中的偏移量来找到对应的名字,而在加载到内存之后的执行视图中,不能按照SHDR
来查找Section的名字了。
后记
在Android GOT Hook该文中,则是通过对GOT表进行Hook,而查找GOT表的方式则是通过Section Header
以及shstr
来找到对应的Got表的偏移地址。
而在本人测试的过程中发现这方案在Runtime时并不可行,在动态链接后,Section Header
有可能不会加载,因为Section Header
的加载可有可无,可能有些ROM会加载,有些ROM不会加载。
在ELF中可以被修改又不影响执行的区域该文中,本人比较认同。因为Program Header
已经将需要加载的Section都决定了,Section Header
只是为了readelf
这种方便读取ELF文件而存在。减少加载也可以减少内存以及文件映射所带来的损耗。
所以对于Got表的偏移量可以在静态的时候,通过readelf
来读取Got表的偏移量,在Runtime的时候,通过基址加偏移量来获取Got表的地址。Android下的GOT表Hook实现这篇文章描述了3中Got表Hook的方式。