Android平台感染ELF文件实现模块注入

对游戏动态修改之前首先要将外挂模块注入游戏进程,本章将介绍Android平台上通过感染ELF文件实现模块注入。

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

根据以上的定义可知实现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。

修改之后的文件结构如下图:
在这里插入图片描述
1.2 实例分析
本文以“2048”游戏为例介绍它的实现方式。首先我们用“010 Editor”打开它的主逻辑模块,应用ELF模式模版之后看到文件结构如下:
在这里插入图片描述
从上图可以看出:

(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的位置和大小信息, 具体信息如下图:
在这里插入图片描述

对游戏动态修改之前首先要将外挂模块注入游戏进程,本章将介绍Android平台上通过感染ELF文件实现模块注入。

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

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

图1. 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。

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

图2 感染后的ELF文件格式

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

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

从上图可以看出:

(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的位置和大小信息, 具体信息如下图:

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

其中“1”为DT_STRTAB,“2”为DT_STRSZ, 3为DT_NEEDED

我们做感染时的修改方式为:

(1)复制整个program_header_table,将它移动到文件尾。

(2)复制一个program_table_element,添加在新program_header_table后面。

(3)修改elf_header.e_phoff指向新program_header_table,并且将elfheader.e_phnum的值加1。

(4)从图3中的“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)修改图3中的"1""2"为新字符串表的位置和大小,这里要注意DT_STRTAB必须为虚拟地址而不是文件偏移。

(9)在图3的"4"处添加一个DT_NEEDED指向自定义so的名称字符串“libhello.so"。

按上文修改步骤修改之后的文件格式如下:
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/douluo998/article/details/129915749