ELF文件分析之0 - 简介和分析工具

0 前言

几年前,在读大学时,刚接触linux的时候,就对计算机的运行原理比较感兴趣,网上找了《CSAPP》和《程序员的自我修养-链接,库》,无奈当时能力不足,
看不懂这些,后面工作了,一直从事嵌入式偏底层的工作,比较熟悉gcc和linker,刚好最近研究hacker和工程需要,就耐着性子研究了ELF文件结构,遇到问题
不少,最终也解决了,所以写到blog记录一下,免得忘记

1 概述

因为ELF涉及的东西比较多,所以分为4篇来介绍

  1. ELF简介和分析工具
    主要介绍ELF的基本知识和讲解ELF分析工具的基本用法
  2. ELF文件格式分析
    主要介绍ELF文件格式
  3. ELF静态链接
    主要分析静态链接中需要解决的问题,以及基本原理
  4. ELF动态链接
    主要分析动态链接中需要解决的问题,以及基本原理

OK, 进入正题

2 ELF是什么

ELF(format of Executable and Linking Format Files),表示可执行文件格式。熟悉linux的同学会知道,我们经常会用./a.out来执行编译后的文件,这个a.out文件就是ELF格式的, 对应windows平台下的EXE文件(PE格式)

wiki上对此的介绍如下

1
In computing, the Executable and Linkable Format (ELF, formerly called Extensible Linking Format) is a common standard
file format for executables, object code, shared libraries, and core dumps. First published in the System V Release 4
(SVR4) Application Binary Interface (ABI) specification,[2] and later in the Tool Interface Standard,[1] it was quickly
accepted among different vendors of Unix systems. In 1999 it was chosen as the standard binary file format for Unix and
Unix-like systems on x86 by the 86open project.
ELF is flexible and extensible by design, and it is not bound to any particular processor or architecture. This has
allowed it to be adopted by many different operating systems on many different platforms.

详细来说,ELF涵盖下面三种文件

  1. 可执行文件
  2. 对象文件
  3. 库文件(*.so)

3 为什么要学习ELF格式

一般来说,对于应用开发来说,只需要能编译出对应的可执行文件就可以了。但稍微懂一些编译器和链接器的底层知识,能编程出更加优质高效的工具和防止黑客攻击。
当然,对于黑客来说,了解计算机原理和运行机制,ELF文件格式,能编写出更好的病毒或者攻击程序

魔高一尺,道高一丈

PS: 我学习ELF是想知道C语言到底是怎么运行的,以及了解病毒的原理

4 谁需要学习ELF

  1. hacker
  2. 病毒分析研究员
  3. 固件破解
  4. 底层程序员

注意:下文假设读者具有基本的编程知识,并熟悉Linux平台,所以某些linux的指令,不会详细介绍,如果您感到困惑,请留言给我

5 分析平台

ELF文件适用于linux架构或者ARM架构,具体平台很多,你可以用手头有的平台来分析,我尝试分析了STM32开发平台下ELF和树莓派系统下的ELF,
基本一致,差别不大

Linux平台比较普遍,也可以用虚拟机来分析,同时,这个平台下有丰富的工具可以用,所以接下来,我会用树莓派2B平台来分析ELF文件

平台: 树莓派2B,linux,ARM架构

6 常用工具用法

在分析之前,我们需要生成对应的可执行文件和对象文件,所以,先简单的介绍工具用法,然后再用实例来巩固

在分析ELF的之前,我们需要建立两个源代码文件

  1. main.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <stdio.h>

    extern int m;
    extern int f_add(int a, int b);

    int main(void)
    {

    m = f_add(1, 2);
    printf("sum = %d\r\n", m);
    return 0;
    }
  2. fun.c

    1
    2
    3
    4
    5
    6
    int m;

    int f_add(int a, int b)
    {

    return a + b;
    }

6.1 gcc

GCC用来编译,生成我们需要的文件,在分析ELF的时候,我们只需要几条指令就可以了

  1. 编译obj文件
    使用-c参数可以只编译,不链接,比如,我们希望将fun.c编译成fun.o,输入的指令为

    1
    gcc -c -g -O0 fun.c
  2. 编译so文件
    使用-shared -fPIC参数可以将文件编译成动态库,指令如下

    1
    gcc -o libfun.so -g -O0 -shared -fPIC fun.c

    特别注意:输出文件的名字,需要符合标准库名称规范libxxx.so,xxx为具体的lib名字,从而便于链接时正确找到lib文件

  3. 编译可执行文件
    这里可以分为3种情况来考虑

    1. 源文件编译

      1
      gcc -g -O0 main.c fun.c #编译成可执行文件
    2. 链接obj文件

      1
      gcc -g -O0 main.o fun.o #编译成可执行文件
    3. 链接so文件

      1
      2
      cp libfun.so /usr/lib/     #将自己的库复制到系统lib目录下
      gcc -g -O0 main.c -lfun #编译成可执行文件

6.2 gdb

我们需要使用GDB进行动态调试和查看GOTPLT段,所以要熟悉一些常用命令,比如:反汇编,查看内存块,显示寄存器值等

我会在下面代码分析的时候,插入介绍这些GDB命令

6.3 objdump

主要用来反汇编,常用命令也就两条

  1. 反汇编代码段

    1
    2
    objdump -d fun.o    # 反汇编fun.o中的代码段
    objdump -d main.o # 反汇编main.o中的代码段
  2. 反汇编所有段

    1
    2
    objdump -D fun.o    # 反汇编fun.o中的所有段
    objdump -D main.o # 反汇编main.o中的所有段

6.4 readelf

主要用来elf文件里面的section, header等信息,常用指令如下

  1. 读取所有信息

    1
    readelf -a fun.o    # 读取fun.o中的所有elf信息
  2. 读取header信息

    1
    readelf -h fun.o    # 读取fun.o中的所有elf header信息
  3. 查看readelf帮助信息

    1
    readelf -h

7 基础知识

在介绍ELF文件结构之前,先复习一下C语言环境编译和链接的基本过程

在嵌入式工程中,可以用C/C++来编写程序,编写完成后,编译代码,生成bin或者hex,然后下载到芯片中,进行调试。
也就是:C源文件->ASM文件->obj文件->elf文件->bin文件
上面的过程是文件级别的,在C语言层次上,我们关心数据的分布,函数的调用和内存分布,以及怎么将这些元素变成BIN文件

现代计算机普遍采用的是哈弗结构,也就是将数据和代码分离的形式,所以,我们可以知道bin里面也包含了数据部分和代码部分,某些情况下, bin也会包含有一些调试信息和备注信息

就数据部分来说,C语言里面有全局变量,局部变量,文件作用域变量/函数,局部变量分配在栈里面,属于动态分配机制;C语言默认符号熟悉为public,也就是具有全局属性,对外显示可见,外部可以通过extern来导入这个符号,这类变量会导出到模块符号表,并具有PUBLIC属性;文件作用域的
符号包括:static修饰过的变量/static修饰过的函数,他们被分配LOCAL属性,对外不可见,C语言利用这个特性,可以编写模块化程序,只对外暴漏
必要的接口符号信息

对于变量来说,可以细分为初始化/未初始化变量,未初始化的变量放置于.bss段中,初始化的变量放置于.data段中,某些情况下,我们需要使用大量的常量数据(比如图片数据,字库),可以将这些放置于.text段中

上面的描述中,有几个名词,符号表.data段,刚开始可以不用深入理解,只需要知道他们表示同一类数据就好了

ELF的存在意义,就是为了

  1. 更好的管理这些基本数据段
  2. 实现更强大的调试功能
  3. 便于工具链分析和处理,生成固件

我们在下一篇来介绍ELF的基本格式信息

8 注意事项

  1. 在反编译时,objdump或者readelf这类工具,会将某些数据段反编译成代码,这时,就需要我们使用gdb命令来直接查看内存区域
  2. gdb中手工反汇编内存段的命令是
    1
    disassemble addr1,addr2

网上某些文档中的地址指令之间没有加,号,属于错误的,无效命令

9 参考文档

  1. ELF规范1.1
  2. GCC手册