转自:
https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/>
因为项目的需要,我对Android系统加载.so文件有一些些研究,把最近看过的一些大牛的分析和现状结合一下,
写篇东西做一下笔记。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#so-%E6%96%87%E4%BB%B6%E6%98%AF%E4%BB%80%E4%B9%88>
.so 文件是什么
.so 文件是 Shared Object 文件的后缀,
直白的说就是Linux系统中的“动态链接库” ,
就和Windows系统下的 .dll 文件(Dynamic Link Library)类似。
.so 文件实则是 ELF 格式的文件,和Linux的可执行文件的格式一致。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#ELF-%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84>
ELF 文件结构
ELF 格式的文件中的“数据”实际上是以“段”(节,英文:Section)的形式存储的。
其顺序大致符合下面的排序:
* ELF Header:ELF头部
* .dynsym:保存动态链接相关符号,记录其偏移值
* .dynstr:.dynsym 的辅助段
* .hash:快速查找符号的哈希表,类似 .dynstr
* .rel.got:数据引用修正,修正到 .got
* .rel.plt:函数引用修正,修正到 .got.plt
* .text:代码段
* 其他自定义的代码段
* .rodata:字符串常量段
* .fini_array:终止函数段
* .init_array:初始化函数段
* .dynamic:动态链接库特有,存储动态链接用到的表信息
* .got:函数的绝对地址
* .data:存放已经初始化的全局变量,静态内存分配相关
* .bss:存放未初始化的全局变量,静态内存分配相关
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#ELF-%E5%A4%B4%E9%83%A8%EF%BC%9AELF-Header>
ELF 头部:ELF Header
其中,一切的起点都在ELF头部,其偏移量(offset)为 0。
ELF头部的结构体为 elf32_hdr 或 elf64_hdr,
在Android系统源代码的 /bionic/libc/kernel/uapi/linux/elf.h 可以找到。
以32位程序的ELF头部为例:
06#define EI_NIDENT 16typedef struct elf32_hdr { unsigned char
e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word
e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word
e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum;
Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx;} Elf32_Ehdr;
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-ident>
e_ident
ELF格式文件的识别区域,固定为 16Bytes 的字符串。
下面是这串字符串各个字节的含义。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#EI-MAG>
EI_MAG
* Offset: 0 - 3 (EI_MAG0)
* Length: 4 (SELFMAG)
* Type: String
ELF Identification 的前四个字节为魔数(Magic Number),
内容为 {0x7F, 'E', 'L', 'F'}。
如果魔数不一致,在Android系统会报错“has bad ELF magic”,
终止 load_library并APP闪退。
相关代码可以查询源码 /xref/bionic/linker/linker_phdr.cpp
<http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker_phdr.cpp>
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#EI-CLASS>
EI_CLASS
* Offset: 4 (EI_CLASS)
* Length: 1
* Type: Number
判断 ELF 文件是 32位的 还是 64位的。
常量定义:
* ELFCLASSNONE = 0:无定义【非法】
* ELFCLASS32 = 1:32位
* ELFCLASS64 = 2:64位
* ELFCLASSNUM = 3:未知【非法】
在Android <5 的系统上,由于当年的系统不支持64位的指令集,
因此只要不是32位,就输出错误 “not 32-bit”,并APP闪退。
在Android >=5 系统上,已经出现了64位的指令集
,如 arm64_v8a、x86_64。
若32位的指令集遇到64位的SO库,
会输出错误 “is 64-bit instead of 32-bit”,
并APP闪退;
若32位的指令集遇到64位的SO库,
会输出错误 “is 32-bit instead of 64-bit”,
并APP闪退;
若出现非法的 ELFCLASS,
会输出错误 “has unknown ELF class: ?”,
并APP闪退。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#EI-DATA>
EI_DATA
* Offset: 5 (EI_DATA)
* Length: 1
* Type: Number (unsigned char)
这个是判断 ELF文件是 LSB(Little-endian,低字节序)
还是 MSB(Big-endian,高字节序)。
常量定义:
* ELFDATANONE = 0:无定义【非法】
* ELFDATA2LSB = 1:LSB
* ELFDATA2MSB = 2:MSB【非法】
安卓系统只允许 LSB,因此只要不是 1,
就输出错误 “not little-endian”,
并APP闪退。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#EI-VERSION>
EI_VERSION
* Offset: 6
* Length: 1
* Type: Number (unsigned char)
顾名思义,是 ELF 文件格式的版本号,默认是 EV_CURRENT(= 1)。
Android系统不从这里检测 Version,而在 e_version 上检测,因此修改无影响。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#EI-OSABI>
EI_OSABI
* Offset: 7
* Length: 1
* Type: Number (unsigned char)
指出来该 ELF 文件可以在什么操作系统运行,参考:
#define ELFOSABI_NONE 0 /* UNIX System V ABI */#define ELFOSABI_SYSV 0 /*
Alias. */#define ELFOSABI_HPUX 1 /* HP-UX */#define ELFOSABI_NETBSD 2 /*
NetBSD. */#define ELFOSABI_LINUX 3 /* Linux. */#define ELFOSABI_SOLARIS 6 /*
Sun Solaris. */#define ELFOSABI_AIX 7 /* IBM AIX. */#define ELFOSABI_IRIX 8 /*
SGI Irix. */#define ELFOSABI_FREEBSD 9 /* FreeBSD. */#define ELFOSABI_TRU64 10
/* Compaq TRU64 UNIX. */#define ELFOSABI_MODESTO 11 /* Novell Modesto. */
#define ELFOSABI_OPENBSD 12 /* OpenBSD. */#define ELFOSABI_ARM_AEABI 64 /* ARM
EABI */#define ELFOSABI_ARM 97 /* ARM */#define ELFOSABI_STANDALONE 255 /*
Standalone (embedded) application */
Android系统下的 SO文件 此处的值默认为 0,
而且加载时不检测,修改无影响。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#EI-ABIVERSION>
EI_ABIVERSION
* Offset: 8
* Length: 1
* Type: Number (unsigned char)
指出该 ELF 文件可以在哪个API版本下运行,Android下的默认值是 0。
此处加载时不检测,修改无影响。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#EL-PAD>
EL_PAD
* Offset: 9
* Length: 7
填充位,无检测,修改无影响。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#%E6%8A%80%E5%B7%A7>
技巧
Android >= 6 的系统版本只针对 0x00 - 0x05 之间的字节进行检测,
0x06 - 0x0F 之间的字节(10 Bytes)无检测,
因此可以任意修改,存放想要存放的数据。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-type>
e_type
* Offset: 0x10
* Length: 2
* Type: unsigned short
ELF文件类型,如:
#define ET_NONE 0 /* 无定义【非法】 */#define ET_REL 1 /* 已编译未链接 */#define ET_EXEC 2
/* 已编译已链接的可执行程序 */#define ET_DYN 3 /* 已编译已链接的动态链接库 */
ET_REL 指的是 Relocatable file,
缺少 Program Header,
不可以加载到内存中,
gcc 编译时候生成的 .o 文件就是 REL文件。
ET_EXEC 指的是可执行程序,
存在程序入口,
有 Program Header,
可以加载到内存中运行,
在 Linux 下的可执行程序都是这样的。
ET_DYN 特指动态链接库。
由于Android的SO库本质就是动态链接库,
因此SO库编译后 e_type = ET_DYN。
Android系统会检测 e_type。
若不为 ET_DYN,则抛出错误 has unexpected e_type,
并APP闪退。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-machine>
e_machine
* Offset: 0x12
* Length: 2
* Type: unsigned short
ELF 文件的CPU平台属性(指令集)。
参考:
#if defined(__arm__) return EM_ARM;#elif defined(__aarch64__) return
EM_AARCH64;#elif defined(__i386__) return EM_386;#elif defined(__mips__) return
EM_MIPS;#elif defined(__x86_64__) return EM_X86_64;#endif
Android系统会使用 GetTargetElfMachine 函数
检测SO库的 e_machine 和现在的系统是否一致。
若不一致,则抛出错误 has unexpected e_machine,
并APP闪退。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-version>
e_version
* Offset: 0x14
* Length: 4
* Type: unsigned int
顾名思义,是 ELF 文件格式的版本号,默认是 EV_CURRENT(= 1)。
Android系统会检测 e_version。
若不为 EV_CURRENT,则抛出错误 has unexpected e_version,
并APP闪退。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-entry>
e_entry
* Offset: 0x18
* Length: 4 (32bits)
* Type: unsigned int
ELF程序的入口虚拟地址。仅用于可执行程序加载完成后,从此处开始执行进程指令。
动态链接库不存在入口地址,所以Android系统不检测。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-phoff>
e_phoff
* Offset: 0x1C
* Length: 4 (32bits)
* Type: unsigned int
程序头表的偏移,涉及“连接视图”和“执行视图”。
与实际SO库中代码的指令执行相关,不允许修改。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-shoff>
e_shoff
* Offset: 0x20
* Length: 4 (32bits)
* Type: unsigned int
段表在文件中的偏移,涉及读取段表。
Android <7 时,读取段表依靠视图,使用的是 e_phoff,而非 e_shoff,
因此可以随意修改。
涉及 linker_phdr.cpp 的 phdr_table_get_dynamic_section 函数
Android >=7 时,读取段表依靠 e_shoff,
修改为其他值会导致无法定位 .dynamic 段,
抛出错误 “.dynamic section header was not found”。
涉及 linker_phdr.cpp 的 ElfReader::ReadDynamicSection 函数
为什么要强调这个?因为看雪里面的这个文章 <https://bbs.pediy.com/thread-191649-1.htm>
是不兼容新的OS的,内容过时了。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-flags>
e_flags
* Offset: 0x24
* Length: 4 (32bits)
* Type: unsigned int
ELF标志位,用来标志一些ELF文件平台相关的属性。
Android 系统不使用也不检测此参数。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-ehsize>
e_ehsize
* Offset: 0x28
* Length: 2
* Type: unsigned short
ELF头部的长度。
Android 系统不使用也不检测此参数。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-phentsize>
e_phentsize
* Offset: 0x2A
* Length: 2
* Type: unsigned short
程序头的大小。
Android 系统不使用也不检测此参数。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-phnum>
e_phnum
* Offset: 0x2C
* Length: 2
* Type: unsigned short
在执行视图中,Segments的数量。
Android 系统对其进行检测,并且严格到实际的数目。
若不一致,则抛出错误 “has invalid e_phnum”、“has invalid phdr offset/size”
或者 “phdr mmap failed”等。
涉及函数 ElfReader::ReadProgramHeaders 和 ElfReader::ReadProgramHeader。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-shentsize>
e_shentsize
* Offset: 0x2E
* Length: 2
* Type: unsigned short
段表描述符的大小,= sizeof(ElfW(Shdr))。
Android <= 6 系统不使用也不检测此参数。
Android >= 7 系统检测此参数。
若与 sizeof(ElfW(Shdr)) 不相等,
则抛出错误 “has unsupported e_shentsize”。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-shnum>
e_shnum
* Offset: 0x30
* Length: 2
* Type: unsigned short
段表描述符的数量。这个值等于ELF文件中拥有的段(section)的数量。
Android <= 6 系统不使用也不检测此参数。
Android >= 7 系统检测并使用此参数。
若为0,则抛出错误 “has no section headers”。
若超出文件大小范围,则抛出错误 “has invalid shdr offset/size”。
参考函数 ElfReader::ReadSectionHeaders。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#e-shstrndx>
e_shstrndx
* Offset: 0x32
* Length: 2
* Type: unsigned short
段表字符串表所在的段在段表中的下标,一般是 = e_shnum - 1。
Android <= 6 系统不使用也不检测此参数。
Android >= 7 系统检测此参数。
若为0,则抛出错误 “has invalid e_shstrndx”。
参考函数 ElfReader::VerifyElfHeader。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#%E5%81%B7%E5%B0%B1%E5%AE%8C%E4%BA%8B%E4%BA%86>
偷就完事了
在ELF头部,有这些地方可以疯狂偷空间:
* 0x06 - 0x0F 合计 10Bytes
* 0x18 - 0x1B 合计 4Bytes
* 0x24 - 0x2B 合计 8Bytes
无限制存储空间合计为 22Bytes。
以下地方,只有Android <=6 才可以偷空间存东西:
* 0x20 - 0x23 合计 4Bytes
* 0x2E - 0x33 合计 6Bytes
存在版本限制存储空间合计为 10Bytes。
为什么要偷空间出来存数据?
主要还是为了方便SO库的打包工具做一些附加数据的存储,
偷出来的地方是随SO库加载而加载,可以在内存直接读取的。
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#%E5%80%BC%E5%BE%97%E5%AD%A6%E4%B9%A0>
值得学习
Android 系统源码提供了很多很好玩的设计思路,下面展示一些:
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#Code-A>
Code A
根据不同的指令集,使用不同的结构体:
// 如果是 64bits 的系统#if defined(__LP64__)#define ElfW(type) Elf64_ ## type#else
#define ElfW(type) Elf32_ ## type#endif// ElfW(Ehdr) === Elf32_Ehdr
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/#Code-B>
Code B
一些关于指令集的宏定义:
// __arm__ -> armeabi, armeabi-v7a [32bits]// __aarch64__ -> arm64-v8a [64bits]
// __i386__ -> x86 [32bits]// __mips__ -> mips, mips64 [32 / 64bits]//
__x86_64__ -> x86_64 [64bits]#if defined(__arm__) printf("This is ARM
Architecture!");#endif
除非注明,麦麦小家文章均为原创,转载请以链接形式标明本文地址。
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
<http://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh>
本文地址:https://blog.micblo.com/2018/02/10/Android-SO库文件头分析/
<https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/>
热门工具 换一换