安卓逆向之native层的so文件格式

安卓逆向的相关工具介绍

反编译代码的工具:

  • dex2jar:把dex文件转成jar文件
  • jd-gui: 这个工具用于将jar文件转换成java代码

反编译资源的工具:

  • APKTool: 本文重要工具,APK逆向工具。它可以将资源解码,并在修改后可以重新构建它们。它还可以执行一些自动化任务,例如构建apk。解码就是将其恢复成未打包的资源文件(包括resources.arsc,class.dex,9.png和xml);解码的资源可以重新打包成apk/jar文件;组织和处理依赖于框架资源的APK;Smali调试;执行自动化任务
  • JEB:JEB是一个功能强大为安全专业人士设计的安卓应用程序反编译工具,用于逆向工程或者审计apk文件。它是可以直接反编译apk文件的。
  • jadx:jadx是一款反编译利器,同时支持命令行和图形界面,能以最简便的方式完成apk的反编译操作。工具支持apk、dex、jar、aar等格式的文件

so文件介绍

概念:.so 文件是ELF对象文件中可被共享的对象文件(Shared object file),这些就是所谓的动态库文件。

动态文件的作用:如果用静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空 间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。

Android中的so文件就是elf文件,所以需要了解so文件,必须先来了解一下elf文件的格式。

elf文件格式

为了让大家对elf文件格式有一个整体的了解,首先先看一张非虫先生总结好的图片讲解:

20140918103240781.png

ELF文件的两种视图,分别是链接视图执行视图

链接视图:是在链接时用到的视图, 以(section)为单位,下图左侧的视角是从链接来看的。从图中我们得到,在链接阶段,我们可以忽略program header table来处理此文件,因为它是按照section header table来处理此文件的。

执行视图:在执行时用到的视图,是以(segment)为单位,下图右侧的视角是执行来看的。从图中我们可以得到,在运行阶段可以忽略section header table来处理此程序,因为它是按照program header table来处理此文件的。

image-20220302143454066

文件的组成

  • ELF header: 描述整个文件的组织。
  • Program Header Table: 描述文件中的各种segments,用来告诉系统如何创建进程映像的。
  • sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件。从图中我们也可以看出,segments与sections是包含的关系,一个segment包含若干个section。
  • Section Header Table: 包含了文件各个segction的属性信息

image-20220302144111413

区分两种视图的原因

在内存之中,多个具有相同权限(flg值)section合并一个segment。操作系统往往以为基本单位来管理内存分配,以及内存的权限管理的粒度也是以页为单位。每个section在映射时的长度都是系统页长度的整数倍,如果section的长度不是其整数倍,则导致多余部分也将占用一个页,因为一个ELF文件具有很多的section,那么这样将会导致内存浪费严重。为了减少页面内部的碎片,节省了空间,显著提高内存利用率,就将相同权限(flg值)section合并一个segment。

ELF Header

32位ELF文件中常用的数据格式

image-20220303170256836

ELF Header的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT]; // ELF的一些标识信息,前四位为.ELF,其他的信息比如大小端等
ELF32_Half e_type; // 标识了文件类型
ELF32_Half e_machine; // 文件的目标体系架构,比如ARM
ELF32_Word e_version; // 0为非法版本,1为当前版本
ELF32__Addr e_entry; // 程序入口的虚拟地址
ELF32_Off e_phoff; // 程序头部表偏移地址
ELF32_Off e_shoff; // 节区头部表偏移地址
ELF32_Word e_flags; // 保存与文件相关的,特定于处理器的标志
ELF32_Half e_ehsize; // ELF头的大小
ELF32_Half e_phentsize; // 每个程序头部表的大小
ELF32_Half e_phnum; // 程序头部表的数量
ELF32_Half e_shentsize; // 每个节区头部表的大小
ELF32_Half e_shnum; // 节区头部表的数量
ELF32_Half e_shstrndx; // 节区字符串表位置
}Elf32_Ehdr;

查看ELF Header结构的内容命令: readelf -h android_server

image-20220303171118246

e_entry

e_entry表示程序入口地址。谓程序进入点是指当程序真正执行起来的时候,要运行的指令的运行时地址。其第一条可执行文件test和动态库.so都存在所谓的进入点,且可执行文件的e_entry指向C库中的**_start,而动态库.so**中的进入点指向 call_gmon_start

重点关注的字段

在ELF Header中我们需要重点关注以下几个字段:

  • e_entry:表示程序入口地址
  • e_ehsize:ELF Header结构大小
  • e_phoff、e_phentsize、e_phnum:描述Program Header Table的偏移、大小、结构。
  • e_shoff、e_shentsize、e_shnum:描述Section Header Table的偏移、大小、结构。
  • e_shstrndx:这一项描述的是字符串表Section Header Table中的索引,值25表示的是Section Header Table中第25项是字符串表(String Table)

Section Header Table

section head table(SHT)包含了用来描述每一个section的条目(entry),每一个entry的内容主要包括该 section 的名称、类型、大小以及在整个ELF文件中的字节偏移位置等等。

每个条目结构定义:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct{
Elf32_Word sh_name; //节区名,是节区头部字符串表节区(Section Header String Table Section)的索引。名字是一个 NULL 结尾的字符串。
Elf32_Word sh_type; //为节区类型
Elf32_Word sh_flags; //节区标志
Elf32_Addr sh_addr; //如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应处的位置。否则,此字段为 0。
Elf32_Off sh_offset; //此成员的取值给出节区的第一个字节与文件头之间的偏移。
Elf32_Word sh_size; //此 成 员 给 出 节 区 的 长 度 ( 字 节 数 )。
Elf32_Word sh_link; //此成员给出节区头部表索引链接。其具体的解释依赖于节区类型。
Elf32_Word sh_info; //此成员给出附加信息,其解释依赖于节区类型。
Elf32_Word sh_addralign; //某些节区带有地址对齐约束.
Elf32_Word sh_entsize; //某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数。
}Elf32_Shdr;

Section

有些节区是系统预订的,一般以点开头号,

常用的系统节区
名称 类型 属性 含义
.bss SHT_NOBITS SHF_ALLOC + SHF_WRITE 包含将出现在程序的内存映像中的为初始化数据。根据定义,当程序开始执行,系统将把这些数据初始化为 0。此节区不占用文件空间。
.comment SHT_PROGBITS (无) 包含版本控制信息。
.data SHT_PROGBITS SHF_ALLOC + SHF_WRITE 这些节区包含初始化了的数据,将出现在程序的内存映像中。
.data1 SHT_PROGBITS SHF_ALLOC + SHF_WRITE 这些节区包含初始化了的数据,将出现在程序的内存映像中。
.debug SHT_PROGBITS (无) 此节区包含用于符号调试的信息。
.dynamic SHT_DYNAMIC 此节区包含动态链接信息。节区的属性将包含 SHF_ALLOC 位。是否 SHF_WRITE 位被设置取决于处理器。
.dynstr SHT_STRTAB SHF_ALLOC 此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称。
.dynsym SHT_DYNSYM SHF_ALLOC 此节区包含了动态链接符号表。
.fini SHT_PROGBITS SHF_ALLOC + SHF_EXECINSTR 此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码。
.got SHT_PROGBITS 此节区包含全局偏移表。
.hash SHT_HASH SHF_ALLOC 此节区包含了一个符号哈希表。
.init SHT_PROGBITS SHF_ALLOC + SHF_EXECINSTR 此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调用主程序入口之前(通常指 C 语言的 main 函数)执行这些代码。
.interp SHT_PROGBITS 此节区包含程序解释器的路径名。如果程序包含一个可加载的段,段中包含此节区,那么节区的属性将包含 SHF_ALLOC 位,否则该位为 0。
.line SHT_PROGBITS (无) 此节区包含符号调试的行号信息,其中描述了源程序与机器指令之间的对应关系。其内容是未定义的。
.note SHT_NOTE (无) 此节区中包含注释信息,有独立的格式。
.plt SHT_PROGBITS 此节区包含过程链接表(procedure linkage table)。
.relname .relaname SHT_REL SHT_RELA 这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。例如 .text 节区的重定位节区名字将是:.rel.text 或者 .rela.text。
.rodata .rodata1 SHT_PROGBITS SHF_ALLOC 这些节区包含只读数据,这些数据通常参与进程映像的不可写段。
.shstrtab SHT_STRTAB 此节区包含节区名称。
.strtab SHT_STRTAB 此节区包含字符串,通常是代表与符号表项相关的名称。如果文件拥有一个可加载的段,段中包含符号串表,节区的属性将包含SHF_ALLOC 位,否则该位为 0。
.symtab SHT_SYMTAB 此节区包含一个符号表。如果文件中包含一个可加载的段,并且该段中包含符号表,那么节区的属性中包含SHF_ALLOC 位,否则该位置为 0。
.text SHT_PROGBITS SHF_ALLOC + SHF_EXECINSTR 此节区包含程序的可执行指令。
so文件中重要的Section
-符号表(.dynsym)

符号表包含用来定位、重定位程序中符号定义和引用的信息,简单的理解就是符号表记录了该文件中的所有符号,所谓的符号就是经过修饰了的函数名或者变量名,不同的编译器有不同的修饰规则。例如符号_ZL15global_static_a,就是由global_static_a变量名经过修饰而来。

格式:

1
2
3
4
5
6
7
8
typedef struct {  
Elf32_Word st_name; //符号表项名称。如果该值非0,则表示符号名的字符串表索引(offset),否则符号表项没有名称。
Elf32_Addr st_value; //符号的取值。依赖于具体的上下文,可能是一个绝对值、一个地址等等。
Elf32_Word st_size; //符号的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。
unsigned char st_info; //符号的类型和绑定属性。
unsigned char st_other; //该成员当前包含 0,其含义没有定义。
Elf32_Half st_shndx; //每个符号表项都以和其他节区的关系的方式给出定义。此成员给出相关的节区头部表索引。
} Elf32_sym;

符号表的内容

image-20220303175513396

-字符串表(.dynstr)

字符串表中存放着所有符号的名称字符串

image-20220303175720635

-重定位表

重定位就是为程序不同部分分配加载地址调整程序中的数据和代码以反映所分配地址的过程。简单的言之,则是将程序中的各个部分映射到合理的地址上来。例如,当程序调用了一个函数时,相关的调用指令必须把控制传输到适当的目标执行地址。

重定位文件必须包含如何修改其节区内容的信息,从而允许可执行文件和共享目标文件保存进程的程序映象的正确信息。

重定位表项的格式:

1
2
3
4
5
typedef struct {  
Elf32_Addr r_offset; //重定位动作所适用的位置(受影响的存储单位的第一个字节的偏移或者虚拟地址)
Elf32_Word r_info; //要进行重定位的符号表索引,以及将实施的重定位类型(哪些位需要修改,以及如何计算它们的取值)
//其中 .rel.dyn 重定位类型一般为R_386_GLOB_DAT和R_386_COPY;.rel.plt为R_386_JUMP_SLOT
} Elf32_Rel;

r_info 成员使用 ELF32_R_TYPE 宏运算可得到重定位类型,使用 ELF32_R_SYM 宏运算可得到符号在符号表里的索引值

三种宏定义:

1
2
3
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define ELF32_R_INFO(s, t) (((s)<<8) + (unsigned char)(t))

重定位表中的内容

image-20220303180343936

Program Header Table

程序头部(Program Header)用来在文件中定位各个段的映像,同时包含其他一些用来为程序创建映像所必须的信息。

文件头部的格式:

1
2
3
4
5
6
7
8
9
10
typedef struct {  
Elf32_Word p_type; //此数组元素描述的段的类型,或者如何解释此数组元素的信息。
Elf32_Off p_offset; //此成员给出从文件头到该段第一个字节的偏移
Elf32_Addr p_vaddr; //此成员给出段的第一个字节将被放到内存中的虚拟地址
Elf32_Addr p_paddr; //此成员仅用于与物理地址相关的系统中。System V忽略所有应用程序的物理地址信息。
Elf32_Word p_filesz; //此成员给出段在文件映像中所占的字节数。可以为0。
Elf32_Word p_memsz; //此成员给出段在内存映像中占用的字节数。可以为0。
Elf32_Word p_flags; //此成员给出与段相关的标志。
Elf32_Word p_align; //此成员给出段在文件中和内存中如何对齐。
} Elf32_phdr;

程序头部表中的内容:

image-20220303181440086

so文件格式中结构体的某些参数未详细说明,可以去查看下面的这个参考文章。

参考文章:

https://blog.csdn.net/mergerly/article/details/94585901