0x00  前言

  最近一段时间在弄android方面的东西,今天有人发了张截图,问:在要dump多大的内存?

  

一时之间我竟然想不起来ELF文件的哪个字段表示的是文件大小,虽然最后给出了解决方法,IDA CTRL+S,直接看Segements信息,可以得出整个文件的大小。但说明了自己对于ELF文件格式远不如PE文件那么熟悉,毕竟看PE文件格式是抱着《加密与解密》来来回回看了好几遍,而对于ELF文件只是在网上随便找了几遍博客,现在还记得大致的结构,对于一些具体的细节却并已经遗忘的差不多。于是想自动动手写篇关于ELF文件格式的文章,加深自己对ELF文件的理解和记忆。关于整个系列的文章中的名词都使用的是英文原名,避免产生歧义,就比如说section和segment,可能各个书中翻译过来都存在差异,对于一些专有名词,我个人还是比较喜欢阅读英文,也不必翻译。

0x01 ELF文件

关于ELF文件的起源,官方的一些东西就不在进行缀述了。直奔主题,先从elf文件的三种类型讲起,elf文件分为三种类型:

1) 可重定位的对象文件(Relocatable file),也就是平常说的目标文件,后缀为.o;

2) 可执行的对象文件(Executable file);

3) 可被共享的对象文件(Shared object file)。

而我们的主要精力就放在第3种,可被共享的对象文件上,也就是所谓的动态库文件,即.so文件,类似于Windows下的dll文件。而动态库文件在发挥作用的都要经过两个过程

①编译阶段。链接编辑器(link editor)拿它和其他Relocatable object file以及其他shared object file作为输入,经链接处理后,生存另外的 shared object file 或者 executable file。

②运行阶段。动态链接器(dynamic linker)拿它和一个executable file以及另外一些 shared object file 来一起处理,在Linux系统里面创建一个进程映像。

下面是elf文件的两种view,linking view 和execution view,Linking View主要是为了给Linker使用,而Execution View是为了给Loader使用。而我们关心的.so文件可以是这两种View并存的,只不过是同一个文件的两种划分方式,这样load完还可以relocate,而.so文件仅仅只是用来执行时,是允许没有section的,而关于section与segment的关系,相同属性的section会被映射到同一个segment中。

ELF Header结构的定义如下,各个字段的含义都有注释:

/* ELF Header */
typedef struct elfhdr {
unsigned char e_ident[EI_NIDENT]; /* ELF Identification */
Elf32_Half e_type; /* object file type */
Elf32_Half e_machine; /* machine */
Elf32_Word e_version; /* object file version */
Elf32_Addr e_entry; /* virtual entry point */
Elf32_Off e_phoff; /* program header table offset */
Elf32_Off e_shoff; /* section header table offset */
Elf32_Word e_flags; /* processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size */
Elf32_Half e_phentsize; /* program header entry size */
Elf32_Half e_phnum; /* number of program header entries */
Elf32_Half e_shentsize; /* section header entry size */
Elf32_Half e_shnum; /* number of section header entries */
Elf32_Half e_shstrndx; /* section header table's "section
header string table" entry offset */
} Elf32_Ehdr;

关于每个字段的含义后面的注释都很清楚,唯一需要说明的一点就是e_shstrndx,这个表示的是.shstrtab section的section header在section header table中的索引,或者恰当的说是偏移。

接下来就是两个header table ,Section header table 和 Program header table。下面分别介绍这两个table的entry的结构,先是section header table entry的结构

Section Header Table 

/* Section Header */
typedef struct {
Elf32_Word sh_name; /* name - index into section heade string table section */
Elf32_Word sh_type; /* type */
Elf32_Word sh_flags; /* flags */
Elf32_Addr sh_addr; /* address */
Elf32_Off sh_offset; /* file offset */
Elf32_Word sh_size; /* section size */
Elf32_Word sh_link; /* section header table index link */
Elf32_Word sh_info; /* extra information */
Elf32_Word sh_addralign; /* address alignment */
Elf32_Word sh_entsize; /* section entry size */
} Elf32_Shdr;

这里简要的介绍下各个字段的含义

sh_name:  该Section的名字,类型是Elf32_Word,指向在串表中的偏移值;

sh_type: 按内容和意义将section分类,下面给出不同的值代表的不同的含义;

关于sh_type的类型,值和表示的含义如下表:

SHT_NULL

0

section头是无效的;它没有相关的section

SHT_PROGBITS

1

该section保存被程序定义了的一些信息,它的格式和意义取决于程序本身

SHT_SYMTAB

2

保存着符号表

SHT_STRTAB

3

保存着一个字符串表

SHT_RELA

4

保存着具有明确加数的重定位入口

SHT_HASH

5

保存着一个标号的哈希(hash)表

SHT_DYNAMIC

6

保存着动态连接的信息

SHT_NOTE

7

保存着其他的一些标志文件的信息

SHT_NOBITS

8

在文件中不占空间,sh_offset成员包含了概念上的文件偏移量

SHT_REL

9

保存着具有明确加数的重定位的入口

SHT_SHLIB

10

类型保留但语意没有指明,包含这个类型的section的程序是不符合ABI的规定

SHT_DYNSYM

11

保存着符号表

SHT_LOPROC

0x70000000

在这范围之间的值为特定处理器语意保留

SHT_HIPROC

0x7fffffff

SHT_LOUSER

0x80000000

为应用程序保留的索引范围的最小边界

SHT_HIUSER

0xffffffff

为应用程序保留的索引范围的最大边界

sh_flags:  表示该section的类型,下表包含了各个类型的解释;

关于sh_flags的值与含义:

SHF_WRITE         0x1      该section包含了在进程执行过程中可被写的数据
SHF_ALLOC         0x2      该section在进程执行过程中占据着内存
SHF_EXECINSTR        0x4      该section包含了可执行的机器指令
SHF_MASKPROC   0xf0000000      为特定处理语意保留的

sh_addr:  表示该section在内存中,相对于基址的偏移;

sh_offset:  表示该section到文件头部的字节偏移;

sh_size:  该section大小;

sh_link:  表示与当前section有link关系的section索引;

sh_info:  一些附加信息;

sh_addralign:  section的地址对齐;

sh_entsize:  section项的大小(bytes)。

讲了那么多也就只是原理的性的东西,为了加深理解,用一个实际的例子。

最后的一个字符串表索引节头就是之前提过的Elf32_Hder中的e_shstrndx成员,来看shstrtab的内容:

我们分析一个特定的节,选择.mytext节

开始的4个字节00 00 00 39 就是“.mytext”字符串在shstrtab中的偏移量(注意大小端的问题,这里是小端存储)。

这里给大家“安利”一个GitHub的开源项目010Editor-stuff,关于各种文件解析模板,超级好用。

还有就是关于android下so文件的问题:

这个是位于/obj/local/armeabi/目录下的so文件的section header :

下面是位于/libs/armeabi/下的so文件,也就是打包进apk的文件;

对比一下,发现很多的section都被删除了,主要是有关debug和.symtab,.strtab section 都被删除了,也就是说android下打包进apk的so文件只支持动态链接。

Program Header Table

接下来就Program header table entry的结构:

typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment offset in file */
Elf32_Addr p_vaddr; /* Segment virtual address in memory*/
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;

每个Segment包含若干个section,只有对executable file和shared object file才能存在Program header。

p_type:表示该Program header所指的Segment的类型

PT_NULL 0 该数组元素未使用;其他的成员值是未定义的
PT_LOAD 1 该数组元素指定一个可载入的段,由 p_filesz 和 p_memsz 描述
PT_DYNAMIC  2 该数组元素指定动态链接信息
PT_INTERP 3 该数组元素指定辅助信息的位置和大小
PT_NOTE 4 该段类型保留且具有未指定的语义,具有一个这种类型数组元素的程序并不遵守ABI 。
 PT_SHLIB  5 该段类型保留且具有未指定的语义
PT_PHDR 6

该数组元素(如果出现),指定了程序头表本身的位置和大小(包括在文件中
和在该程序的内存映像中)

PT_LOPROC 0x70000000 保留用于特定处理器的语义
PT_HIPROC 0x7fffffff

p_offset:表示该Program header所指的Segment在文件中偏移量。

p_vaddr:表示该Program header所指的Segment在内存中的虚拟地址。

p_vaddr:表示该Program header所指的Segment在内存中的物理地址,一般可忽略。

p_filesz:表示该Program header所指的Segment在文件中的大小。

p_memsz:表示该Program header所指的Segment在内存中的大小,一般地,p_memsz>=p_filesz,对p_filesz不足的内存区域填0。

最后作为结尾写段代码加深对个格式的理解:

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#ifdef __x86_64
#define Elf_Ehdr Elf64_Ehdr
#define Elf_Shdr Elf64_Shdr
#define Elf_Sym Elf64_Sym
#define Elf_Rel Elf64_Rela
#define ELF_R_SYM ELF64_R_SYM
#define REL_DYN ".rela.dyn"
#define REL_PLT ".rela.plt"
#else
#define Elf_Ehdr Elf32_Ehdr
#define Elf_Shdr Elf32_Shdr
#define Elf_Sym Elf32_Sym
#define Elf_Rel Elf32_Rel
#define ELF_R_SYM ELF32_R_SYM
#define REL_DYN ".rel.dyn"
#define REL_PLT ".rel.plt"
#endif #define LOG(...) printf(__VA_ARGS__); static uint32_t get_module_base(pid_t pid, const char *module_path) {
FILE *fp = NULL;
char *pch = NULL;
char filename[];
char line[];
uint32_t addr = ; LOG("[+] get libc base...\n");
if (pid < )
snprintf(filename, sizeof(filename), "/proc/self/maps");
else
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid); if ((fp = fopen(filename, "r")) == NULL) {
LOG("[-]open %s failed!", filename);
return ;
} while (fgets(line, sizeof(line), fp)) {
if (strstr(line, module_path)) {
pch = strtok(line, "-");
addr = strtoul(pch, NULL, );
break;
}
} fclose(fp);
LOG("[+] libc base:0x%x...\n",addr); return addr;
} int read_header(int d, Elf_Ehdr **header)//read elf header structure
{
*header = (Elf_Ehdr *)malloc(sizeof(Elf_Ehdr)); if (lseek(d, , SEEK_SET) < )//seek to the begin of file
{
free(*header); return errno;
} if (read(d, *header, sizeof(Elf_Ehdr)) <= )//read from begin,read sizof(Elf_Ehdr) bytes ==> header
{
free(*header); return errno = EINVAL;
} return ;
} static int read_section_table(int d, Elf_Ehdr const *header, Elf_Shdr **table)//read elf header,find section header base address
{
size_t size; if (NULL == header)
return EINVAL; size = header->e_shnum * sizeof(Elf_Shdr);//section numbers and total size
*table = (Elf_Shdr *)malloc(size); if (lseek(d, header->e_shoff, SEEK_SET) < )//point to section header,offset 0
{
free(*table); return errno;
} if (read(d, *table, size) <= )//read section header structure to **table
{
free(*table); return errno = EINVAL;
} return ;
} static int read_string_table(int d, Elf_Shdr const *section, char const **strings)
{
if (NULL == section)//section == > .dynstr section
return EINVAL; *strings = (char const *)malloc(section->sh_size); if (lseek(d, section->sh_offset, SEEK_SET) < )
{
free((void *)*strings); return errno;
} if (read(d, (char *)*strings, section->sh_size) <= )//strings include all strings in .dynstr sections
{
free((void *)*strings); return errno = EINVAL;
} return ;
} int main()
{
LOG("[+]Arm ELF32 reader...\n");
uint32_t lic_base = get_module_base(-,"/system/lib/libc.so");
int descriptor = open("/system/lib/libc.so", O_RDONLY);//open libc.so,and return the handle
Elf_Ehdr *header = NULL;//elf header
Elf_Shdr *section_header = NULL;//section header array ptr
char const *strings = NULL;//string table ptr
read_header(descriptor,&header);
LOG("[+]libc.so elf header:\n");
LOG("[+]e_ident[EI_NIDENT]: %s\n",header->e_ident);
LOG("[+]e_type:%d(ET_DYN:%d,DYN (Shared object file))\n",header->e_type,ET_DYN);
LOG("[+]e_machine:%d(EM_ARM:%d,Advanced RISC Machines)\n",header->e_machine,EM_ARM);
LOG("[+]e_shoff:%d bytes\n",header->e_shoff); LOG("[+]libc.so section header:\n");
read_section_table(descriptor,header,§ion_header);
read_string_table(descriptor,§ion_header[header->e_shstrndx], &strings);//header->e_shstrndx ==>the index of string section header in section headers
int i = ;
for(i = ;i<header->e_shnum;++i)
{
LOG("Section[%d] name:%s,type:%d,addr:0x%x,offset:0x%x,size:%dbytes,etc...\n",i,&strings[section_header[i].sh_name],section_header[i].sh_type,section_header[i].sh_addr,section_header[i].sh_offset,section_header[i].sh_size);
}
close(descriptor);
return ;
}

0x02 小结

只是总结了ELF文件的大体的结构,对于每个section的结构并没有深入,之后的学习会更加深入elf文件的学习,主要聚焦在android 下的.so文件,还会加上一些关于 so加密的知识。

Android so文件进阶 <一>的更多相关文章

  1. Android so 文件进阶<三> so文件的简单加密

    0x00  前言 之前的两篇文章从链接视图和执行视图分析了elf文件的大致结构,这篇文章主要内容是对于so文件进行简单的加密工作,针对Ida等静态分析工具的分析,一旦开始动态调试就应该很容易就可以du ...

  2. Android so 文件进阶<二> 从dlsym()源码看android 动态链接过程

    0x00  前言 这篇文章其实是我之前学习elf文件关于符号表的学习笔记,网上也有很多关于符号表的文章,怎么说呢,感觉像是在翻译elf文件格式的文档一样,千篇一律,因此把自己的学习笔记分享出来.dls ...

  3. Android JNI入门第四篇——Android.mk文件分析

    ndroid.mk文件是在使用NDK编译C代码时必须的文件,Android.mk文件中描述了哪些C文件将被编译且指明了如何编译.掌握Android.mk文件的编写主要是掌握其里头将要使用的一些关键字, ...

  4. Android高级架构进阶之数据传输与序列化

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从以下几个内容来阐述数据传输与序列化: [Serializable原理] ...

  5. Android.mk 文件语法详解

    0. Android.mk简介: Android.mk文件用来告知NDK Build 系统关于Source的信息. Android.mk将是GNU Makefile的一部分,且将被Build Syst ...

  6. Android根据文件路径使用File类获取文件相关信息

    Android通过文件路径如何得到文件相关信息,如 文件名称,文件大小,创建时间,文件的相对路径,文件的绝对路径等: 如图: 代码: public class MainActivity extends ...

  7. Android资源文件简介

    Android资源文件简介 1. Android应用资源的作用 (1) Android项目中文件分类 在Android工程中, 文件主要分为下面几类 : 界面布局文件, Java src源文件, 资源 ...

  8. 无废话Android之android下junit测试框架配置、保存文件到手机内存、android下文件访问的权限、保存文件到SD卡、获取SD卡大小、使用SharedPreferences进行数据存储、使用Pull解析器操作XML文件、android下操作sqlite数据库和事务(2)

    1.android下junit测试框架配置 单元测试需要在手机中进行安装测试 (1).在清单文件中manifest节点下配置如下节点 <instrumentation android:name= ...

  9. android 获取文件夹、文件的大小 以B、KB、MB、GB 为单位

    android 获取文件夹.文件的大小 以B.KB.MB.GB 为单位   public class FileSizeUtil { public static final int SIZETYPE_B ...

随机推荐

  1. POJ2385--Apple Catching(动态规划)

    It is a little known fact that cows love apples. Farmer John has two apple trees (which are convenie ...

  2. 第32讲:List的基本操作实战与基于模式匹配的List排序算法实现

    今天来学习一下list的基本操作及基于模式匹配的排序操作 让我们从代码出发 val bigData = List("hadoop","spark") val d ...

  3. POJ1644状态转移的思想——排列组合

    m个物品放n个盒子,盒子物品都相同,问你放的方法总数是多少 看着像个排列组合,算着算着就发现我排列组合都忘得差不多啦,哎,什么时候能打败遗忘呢 然后想用dp做,但是转移的方面没有想好 看了看题解感觉这 ...

  4. 安装部署Ceph Calamari

    根据http://ovirt-china.org/mediawiki/index.php/%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2Ceph_Calamari 原文如下: ...

  5. string转Date转回String(JAVA)

    String dateString = "2012-12-06 ";   SimpleDateFormat sdf = new SimpleDateFormat("yyy ...

  6. MySql and Oracle Data Type Mappings

    the default settings used by SQL Developer to convert data types from MySQL to Oracle. SQL Developer ...

  7. 二、RHCSA试题解析

    一.设置YUM仓库 YUM的软件库源地址为:http://content.example.com/rhel7.0/x86_64/dvd,将此配置为操作系统的默认软件仓库. 方法一(修改配置文件): v ...

  8. 在ASP.NET Core中,静态类如何读取配置文件

    这是今天下午一个同事问我的问题,如何在静态类中读取json配置文件.我当时并没有告诉他如何如何去做,办法肯定是有,但是这种编程思维确实得改改了.静态类.静态方法不是面向对象编程的最佳实践..NET C ...

  9. Lerning Entity Framework 6 ------ Using a commandInterceptor

    Sometimes, We want to check the original sql statements. creating a commandInterceptor is a good way ...

  10. MariaDB 插入&更新&删除数据(8)

    MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可MariaDB的目的是完全兼容MySQL,包括API和命令行,MySQL由于现在闭源了,而能轻松成为MySQ ...