Android逆向-基础与实践 (九) ELF文件感染注入

前言:

在 Android 平台下 Native 层的可执行文件属于 ELF 文件格式,通过感染 ELF 文件可以实现 Android 平台的注入功能。感染 ELF 文件的注入功能有很多种实现方式,这篇文章将讲解一种感染 ELF 文件的注入技术的实现原理,并结合代码剖析实现过程。

一、实现原理

可执行文件感染就是通过修改可执行文件,添加自己的代码,使得可执行文件运行时先执行添加的代码再执行原逻辑。

根据以上的定义可知实现ELF感染需要直接修改二进制文件, 所以必须先了解ELF文件格式。之前的文章已经对ELF文件的格式做了详细介绍,本文只简要介绍与ELF感染的实现相关的文件结构。

ELF 文件结构(图1)

 

如上图所示,为ELF原文件格式。这里关注几个重要结构:

(1) ELF Header,它是ELF文件中唯一一个固定位置的文件结构,其中保存了Program Header Table和Section Header Table的位置和大小信息。

(2) Program Header Table,它保存了ELF文件的加载过程中文件的内存映射,依赖库等信息,是对于实现ELF感染最重要的结构之一。

 本文介绍的ELF感染思路就是通过修改Program Header Table中的依赖库信息,添加自定义的库文件,使得游戏加载主逻辑模块时也会加载我们自定义的so文件。

其中Program Header Table的表项结构如下:

其中p_type有如下取值

当p_type为PT_DYNAMIC时, 由p_offset和p_filesz指定的数据块一般会指向 .dynamic段, 该段包含了程序链接和加载时的依赖库信息。它也是一个数组, 数组元素的结构如下:当p_type为PT_LOAD时, 此表项描述了程序加载时的内存映射信息。

其中d_tag的取值我们关注四个:

所以思路就很明显了:

(1)先修改DT_STRTAB指向的字符串表,在里面添加自定义的so名。由于直接在原字符串表中添加会导致字符串表后面的所有数据的文件偏移改变,所以我们会把字符串表移动到文件尾。

(2)由于字符串表被要求映射到内存,所以需要在Program Header Table中添加一个PT_Load表项将文件尾添加的数据映射到内存,同样要把Program Header Table移动到文件尾。

(3)修改DT_STRTAB,DT_STRSZ指向新字符串表,并且在dynamic array的结尾加上一个DT_NEEDED项,指向自定义so名字。

(4) 修改Elf Header Table中的Program Header Table的位置,指向新的Program Header Table。

修改之后的文件结构如下图:

感染后的ELF文件格式(图2)

 

二、实例分析

本文以“2048”游戏为例介绍它的实现方式。首先我们用“010 Editor”打开它的主逻辑模块,应用ELF模式模版之后看到文件结构如下:

2048游戏主逻辑模块文件格式(图3)

 

从上图可以看出:

(1) elfheader.e_phoff,elfheader.e_phentsize, elfheader.e_phnum分别保存了Program Header Table的位置、表项大小、表项个数信息。

(2) program_header_table.program_table_element[0]中也保存了program_header_table的位置信息和内存映射信息。

(3)program_header_table.program_table_element[1]将文件偏移为0-2238752的数据映射到内存中地址为0-2238752的位置。

(4)program_header_table.program_table_element[3]保存了dynamic array结构数组的的起始位置和大小信息, 具体信息如下图:

2048游戏的“dynamic array”数据(图4)

上图中 “1” 为DT_STRTAB数据(字符串表)

“2”为DT_STRSZ数据(字符串表的大小)

“3”表示DT_NEEDED数据(需要倒入的so模块名在DT_STRTAB中的偏移)

“4”表示为实现注入需要添加一项新的 DT_NEEDED 数据

 

针对《2048》游戏,感染主逻辑模块的 ELF 文件的过程如下:

(1)复制整个 program_header_table 项数据,并将复制的数据移动至文件末尾。

(2)复制一个 program_table_element 项数据,并添加至新的 program_header_table 数据项的尾部。

(3)修改 elf_header.e_phoff 数据并指向新的 program_header_table 数据,同时将elfheader.e_phnum 变量的数值加1。

(4)从图4中的“1”,“2”的数据可得到字符串表的位置和大小信息,将字符串表的信息复制到文件末尾。

(5)修改新字符串表,添加自定义模块名,本文使用的模块名为 “libhello.so"。

(6)  修改新添加的 program_table_element 项相关的数据,将 p_type 数据修改为 PT_Load 类型,将 p_offset 数据设置为新 program_table_header 数据项的文件偏移,将 p_filesz 和 p_memsz 数据修改为新添加数据的长度,将 p_flags 数据修改为“7",即可读可写可执行。

在修改 p_vaddr 和 p_paddr 数据时,需要注意不能与前面两个 PT_Load 段的地址重叠。

(7)将 program_header_table.program_table_element[0] 结构中的 p_offset 和 p_filesz 数据修改为新的 program_header_table 数据项的位置和大小,同时将 p_vaddr,p_paddr 和 p_memsz 数据修改为新 program_header_table 数据映射到内存中的位置和大小。

(8)将图4中的"1","2"的数据修改为新字符串表的位置和大小,这里要注意 DT_STRTAB 必须为内存虚拟地址而不是文件偏移地址。

(9)将图4的"4"的全零数据填入一项 DT_NEEDED 数据,并指向需要注入的 so 模块的字符串信息。

按上文修改步骤修改之后的文件格式如下:

感染之后的2048主逻辑模块格式(图5)

感染之后的dynamic array段(图6)

 

三、感染 ELF 文件的注入编程实现

本文介绍的ELF感染方法的编码实现就是将以上手动修改的过程通过代码实现, 这里介绍4处修改的代码:

首先是修改程序头:

 

修改 ELF 文件的程序头信息的执行过程为: 首先通过 pEhdr 指针定位到 Phdr 数据,并将其移动到文件末尾,然后新添加一个表项。

在程序头信息修改完毕之后,需要找到字符串表并将其移动到文件末尾,通过遍历 program_header_table 和 dynamic array 信息找到 DT_STRTAB 和 DT_STRSZ 项数据,对应的关键代码及解释如下:

 

在字符串表处理完毕之后,需要解决新添加的 PT_Load 项的内存地址映射过程,对应的关键代码如下:

由于不能与原始 PT_Load 项映射的内存地址重叠,而且原始的 PT_Load 为了能够映射 .bss 段而在内存中占了更多空间, 所以在新添加 PT_Load 项时,在映射的内存地址与文件偏移之间需要保留合理的差值,即代码中的 ulVAOffset 变量值。 

四、完整代码已经上传到附件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值