Android ELF 文件实例解析

别说谁变了你拦得住时间么 提交于 2020-12-19 06:48:08

上一篇讲述了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内容


ELF文件头的解析


使用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格式分布的,对于每个节各个字节的含义,由于篇幅的原因,这里并没有完全展开,感兴趣的读者可以查阅官方文档继续深入。


如何定位init或init_array函数


上面提到了两个节区,分别是.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反调试代码,或者修改寄存器的值,好了,本篇就分享到此,祝大家端午节快乐。


吃粽子迎端午

---------------------------------------------
后续部分节选自《Android 应用安全测试与防护》
如果您对App安全有任何问题
可在本公众号进行留言
我们会进行回复~
---------------------------------------------
本书由中国工信出版集团人民邮电出版社(2020年5月)出版,可到各大电商平台(京东、天猫、当当等)购买,搜索书名《Android 应用安全测试与防护》即可。

欢迎关注本书公众号
获取更多App安全知识

       

本文分享自微信公众号 - App安全红宝书(apphongbaoshu)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!