上一篇讲述了Android ELF文件结构解析,本篇文章通过一个样本实例深入的理解一下Android ELF文件结构内容,以及如何定位 init 或init_array函数。
一般情况下,开发者为了增加代码的安全性,会将关键的代码封装在动态链接库so文件中,下面我们通过一个样本文件自带的libcore.so文件为例,从十六进制数据的角度,进一步掌握Android ELF文件格式。通过使用十六进制编辑工具(如:010Editor)打开libcore.so文件,这个工具会有ELFTemplate.bt模板,相对比较方便,推荐使用。
打开并运行ELFTemplate.bt模板,在Template Results窗口可以看到解析的结果,如下图1所示。
图1 目标文件libcore.so内容
使用arm-linux-androideabi-readelf.exe-h libcore.so或者使用”>”追加到文件,如:arm-linux-androideabi-readelf.exe-h libcore.so > ELF_Header.txt
从上图我们知道ELF Header有0x34个字节,也就是52个字节,使用010Editor工具获取libcore.so文件的ELF文件头部信息,前0x34个字节(从0000到0033),如图2所示。
图2 libcore.so文件的ELF文件头部信息
结合ELF Header数据结构和图2内容,解析归纳如下:
起始偏移 |
字段意义 |
占字节数 |
具体的值 |
基本说明 |
|
0000h
|
Magic |
4 |
7F 45 4C 46 |
文件标示(.ELF) |
|
Class |
1 |
01 |
ELF 32位对象 |
||
Data |
1 |
01 |
编码是小端 |
||
Version |
1 |
01 |
current |
||
OS/ABI |
1 |
00 |
UNIX System V ABI |
||
ABI Version |
1 |
00 |
|||
对齐补位 |
7 |
00 00 00 00 00 00 00 |
|||
0010h |
ELF文件类型 |
2 |
0003 |
共享文件 |
|
0012h |
CPU平台属性 |
2 |
0028 |
Advanced RISC Machines ARM |
|
0014h |
ELF版本号 |
4 |
00000001 |
current |
|
0018h |
入口虚拟地址 |
4 |
00000000 |
此值针对可执行文件。 |
|
001Ch |
程序头表文件偏移 |
4 |
00000034 |
Program Header Table的开始 |
|
0020h |
节区头表文件偏移 |
4 |
00003130 |
Section Header Table的开始 |
|
0024h |
ELF标识位 |
4 |
05000000 |
未使用 |
|
0028h |
ELF文件头部大小 |
2 |
0034 |
ELF文件头部大小52个字节 |
|
002Ah |
程序头表的大小 |
2 |
0020 |
Program Header Table的大小 |
|
002Ch |
程序头表的数量 |
2 |
0007 |
Program Header Table的数量 |
|
002Eh |
节区头表的大小 |
2 |
0028 |
Section Header Table的大小 |
|
0030h |
节区头表的数量 |
2 |
0015 |
Section Header Table的数量 |
|
0032h |
节字符串表的索引 |
2 |
0014 |
节或段字符串表的索引 |
注意:
(1) 010Editor工具获取数据全部是16进制的。
(2) ELF 标示头:7F 45 4C 46。
(3) ELF文件头部大小:0x0034 = 52个字节。
(4) Program Header Table的大小: 0x20 = 32个字节。
(5) Section Header Table的大小:0x28 = 40个字节。
程序头表的解析
使用arm-linux-androideabi-readelf.exe-l libcore.so或者使用”>”追加到文件,如:arm-linux-androideabi-readelf.exe-l libcore.o > Program_Header_Table.txt
根据ELF Header,我们知道Program Header Table开始是0x34,大小为0x20,数量有0x7个,也就是从0034h到0113h,使用010Editor工具获取libcore.so文件Program Header Table的头部信息,如下图3:
图3 libcore.so文件的Program Header Table头部信息
结合Program Header Table数据结构和图3,解析归纳如下:
Program Header Table中前4个成员对象:
序号 |
值/段类型 |
段偏移 |
段内存映像入口地址 |
未使用 |
|
01 |
00000006 |
PT_PHDR |
00000034 |
00000034 |
00000034 |
02 |
00000001 |
LOAD |
00000000 |
00000000 |
00000000 |
03 |
00000001 |
LOAD |
00002EB8 |
00003EB8 |
00003EB8 |
04 |
00000002 |
DYNAMIC |
00002E4C |
00003E4C |
00003E4C |
05 |
6474E551 |
GNU_STACK |
00000000 |
00000000 |
00000000 |
06 |
70000001 |
EXIDX |
00002210 |
00002210 |
00002210 |
07 |
6474E552 |
GNU_RELRO |
00002EB8 |
00003EB8 |
00003EB8 |
Program Header Table中后4个成员对象:
段在文件中的大小 |
段在内存中的大小 |
段的访问权限 |
段对齐方式 |
||
000000E0 |
000000E0 |
00000004 |
可读(R) |
00000004 |
4字节 |
00002387 |
00002387 |
00000005 |
可读+可执行(R+E) |
00001000 |
1K字节 |
0000014C |
0000014C |
00000006 |
可读+可写(R+W) |
00001000 |
1K字节 |
000000F8 |
000000F8 |
00000006 |
可读+可写(R+W) |
00000004 |
4字节 |
00000000 |
00000000 |
00000006 |
可读+可写(R+W) |
00000000 |
0 |
00001000 |
00001000 |
00000004 |
可读(R) |
00000004 |
4字节 |
00000148 |
00000148 |
00000006 |
可读+可写(R+W) |
00000148 |
4字节 |
注意:
(1) 程序头部仅对于可执行文件或共享目标文件有意义。
(2) LOAD段是需要加载到内存中的。
节区头表的解析
使用arm-linux-androideabi-readelf.exe-S libcore.so或者使用”>”追加到文件,如:arm-linux-androideabi-readelf.exe-S libcore.o > Section_Header_Table.txt
根据ELFHeader,我们知道Section Header Table开始是0x3130,大小为0x28,数量有0x15个,也就是从3130h到3477h,使用010Editor工具获取libcore.so文件Section Header Table的头部信息,如下图4:
图4 libcore.so文件Section Header Table的头部信息
结合Section Header Table数据结构和图4,归纳如下:
Section Header Table数据结构前4个成员对象:
序号 |
节的名称 |
节的类型 |
节属性 |
节在内存映像入口地址 |
|||
00 |
00000000 |
无 |
00000000 |
无 |
00000000 |
无 |
00000000 |
01 |
0000000B |
.dynsym |
0000000B |
DYNSYM |
00000002 |
ALLOC |
00000114 |
02 |
00000013 |
.dynstr |
00000003 |
STRTAB |
00000002 |
ALLOC |
000004D4 |
03 |
0000001B |
.hash |
00000005 |
HASH |
00000002 |
ALLOC |
00000A48 |
04 |
00000021 |
.rel.dyn |
00000009 |
REL |
00000002 |
ALLOC |
00000BD4 |
05 |
0000002A |
.rel.plt |
00000009 |
REL |
00000002 |
ALLOC |
00000C14 |
06 |
0000002E |
.plt |
00000001 |
PROGBITS |
00000006 |
A+X |
00000C4C |
07 |
00000033 |
.text |
00000001 |
PROGBITS |
00000006 |
A+X |
00000CB4 |
08 |
00000039 |
.ARM.extab |
00000001 |
PROGBITS |
00000002 |
A+X |
000021BC |
09 |
00000044 |
.ARM.exidx |
70000001 |
ARM_EXIDX |
00000082 |
AL |
00002210 |
10 |
0000004F |
.rodata |
00000001 |
PROGBITS |
00000032 |
AMS |
00002310 |
11 |
00000057 |
.fini_array |
0000000F |
FINI_ARRAY |
00000003 |
AW |
00003E8B |
12 |
00000063 |
.init_array |
0000000E |
INIT_ARRAY |
00000003 |
AW |
00003EC0 |
13 |
0000006F |
.dynamic |
00000006 |
DYNAMIC |
00000003 |
AW |
00003EC4 |
14 |
00000078 |
.got |
00000001 |
PROGBITS |
00000003 |
AW |
00003FBC |
15 |
0000007D |
.data |
00000001 |
PROGBITS |
00000003 |
AW |
00004000 |
16 |
00000083 |
.bss |
00000008 |
REL |
00000003 |
AW |
00004004 |
17 |
00000088 |
.commment |
00000001 |
PROGBITS |
00000030 |
MS |
00000000 |
18 |
00000091 |
.not.gnu.gold-version |
00000007 |
NOTE |
00000000 |
无 |
00000000 |
19 |
000000A8 |
.ARM.arttributes |
70000003 |
ARM_ATRIBUTES |
00000000 |
无 |
00000000 |
20 |
00000001 |
.shstrtab |
00000003 |
STRTAB |
00000000 |
无 |
00000000 |
Section Header Table数据结构后6个成员对象:
节在文件中偏移 |
节的大小 |
节区头部的索引 |
附加信息 |
节对齐方式 |
节包含固定大小的项目 |
|
00000000 |
00000000 |
00000000 |
00000000 |
00000000 |
无 |
00000000 |
00000114 |
000003C0 |
00000002 |
00000001 |
00000004 |
4字节 |
00000010 |
000004D4 |
00000574 |
00000000 |
00000000 |
00000001 |
1字节 |
00000000 |
00000A48 |
000001BC |
00000001 |
00000000 |
00000004 |
4字节 |
00000004 |
00000BD4 |
00000040 |
00000001 |
00000000 |
00000004 |
4字节 |
00000008 |
00000C14 |
00000038 |
00000001 |
00000006 |
00000004 |
4字节 |
00000008 |
00000C4C |
00000068 |
00000000 |
00000000 |
00000004 |
4字节 |
00000000 |
00000CB4 |
000014D8 |
00000000 |
00000000 |
00000004 |
4字节 |
00000000 |
000021BC |
00000084 |
00000000 |
00000000 |
00000004 |
4字节 |
00000000 |
00002210 |
00000100 |
00000007 |
00000000 |
00000004 |
4字节 |
00000008 |
00002310 |
00000077 |
00000000 |
00000000 |
00000001 |
1字节 |
00000001 |
00002E8B |
00000008 |
00000000 |
00000000 |
00000004 |
4字节 |
00000000 |
00002EC0 |
00000004 |
00000000 |
00000000 |
00000001 |
1字节 |
00000000 |
00002EC4 |
000000F8 |
00000002 |
00000000 |
00000004 |
4字节 |
00000000 |
00002FBC |
00000044 |
00000000 |
00000000 |
00000004 |
4字节 |
00000000 |
00003000 |
00000004 |
00000000 |
00000000 |
00000004 |
4字节 |
00000000 |
00003004 |
00000000 |
00000000 |
00000000 |
00000001 |
1字节 |
00000000 |
00003004 |
00000026 |
00000000 |
00000000 |
00000001 |
1字节 |
00000001 |
0000302C |
0000001C |
00000000 |
00000000 |
00000004 |
4字节 |
00000000 |
00003048 |
0000002D |
00000000 |
00000000 |
00000001 |
1字节 |
00000000 |
00003075 |
000000B8 |
00000000 |
00000000 |
00000001 |
1字节 |
00000000 |
libcore.so文件的Section Header Table数据就解析完了,剩余sh_name字段,但是我们已经知道了sh_name在字符串表的下标偏移。根据STRTAB节在文件中偏移00003075,定位到字符串节区20的位置,如下图5:
图5 STRTAB节区内容
到目前为止,ELF Header、Program Header Table、Section Header Table,以及.shstrtab节区了解了。libcore.so文件的内容剩余0114h到3074h,也就是Section Header Table中19个节的具体内容。
节区1: .dynsym (文件起始偏移: 0114,大小:0x3C0,即文件结束偏移: 04D3)
节区2: .dynstr (文件起始偏移: 04D4,大小:0x574,即文件结束偏移:0A47)
节区3: .hash (文件起始偏移: 0A48,大小:0x18C,即文件结束偏移:0BD3)
节区4: .rel.dyn (文件起始偏移: 0BD4,大小:0x40,即文件结束偏移:0C13)
节区5: .rel.plt (文件起始偏移: 0C14,大小:0x38,即文件结束偏移:0C4B)
节区6: .plt (文件起始偏移: 0C4C,大小:0x68,即文件结束偏移:0CB3)
节区7: .text (文件起始偏移: 0CB4,大小:0x14D8,即文件结束偏移:218B)
......
由于.text节区比较长,省略一部分,直到0x218B,
节区8: .ARM.extab (文件起始偏移: 218C,大小:0x84,即文件结束偏移:220F)
节区9:. ARM.exidx (文件起始偏移: 2210,大小:0x100,即文件结束偏移:230F)
节区10: .rodata (文件起始偏移: 2310,大小:0x77,即文件结束偏移:2386)
从2386到2EB7 全零补齐。
节区11: .fini_array (文件起始偏移: 2EB8,大小:0x8,即文件结束偏移:2EBF)
节区12: .init_array (文件起始偏移: 2EC0,大小:0x4,即文件结束偏移:2EC3)
节区13: .dynamic (文件起始偏移: 2EC4,大小:0xF8,即文件结束偏移:2FBB)
节区14: .got (文件起始偏移: 2FBC,大小:0x44,即文件结束偏移: 2FFF)
节区15: .data (文件起始偏移: 3000,大小:0x4,即文件结束偏移: 3003)
节区16: .bss (文件起始偏移: 3004,大小:0x0,即文件结束偏移: 3003)
无
节区17: .commment (文件起始偏移: 3004,大小:0x26,即文件结束偏移: 3029)
节区18:.not.gnu.gold-version (文件起始偏移: 302C,大小:0x1C,即文件结束偏移: 3047)
节区19: .ARM.arttributes (文件起始偏移: 3048,大小:0x2D,即文件结束偏移: 3074)
以上就是libcore.so文件数据是如何在ELF格式分布的,对于每个节各个字节的含义,由于篇幅的原因,这里并没有完全展开,感兴趣的读者可以查阅官方文档继续深入。
上面提到了两个节区,分别是.fini_array和.init_array,这两个函数是脱壳,逆向非常关键函数,由于程序加壳,壳必须比原应用程序优先获取执行权,然后在壳代码中添加反调试,桩等手段防止逆向者正常调试,因此,我们必须比壳代码更要提前获得执行权,那怎么才能更提前获取执行权呢?
对,就是使用IDA Pro调试时,提前先到达.fini_array或.init_array函数。
定位到init函数入口的方法步骤:
1. 查看libdvm.so模块,搜索dvmLoadNativeCode函数,找到对dlopen函数的调用,下断点并F7跟进(对于dlopne函数源码的分析在下一篇会分享);
2. 由于rom包版本的不同这一点可能不一样,在4.4.2 rom包上,dlopen函数是对do_dlopen函数的封装,对 do_dlopen(soName, falgs),F7跟进;
3. do_dlopen函数中调用find_library(soName).完成对so的加载,并会返回一个soinfo对象,调用soinfo对象的成员函数constructors(),完成调用动态链接库初始化代码;
4. 在constructors()函数中,调用CallFunction(“DT_INIT”, init_func); 回调函数init_func就是init段中的函数;
5. 进入CallFunction 找到BLX R4 既是对init_func函数的调用。
6. BLX R4函数即对应function()函数,下断点,F7跟进,就成功来到init段函数。
至此,逆向者就可以在加载壳的so文件时,提前nop反调试代码,或者修改寄存器的值,好了,本篇就分享到此,祝大家端午节快乐。
本文分享自微信公众号 - App安全红宝书(apphongbaoshu)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4596282/blog/4438042