elf文件格式实例解析

 

试验环境:archlinux 速龙3000+(即x86兼容32位处理器)

必须软件:gcc binutils

参考资料:

System V application binary interface

ELF Format (mirror txt format )

Hello,world in less than 20 bytes

Tutorial on creating teensy ELF file on linux (中文翻译版本 ,also see (smallest elf32 hello,world ))

Introduction to reverse engineering on linux   (also see crackz reverse engineering page(windows),resources )

Deconstructing an ELF file

The ELF virus writing howto

Playing with binary format

ELF hackery (many links)

ELF or assembly reference (on skyeye)

linkers and loaders

linkers(part 1 ,part 2 , part 3 , part 4 , part 5 , part 6 , part 7 , part 8 ,part 9 ,part 10

      part 11 ,part 12 , part 13 , part 14 , part 15 , part 16 , part 17 , part 18 , part 19 , part 20 )

hacker's wisdom

还可用百度搜索"ELF site:ibm.com",能搜索到很多关于ELF中文翻译教程,其中文后的参考文献也很值得看。

pe(window下的库文件和可执行文件格式)相关链接可以在wikipedia上找到 。

 

ELF 文件分为三类:(1)可重定位文件(目标文件或者静态库文件,即linux通常后缀为.a和.o的文件) (2)可执行文件(即可以运行的二进制文件,例如bash,gcc等)(3)共享目标文件(即linux下后缀为so的文件)。Elf文件格式(参见System V Application Binary interface 第46页)的布局如下:

 

-----------------------------

ELF文件头(即ELF Header)

----------------------------

程序文件头表(Program header table)

-----------------------------------

Section 1

-----------------------------------

     ...

-----------------------------------

Section n

----------------------------------

     ...

----------------------------------

段表(Section header table)

-----------------------------------

 

其中程序文件头表对于可重定位文件是可选项(对另外两类文件是必需项),而段表对于可执行文件是可选项(对另外两类文件是必需项)。另外,Section可以是.text,.data,.bss(即代码段,数据段(用来存放已经初始化的全局变量和静态变量)和BSS段(用来存放未初始化的全局变量和静态变量))等

 

使用《程序员的自我修养--链接 装载和库》中第三章的例子来说明elf的具体格式

/*
 * SimpleSection.c
 * 
 * Linux:
 *   gcc -c SimpleSection.c
 * Windows:
 *   cl SimpleSection.c /c /Za
 */
int printf(const char* format, ...);
int global_init_var = 84;
int global_uinit_var;

void func1(int i)
{
  printf("%d\n", i);
}

int main(void)
{
  static int static_var = 85;
  static int static_var2;
  int a = 1;
  int b;
  
  func1(static_var + static_var2 + a + b);

  return a;
}

 使用下面命令来编译:

 

gcc -c SimpleSection.c

 再使用下面命令来显示生成的目标文件(SimpleSection.c)的类型

 

file SimpleSection.o

 输出下列内容:

SimpleSection.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

 说明SimpleSection.o是一个重定位文件

使用下面命令查看SimpleSection.o的大小:

 

ls -l SimpleSection.o

 输出结果为:

 

-rw-r--r-- 1 host users 1092 11月  5 14:38 SimpleSection.o

 根据输出结果可以知道,SimpleSection.o大小为1092字节。

使用下面命令用16进制的数字来显示SimpleSection.o的内容(也可以用od -x SimpleSection.o命令)

 

hexdump -x SimpleSection.o

 输出结果为:

0000000    457f    464c    0101    0001    0000    0000    0000    0000
0000010    0001    0003    0001    0000    0000    0000    0000    0000
0000020    010c    0000    0000    0000    0034    0000    0000    0028
0000030    000b    0008    8955    83e5    18ec    458b    8908    2444
0000040    c704    2404    0000    0000    fce8    ffff    c9ff    55c3
0000050    e589    e483    83f0    20ec    44c7    1c24    0001    0000
0000060    158b    0004    0000    00a1    0000    8d00    0204    4403
0000070    1c24    4403    1824    0489    e824    fffc    ffff    448b
0000080    1c24    c3c9    0054    0000    0055    0000    6425    000a 
0000090    4700    4343    203a    4728    554e    2029    2e34    2e35
00000a0    2032    3032    3131    3130    3732    2820    7270    7265
00000b0    6c65    6165    6573    0029    2e00    7973    746d    6261
00000c0    2e00    7473    7472    6261    2e00    6873    7473    7472
00000d0    6261    2e00    6572    2e6c    6574    7478    2e00    6164
00000e0    6174    2e00    7362    0073    722e    646f    7461    0061
00000f0    632e    6d6f    656d    746e    2e00    6f6e    6574    472e
0000100    554e    732d    6174    6b63    0000    0000    0000    0000
0000110    0000    0000    0000    0000    0000    0000    0000    0000
*
0000130    0000    0000    001f    0000    0001    0000    0006    0000
0000140    0000    0000    0034    0000    0050    0000    0000    0000
0000150    0000    0000    0004    0000    0000    0000    001b    0000
0000160    0009    0000    0000    0000    0000    0000    041c    0000
0000170    0028    0000    0009    0000    0001    0000    0004    0000
0000180    0008    0000    0025    0000    0001    0000    0003    0000
0000190    0000    0000    0084    0000    0008    0000    0000    0000
00001a0    0000    0000    0004    0000    0000    0000    002b    0000
00001b0    0008    0000    0003    0000    0000    0000    008c    0000
00001c0    0004    0000    0000    0000    0000    0000    0004    0000
00001d0    0000    0000    0030    0000    0001    0000    0002    0000
00001e0    0000    0000    008c    0000    0004    0000    0000    0000
00001f0    0000    0000    0001    0000    0000    0000    0038    0000
0000200    0001    0000    0030    0000    0000    0000    0090    0000
0000210    0028    0000    0000    0000    0000    0000    0001    0000
0000220    0001    0000    0041    0000    0001    0000    0000    0000
0000230    0000    0000    00b8    0000    0000    0000    0000    0000
0000240    0000    0000    0001    0000    0000    0000    0011    0000
0000250    0003    0000    0000    0000    0000    0000    00b8    0000
0000260    0051    0000    0000    0000    0000    0000    0001    0000
0000270    0000    0000    0001    0000    0002    0000    0000    0000
0000280    0000    0000    02c4    0000    00f0    0000    000a    0000
0000290    000a    0000    0004    0000    0010    0000    0009    0000
00002a0    0003    0000    0000    0000    0000    0000    03b4    0000
00002b0    0065    0000    0000    0000    0000    0000    0001    0000
00002c0    0000    0000    0000    0000    0000    0000    0000    0000
00002d0    0000    0000    0001    0000    0000    0000    0000    0000
00002e0    0004    fff1    0000    0000    0000    0000    0000    0000
00002f0    0003    0001    0000    0000    0000    0000    0000    0000
0000300    0003    0003    0000    0000    0000    0000    0000    0000
0000310    0003    0004    0000    0000    0000    0000    0000    0000
0000320    0003    0005    0011    0000    0004    0000    0004    0000
0000330    0001    0003    0021    0000    0000    0000    0004    0000
0000340    0001    0004    0000    0000    0000    0000    0000    0000
0000350    0003    0007    0000    0000    0000    0000    0000    0000
0000360    0003    0006    0032    0000    0000    0000    0004    0000
0000370    0011    0003    0042    0000    0004    0000    0004    0000
0000380    0011    fff2    0053    0000    0000    0000    001b    0000
0000390    0012    0001    0059    0000    0000    0000    0000    0000
00003a0    0010    0000    0060    0000    001b    0000    0035    0000
00003b0    0012    0001    5300    6d69    6c70    5365    6365    6974
00003c0    6e6f    632e    7300    6174    6974    5f63    6176    2e72
00003d0    3231    3232    7300    6174    6974    5f63    6176    3272
00003e0    312e    3232    0033    6c67    626f    6c61    695f    696e
00003f0    5f74    6176    0072    6c67    626f    6c61    755f    6e69
0000400    7469    765f    7261    6600    6e75    3163    7000    6972
0000410    746e    0066    616d    6e69    0000    0000    0010    0000
0000420    0501    0000    0015    0000    0d02    0000    002e    0000
0000430    0301    0000    0033    0000    0401    0000    0046    0000
0000440    0c02    0000                                                
0000444

 上面的数据均为16进制数据(因为使用了-x选项),并且第一列为偏移地址。

使用下面命令来显示SimpleSection.o中各个段相关信息:

 

objdump -x SimpleSection.o

 输出结果为:

 

SimpleSection.o:     file format elf32-i386
SimpleSection.o
architecture: i386, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000050  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  00000000  00000000  00000084  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  00000000  00000000  0000008c  2**2
                  ALLOC
  3 .rodata       00000004  00000000  00000000  0000008c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      00000028  00000000  00000000  00000090  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  00000000  00000000  000000b8  2**0
                  CONTENTS, READONLY
SYMBOL TABLE:
00000000 l    df *ABS*	00000000 SimpleSection.c
00000000 l    d  .text	00000000 .text
00000000 l    d  .data	00000000 .data
00000000 l    d  .bss	00000000 .bss
00000000 l    d  .rodata	00000000 .rodata
00000004 l     O .data	00000004 static_var.1222
00000000 l     O .bss	00000004 static_var2.1223
00000000 l    d  .note.GNU-stack	00000000 .note.GNU-stack
00000000 l    d  .comment	00000000 .comment
00000000 g     O .data	00000004 global_init_var
00000004       O *COM*	00000004 global_uinit_var
00000000 g     F .text	0000001b func1
00000000         *UND*	00000000 printf
0000001b g     F .text	00000035 main


RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE 
00000010 R_386_32          .rodata
00000015 R_386_PC32        printf
0000002e R_386_32          .data
00000033 R_386_32          .bss
00000046 R_386_PC32        func1

也可以用下面的命令来查看各个段信息:

 

readelf -a SimpleSection.o

  输出结果为:

 

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          268 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         11
  Section header string table index: 8

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000050 00  AX  0   0  4
  [ 2] .rel.text         REL             00000000 00041c 000028 08      9   1  4
  [ 3] .data             PROGBITS        00000000 000084 000008 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 00008c 000004 00  WA  0   0  4
  [ 5] .rodata           PROGBITS        00000000 00008c 000004 00   A  0   0  1
  [ 6] .comment          PROGBITS        00000000 000090 000028 01  MS  0   0  1
  [ 7] .note.GNU-stack   PROGBITS        00000000 0000b8 000000 00      0   0  1
  [ 8] .shstrtab         STRTAB          00000000 0000b8 000051 00      0   0  1
  [ 9] .symtab           SYMTAB          00000000 0002c4 0000f0 10     10  10  4
  [10] .strtab           STRTAB          00000000 0003b4 000065 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

There are no program headers in this file.

Relocation section '.rel.text' at offset 0x41c contains 5 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000010  00000501 R_386_32          00000000   .rodata
00000015  00000d02 R_386_PC32        00000000   printf
0000002e  00000301 R_386_32          00000000   .data
00000033  00000401 R_386_32          00000000   .bss
00000046  00000c02 R_386_PC32        00000000   func1

There are no unwind sections in this file.

Symbol table '.symtab' contains 15 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS SimpleSection.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000000     0 SECTION LOCAL  DEFAULT    5 
     6: 00000004     4 OBJECT  LOCAL  DEFAULT    3 static_var.1222
     7: 00000000     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1223
     8: 00000000     0 SECTION LOCAL  DEFAULT    7 
     9: 00000000     0 SECTION LOCAL  DEFAULT    6 
    10: 00000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    11: 00000004     4 OBJECT  GLOBAL DEFAULT  COM global_uinit_var
    12: 00000000    27 FUNC    GLOBAL DEFAULT    1 func1
    13: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    14: 0000001b    53 FUNC    GLOBAL DEFAULT    1 main

No version information found in this file.
 

 下面分析SimpleSection.o文件内容

  首先是Elf文件头,其定义为(在/usr/include/elf.h中)

#define EI_NIDENT (16)
typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf32_Half	e_type;			/* Object file type */
  Elf32_Half	e_machine;		/* Architecture */
  Elf32_Word	e_version;		/* Object file version */
  Elf32_Addr	e_entry;		/* Entry point virtual address */
  Elf32_Off	e_phoff;		/* Program header table file offset */
  Elf32_Off	e_shoff;		/* Section header table file offset */
  Elf32_Word	e_flags;		/* Processor-specific flags */
  Elf32_Half	e_ehsize;		/* ELF header size in bytes */
  Elf32_Half	e_phentsize;		/* Program header table entry size */
  Elf32_Half	e_phnum;		/* Program header table entry count */
  Elf32_Half	e_shentsize;		/* Section header table entry size */
  Elf32_Half	e_shnum;		/* Section header table entry count */
  Elf32_Half	e_shstrndx;		/* Section header string table index */
} Elf32_Ehdr;

 大小为52个字节(16进制表示为0x34),因此SimpleSection.o前52个字节内容为ELF文件头,其二进制表示为:

0000000    457f    464c    0101    0001    0000    0000    0000    0000
0000010    0001    0003    0001    0000    0000    0000    0000    0000
0000020    010c    0000    0000    0000    0034    0000    0000    0028
0000030    000b    0008  

 因为intel及其兼容处理器使用了小端法,此处的-x命令选项是一次性输出两个字节,所以457f实际表示了7f45,而464c实际上是4c46,依次类推。

其前16个字节(第一行,对应e_ident[EI_NIDENT])实际表示内容为7f454c46010101000000000000000000,前四个字节7f454c46(0x45,0x4c,0x46是'e','l','f'对应的ascii编码)是一个魔数(magic number),表示这是一个ELF对象。接下来的一个字节01表示是一个32位对象,接下来的一个字节01表示是小端法表示,再接下来的一个字节01表示文件头版本。剩下的默认都设置为0.

接下来(第二行)e_type(两个字节)值为0x0001,表示是一个重定位文件。e_machine(两个字节)值为0x0003,表示是intel80386处理器体系结构。e_version(四个字节)值为0x00000001,表示是当前版本。e_entry(四个字节)值为0x00000000,表示没有入口点。e_phoff(四个字节)值为0x00000000,表示没有程序头表。

接下来(第三行)e_shoff(四个字节)值为0x0000010c,表示段表的偏移地址。e_flags(四个字节)值为0x00000000,表示未知处理器特定标志(#define EF_SH_UNKNOWN        0x0)。e_ehsize(两个字节)值为0034,表示elf文件头大小(正好是52个字节)。e_phentsize(两个字节)和e_phnum(两个字节)的值均为0x0000,因为重定位文件没有程序头表。e_ehentsize(两个字节)值为0x0028表示段头大小为40个字节。

接下来(第四行)e_shnum(两个字节)值为0x000b,表示段表入口有11个。e_shstrndx(两个字节)值为0x0008,表示段名串表的在段表中的索引号。

 

SimpleSection.o中紧接着ELF头的部分是代码段(.text)。使用下面命令对SimpleSection.o的文本段进行反汇编:

 

objdump -d SimpleSection.o

 输出结果为:

00000000 :
   0:	55                   	push   %ebp
   1:	89 e5                	mov    %esp,%ebp
   3:	83 ec 18             	sub    $0x18,%esp
   6:	8b 45 08             	mov    0x8(%ebp),%eax
   9:	89 44 24 04          	mov    %eax,0x4(%esp)
   d:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
  14:	e8 fc ff ff ff       	call   15 
  19:	c9                   	leave  
  1a:	c3                   	ret    

0000001b 
: 1b: 55 push %ebp 1c: 89 e5 mov %esp,%ebp 1e: 83 e4 f0 and $0xfffffff0,%esp 21: 83 ec 20 sub $0x20,%esp 24: c7 44 24 1c 01 00 00 movl $0x1,0x1c(%esp) 2b: 00 2c: 8b 15 04 00 00 00 mov 0x4,%edx 32: a1 00 00 00 00 mov 0x0,%eax 37: 8d 04 02 lea (%edx,%eax,1),%eax 3a: 03 44 24 1c add 0x1c(%esp),%eax 3e: 03 44 24 18 add 0x18(%esp),%eax 42: 89 04 24 mov %eax,(%esp) 45: e8 fc ff ff ff call 46 4a: 8b 44 24 1c mov 0x1c(%esp),%eax 4e: c9 leave 4f: c3 ret

 代码段刚好对应SimpleSection.o紧接着ELF头的80(0x50)个字节代码。即

                           8955    83e5    18ec    458b    8908    2444
0000040    c704    2404    0000    0000    fce8    ffff    c9ff    55c3
0000050    e589    e483    83f0    20ec    44c7    1c24    0001    0000
0000060    158b    0004    0000    00a1    0000    8d00    0204    4403
0000070    1c24    4403    1824    0489    e824    fffc    ffff    448b
0000080    1c24    c3c9    

 这段代码是func1和main函数对应的汇编代码

 

 

 紧接着代码段是数据段(.data)的内容(8个字节,16进制表示为0x08),即0x00000084地址开始8个字节内容:

 

 0054    0000    0055    0000

 数据段是全局和静态变量初始化数据的存放地。其中global_init_var(int类型,四个字节) 值为84(16进制表示为0x00000054),对应0054 0000.而static_var(static int类型,四个字节)值为85(16进制表示为0x00000055),对应0055 0000.

 

 

紧接着数据段的是.bss和.rodata(只读数据段,用于存放常数串等,此处与.bss段重叠)段,大小为4个字节对应0x0000008c地址开始四个字节内容:

 6425    000a

 恰好是字符串"%d\n"的二进制表示(0x64对应字符'd',0x25对应字符'%',0x0a对应字符LF(换行符号,unix/linux下'\n'用与LF相对应,而在为window则需要用CR(回车)和LF两个符号来对应与'\n'),0x00对应字符'\0'来作为串的终止符号)。

紧接着.rodata段的是.comment段,它用来存放编译器版本信息等,此处对应0x00000090地址开始的40个字节(16进制下为0x28):

0000090    4700    4343    203a    4728    554e    2029    2e34    2e35
00000a0    2032    3032    3131    3130    3732    2820    7270    7265
00000b0    6c65    6165    6573    0029

 实际对应于一个字符串"\0GCC: (GNU) 4.5.2 20110127 (prerelease)\0"(感兴趣的话,可以自己将上面的16进制ascii值转换成字符试试,刚好与使用gcc -v命令的得到的结果一致(这个结果是使用od -c SimpleSection.o和上面使用-x选项的结果对比得到的,该命令在我的电脑上输出结果中最后一行为:gcc 版本 4.5.2 20110127 (prerelease) (GCC) )

 

 

紧接着.note.GNU-Stack段(该段大小为0,所以没有对应的实质性内容)

 紧接着从0x000000b8地址开始81(0x51)个字节是.shstrtab段,用来存放段的名称。对应内容为:

                                           2e00    7973    746d    6261
00000c0    2e00    7473    7472    6261    2e00    6873    7473    7472
00000d0    6261    2e00    6572    2e6c    6574    7478    2e00    6164
00000e0    6174    2e00    7362    0073    722e    646f    7461    0061
00000f0    632e    6d6f    656d    746e    2e00    6f6e    6574    472e
0000100    554e    732d    6174    6b63    00 

 其对应字符串为"\0.symtab\0.strtab\0.shstrtab\0.rel.text\0.data\0.bss\0.rodata\0.comment\0.note.GNU-Stack\0"(双引号是我手动添加的,为了看起来更好看,这个字符串是我用od -c SimpleSection.o得到的结果与前面使用-x选项得到结果对比得到的。)

 

 

紧接着.shstrtab段的是段表(Section table),从前面对ELF头的解析可以知道段表的地址是从0x0000010c(e_shoff项)开始,而上面的.shstrtab的开始地址为0xb8,大小为0x51,所以此处段表应该是0x109开始,但是从前面使用readelf得出结果可以知道,段表对齐要求是4个字节对齐,所以地址最后两位必须是00,这就导致是从0x10c开始,留下了三个字节的空洞(英文为hole)。再根据e_shnum=11和ehentsize=40可知,有11个段,每个段占40个字节大小,总共占据440个字节(16进制为0x1b8)。段入口的类型定义如下(/usr/include/elf.h):

typedef struct
{
  Elf32_Word	sh_name;		/* Section name (string tbl index) */
  Elf32_Word	sh_type;		/* Section type */
  Elf32_Word	sh_flags;		/* Section flags */
  Elf32_Addr	sh_addr;		/* Section virtual addr at execution */
  Elf32_Off	sh_offset;		/* Section file offset */
  Elf32_Word	sh_size;		/* Section size in bytes */
  Elf32_Word	sh_link;		/* Link to another section */
  Elf32_Word	sh_info;		/* Additional section information */
  Elf32_Word	sh_addralign;		/* Section alignment */
  Elf32_Word	sh_entsize;		/* Entry size if section holds table */
} Elf32_Shdr;

 恰好占据了40个字节。

从0x0000010c开始有11个段,每个段占40个字节大小。

第一个段为0x0000010c-0x00000133,其中内容全部为0,所以不表示任何段。

第二个段为0x00000134-0x0000015b,对应内容为:

                           001f    0000    0001    0000    0006    0000
0000140    0000    0000    0034    0000    0050    0000    0000    0000
0000150    0000    0000    0004    0000    0000    0000   

 段中每个成员均为4个字节,所以分析起来相对简单一些。

 sh_name值为0x0000001f,它表示该段名称在.shstrtab中偏移量,通过计算可知该名称为.text。sh_type值为0x00000001(对应SHT_PROGBITS),表示这个段拥有程序所定义的信息,其格式和含义完全有该程序确定。sh_flags值为0x00000006(对应于SHF_ALLOC和SHF_EXECINSTR)。sh_addr值为0x00000000,表示这个段不会出现在进程的地址镜像中。

sh_offset值为0x00000034(偏移地址),sh_size值为0x00000050,表示代码段大小为80(0x50)个字节。sh_link值为0x00000000,表示没有链接信息。sh_info值为0x00000000,表示没有辅助信息。sh_addalign值为0x00000004,表示4个字节对齐,sh_entsize值为0x00000000,表示没有入口。

第三个段为0x0000015c-0x00000184,对应内容为:

                                                           001b    0000
0000160    0009    0000    0000    0000    0000    0000    041c    0000
0000170    0028    0000    0009    0000    0001    0000    0004    0000
0000180    0008    0000 

 该段为.rel.text段(偏移量为0x0000001b),sh_type为0x00000009(对应SHT_REL),sh_offset为0x0000041c,sh_size为0x00000028(40个字节)。sh_link和sh_info分别为9和1,分别表示相关符号表索引和重定位应用段的段头索引。其余段的内容不再详细分析,可以自己分析并于前面readelf输出结果相对照。

第四个段为0x00000184-0x000001ac(0x25,即.shstrtab第37个字节偏移处,即.data段),对应内容:

                           0025    0000    0001    0000    0003    0000
0000190    0000    0000    0084    0000    0008    0000    0000    0000
00001a0    0000    0000    0004    0000    0000    0000 

第五个段为0x000001ac-0x00001d4(0x2b,即第43个字节偏移处,即.bss段),对应内容为:

                                                           002b    0000
00001b0    0008    0000    0003    0000    0000    0000    008c    0000
00001c0    0004    0000    0000    0000    0000    0000    0004    0000
00001d0    0000    0000   

第六个段为0x000001d4-0x000001fc(0x30,即第48个字节偏移处,即.rodata段),对应内容为:

                           0030    0000    0001    0000    0002    0000
00001e0    0000    0000    008c    0000    0004    0000    0000    0000
00001f0    0000    0000    0001    0000    0000    0000 
 

第七个段为0x000001fc-0x00000224(0x38,即第56个字节偏移处,即.comment段),对应内容为:

                                                           0038    0000
0000200    0001    0000    0030    0000    0000    0000    0090    0000
0000210    0028    0000    0000    0000    0000    0000    0001    0000
0000220    0001    0000   

第八个段为0x00000224-0x0000024c(0x41,即第65个字节偏移处,即.note.GNU-Stack段),对应内容为:

                           0041    0000    0001    0000    0000    0000
0000230    0000    0000    00b8    0000    0000    0000    0000    0000
0000240    0000    0000    0001    0000    0000    0000  

第九个段为0x0000024c-0x00000274(0x11,即第17个字节偏移处,即.shstrtab段),对应内容为:

                                                           0011    0000
0000250    0003    0000    0000    0000    0000    0000    00b8    0000
0000260    0051    0000    0000    0000    0000    0000    0001    0000
0000270    0000    0000   

第十个段为0x00000274-0x0000029c(0x01,即第1个字节偏移处,即.symtab段),对应内容为:

                           0001    0000    0002    0000    0000    0000
0000280    0000    0000    02c4    0000    00f0    0000    000a    0000
0000290    000a    0000    0004    0000    0010    0000   
 

第十一个段为0x0000029c-0x000002c4(0x9,即第9个字节偏移处,即.strtab段),对应内容为:

                                                           0009    0000
00002a0    0003    0000    0000    0000    0000    0000    03b4    0000
00002b0    0065    0000    0000    0000    0000    0000    0001    0000
00002c0    0000    0000    

所以段表中表示的段从偏移地址1开始(1-10,因为第一个段全为空)依次为.text, .rel.text, .data, .bss, .rodata, .comment, .note.GNU-Stack, .shstrtab, .symtab, .strtab)

 

 从0x000002c4开始为.symtab(符号表)段,大小为0xf0(240个字节),对应内容为:

 

                           0000    0000    0000    0000    0000    0000
00002d0    0000    0000    0001    0000    0000    0000    0000    0000
00002e0    0004    fff1    0000    0000    0000    0000    0000    0000
00002f0    0003    0001    0000    0000    0000    0000    0000    0000
0000300    0003    0003    0000    0000    0000    0000    0000    0000
0000310    0003    0004    0000    0000    0000    0000    0000    0000
0000320    0003    0005    0011    0000    0004    0000    0004    0000
0000330    0001    0003    0021    0000    0000    0000    0004    0000
0000340    0001    0004    0000    0000    0000    0000    0000    0000
0000350    0003    0007    0000    0000    0000    0000    0000    0000
0000360    0003    0006    0032    0000    0000    0000    0004    0000
0000370    0011    0003    0042    0000    0004    0000    0004    0000
0000380    0011    fff2    0053    0000    0000    0000    001b    0000
0000390    0012    0001    0059    0000    0000    0000    0000    0000
00003a0    0010    0000    0060    0000    001b    0000    0035    0000
00003b0    0012    0001   

 符号表结构定义(/usr/include/elf.h):

 

typedef struct
{
  Elf32_Word	st_name;		/* Symbol name (string tbl index) */
  Elf32_Addr	st_value;		/* Symbol value */
  Elf32_Word	st_size;		/* Symbol size */
  unsigned char	st_info;		/* Symbol type and binding */
  unsigned char	st_other;		/* Symbol visibility */
  Elf32_Section	st_shndx;		/* Section index */
} Elf32_Sym;

  该结构大小为16个字节,而整个符号表段大小为240个字节,所以总共可以分成15个符号(有些符号未使用)。这15个符号刚好和前面objdump输出的符号表(14个符号,第一个符号为空,所以被忽略)结果相对应:

 

SYMBOL TABLE:  
00000000 l    df *ABS*  00000000 SimpleSection.c  
00000000 l    d  .text  00000000 .text  
00000000 l    d  .data  00000000 .data  
00000000 l    d  .bss   00000000 .bss  
00000000 l    d  .rodata    00000000 .rodata  
00000004 l     O .data  00000004 static_var.1222  
00000000 l     O .bss   00000004 static_var2.1223  
00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack  
00000000 l    d  .comment   00000000 .comment  
00000000 g     O .data  00000004 global_init_var  
00000004       O *COM*  00000004 global_uinit_var  
00000000 g     F .text  0000001b func1  
00000000         *UND*  00000000 printf  
0000001b g     F .text  00000035 main  
  

  也和使用readelf中符号表相关内容对应(readelf命令输出结果比objdump输出结果看起来更好看一些):

 

Symbol table '.symtab' contains 15 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS SimpleSection.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000000     0 SECTION LOCAL  DEFAULT    5 
     6: 00000004     4 OBJECT  LOCAL  DEFAULT    3 static_var.1222
     7: 00000000     4 OBJECT  LOCAL  DEFAULT    4 static_var2.1223
     8: 00000000     0 SECTION LOCAL  DEFAULT    7 
     9: 00000000     0 SECTION LOCAL  DEFAULT    6 
    10: 00000000     4 OBJECT  GLOBAL DEFAULT    3 global_init_var
    11: 00000004     4 OBJECT  GLOBAL DEFAULT  COM global_uinit_var
    12: 00000000    27 FUNC    GLOBAL DEFAULT    1 func1
    13: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf
    14: 0000001b    53 FUNC    GLOBAL DEFAULT    1 main
 

15个符号中分析比较重要的符号,其他部分可以自己分析。

首先是0x000002d4-0x000002e3,对应内容:

 

                        0001    0000    0000    0000    0000    0000
00002e0    0004    fff1 

 该部分内容中st_name值为0x00000001,表示在常数串表中偏移量为1,即SimpleSection.c的首地址。st_info值为0x04,表示是文件名和局部符号。st_shndx值为0xfff1(即SHN_ABS),宝石该付好包含了一个绝对值。其他成员值为0.

接下来64个字节(地址0x000002e4-0x00000323,每个符号占16个字节,4个符号)对应内容为:

 

                           0000    0000    0000    0000    0000    0000  
00002f0    0003    0001    0000    0000    0000    0000    0000    0000  
0000300    0003    0003    0000    0000    0000    0000    0000    0000  
0000310    0003    0004    0000    0000    0000    0000    0000    0000  
0000320    0003    0005  

  这四个符号除了值有st_shndx成员的值不同(分别为1,3,4,5),其他成员均相同。而其他成员值有st_info的值有实际含义,均为0x03,表示该符号是一个段,所以1,3,4,5分别对应段的偏移,分别表示.text,.data,.bss,.rodata(可以查看readelf -a输出结果,其中段表部分第一列即为偏移量)。

接下来比较重要的符号是0x00000324-0x00000333,对应内容:

 

                        0011    0000    0004    0000    0004    0000
0000330    0001    0003  

 这部分内容st_name值为0x00000011,表示在常数串表中偏移量为0x00000011(17),即static_var.1222的首地址。st_size的值均为0x00000004(int类型),分别表示符号值和符号大小。st_info值为0x01,表示是一个局部变量。st_value值为0x00000004,st_shndx值为0x0003,表示偏移为4的段(即.data段)中偏移地址为3的位置。

接下来的重要符号地址为0x00000334-0x00000343,对应内容:

 

                        0021    0000    0000    0000    0004    0000
0000340    0001    0004 

 该部分内容st_name值为0x00000021,表示常数串中偏移量为0x00000021(33),即static_var2.1223的首地址。st_size值为0x00000004(int类型)。st_info值为0x01,表示是一个局部变量.st_shndx值为0x0003,st_value值为0x00000000,表示偏移为0的段中偏移地址为4的未知。

另一个重要符号地址为0x00000364-0x00000373,对应内容:

 

                        0032    0000    0000    0000    0004    0000
0000370    0011    0003   

 该部分st_name值为0x00000032,表示常数串中偏移量为0x00000032(50),即global_init_var的首地址。st_info值为0x11,表示全局变量。其他成员含义与上一个符号类似。

还有一个重要符号地址为0x00000374-0x00000383,对应内容:

 

                        0042    0000    0004    0000    0004    0000
0000380    0011    fff2   

 该部分st_name值为0x00000042,表示常数串中偏移量为0x0000042(66),即global_uint_var的首地址。st_shndx值为0xfff2,表示该符号是common类型符号。st_value值为0x00000004,表示是4个字节对齐。st_size值为0x00000004,表示大小为4(int类型)。

还有重要符号地址为0x00000384-0x00000393,对应内容:

 

                       0053    0000    0000    0000    001b    0000
0000390    0012    0001   

 该部分st_name值为0x00000053,表示对应func1首地址。st_info值为0x12,表示是全局函数。st_value值为0x0000000,st_size值为0x0000001b,故表示偏移为0(.text)的段中28个字节。

 

 

从 0x000003b4开始为.strtab(字符串表)段,大小为0x65(101个字节),对应内容为:

 

                           5300    6d69    6c70    5365    6365    6974
00003c0    6e6f    632e    7300    6174    6974    5f63    6176    2e72
00003d0    3231    3232    7300    6174    6974    5f63    6176    3272
00003e0    312e    3232    0033    6c67    626f    6c61    695f    696e
00003f0    5f74    6176    0072    6c67    626f    6c61    755f    6e69
0000400    7469    765f    7261    6600    6e75    3163    7000    6972
0000410    746e    0066    616d    6e69    00

 对应字符串为"\0SimpleSection.c\0static_var.1222\0static_var2.1223\0global_init_var\0global_uint_var\0func1\0printf\0main\0"(使用od -c SimpleSection.o与-x选项得到结果对比即可)

 

 

从0x0000041c开始为.rel.text段(重定位表),大小为0x28(40个字节),对应内容为:

                                                           0010    0000
0000420    0501    0000    0015    0000    0d02    0000    002e    0000
0000430    0301    0000    0033    0000    0401    0000    0046    0000
0000440    0c02    0000  

重定位表数据结构(/usr/include/elf.h):

typedef struct
{
  Elf32_Addr    r_offset;               /* Address */
  Elf32_Word    r_info;                 /* Relocation type and symbol index */
} Elf32_Rel;

 该数据结构大小为8个字节,所以40个字节应该包含5个这样的结构。

 而需要重定位的符号可以使用下面的命令列出:

objdump -r SimpleSection.o

 输出结果:

 

SimpleSection.o:     file format elf32-i386

RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE 
00000010 R_386_32          .rodata
00000015 R_386_PC32        printf
0000002e R_386_32          .data
00000033 R_386_32          .bss
00000046 R_386_PC32        func1

 刚好是5个符号。

 

根据上面内容,SimpleSection.o的结构形式如下:

          ------------------------------------- 0x00000000

          |   ELF Header(e_shoff=0x10c)     

          ------------------------------------- 0x00000034

0x50  | .text             

          ------------------------------------- 0x00000084

0x08  | .data

          ------------------------------------- 0x0000008c

0x04  | .rodata

          ------------------------------------- 0x00000090

0x28  | .comment

          ------------------------------------- 0x000000b8

0x51  | .shsttab

          ------------------------------------- 0x00000109

           --------------------------------------0x0000010c

0x1b8 | Section Table

           -------------------------------------- 0x000002c4

0xf0   |  .symtab

          -------------------------------------- 0x000003b4

0x65  |   .strtab

          -------------------------------------- 0x0000041c

0x28  |   .rel.text

          --------------------------------------- 0x00000444

 

根据上面的实例观察可知,分析一个elf文件的内部构造,可以先找到elf头,并找到其中几个关键成员e_shoff(段表偏移地址), e_shentnum(段的个数), e_shsize(每个段的大小)。这样可以到段表中再分析每个段相关信息。另外,这个例子中elf文件是重定位文件,它缺乏elf中的一个重要段(程序文件头表),这个表只存在于可执行文件或动态链接库文件中。它可以通过elf头的e_phoff(文件头表偏移地址),e_phnum(文件头表个数)和e_phsize(每个表大小)来确定该文件头表的位置及大小。分析方式和elf头类似。文件头表类型定义(/usr/include/elf.h):

 

typedef struct
{
  Elf32_Word    p_type;                 /* Segment type */
  Elf32_Off     p_offset;               /* Segment file offset */
  Elf32_Addr    p_vaddr;                /* Segment virtual address */
  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;

/* Special value for e_phnum.  This indicates that the real number of
   program headers is too large to fit into e_phnum.  Instead the real
   value is in the field sh_info of section 0.  */

#define PN_XNUM         0xffff

/* Legal values for p_type (segment type).  */

#define PT_NULL         0               /* Program header table entry unused */
#define PT_LOAD         1               /* Loadable program segment */
#define PT_DYNAMIC      2               /* Dynamic linking information */
#define PT_INTERP       3               /* Program interpreter */
#define PT_NOTE         4               /* Auxiliary information */
#define PT_SHLIB        5               /* Reserved */
#define PT_PHDR         6               /* Entry for header table itself */
#define PT_TLS          7               /* Thread-local storage segment */
#define PT_NUM          8               /* Number of defined types */
#define PT_LOOS         0x60000000      /* Start of OS-specific */
#define PT_GNU_EH_FRAME 0x6474e550      /* GCC .eh_frame_hdr segment */
#define PT_GNU_STACK    0x6474e551      /* Indicates stack executability */
#define PT_GNU_RELRO    0x6474e552      /* Read-only after relocation */
#define PT_LOSUNW       0x6ffffffa
#define PT_SUNWBSS      0x6ffffffa      /* Sun Specific segment */
#define PT_SUNWSTACK    0x6ffffffb      /* Stack segment */
#define PT_HISUNW       0x6fffffff
#define PT_HIOS         0x6fffffff      /* End of OS-specific */
#define PT_LOPROC       0x70000000      /* Start of processor-specific */
#define PT_HIPROC       0x7fffffff      /* End of processor-specific */

/* Legal values for p_flags (segment flags).  */

#define PF_X            (1 << 0)        /* Segment is executable */
#define PF_W            (1 << 1)        /* Segment is writable */
#define PF_R            (1 << 2)        /* Segment is readable */
#define PF_MASKOS       0x0ff00000      /* OS-specific */
#define PF_MASKPROC     0xf0000000      /* Processor-specific */

 感兴趣的可以自己分析,并和readelf -a得到结果对比来加深理解。

例如我写的一个小测试程序:

 

#include 
#include 
int main(void)
{
  printf("%d\n",sizeof(Elf32_Sym));
  printf("Hello, World\n");
  return 0;
}

 使用下面命令编译:

 

gcc -o test test.c

 得到一个名为test的可执行文件。

使用下面命令得到test文件文件头表相关部分的结构:

 

readelf -l test

 该命令输出:

 

Elf file type is EXEC (Executable file)
Entry point 0x8048340
There are 8 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4
  INTERP         0x000134 0x08048134 0x08048134 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x005b8 0x005b8 R E 0x1000
  LOAD           0x0005b8 0x080495b8 0x080495b8 0x0010c 0x00114 RW  0x1000
  DYNAMIC        0x0005cc 0x080495cc 0x080495cc 0x000d0 0x000d0 RW  0x4
  NOTE           0x000148 0x08048148 0x08048148 0x00020 0x00020 R   0x4
  GNU_EH_FRAME   0x000514 0x08048514 0x08048514 0x00024 0x00024 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag 
   06     .eh_frame_hdr 
   07     

 使用hexdump得到test文件前308个字节(前52个字节为ELF头内容,后面256个字节(这部分是文件头表,可以从0000001c地址开始四个字节值0x00000034(e_phoff)知道文件头表偏移地址是从第52个字节开始,每个表32(e_phentsize,0x0000002a地址开始的2个字节,0x0020)个字节,共8(e_phnum,0x0000002c地址开始的2个字节,对应0x0008)个表)内容:

 

hexdump -x test -n 308

 输出结果:

 

0000000    457f    464c    0101    0001    0000    0000    0000    0000
0000010    0002    0003    0001    0000    8340    0804    0034    0000
0000020    07e8    0000    0000    0000    0034    0020    0008    0028
0000030    001e    001b    0006    0000    0034    0000    8034    0804
0000040    8034    0804    0100    0000    0100    0000    0005    0000
0000050    0004    0000    0003    0000    0134    0000    8134    0804
0000060    8134    0804    0013    0000    0013    0000    0004    0000
0000070    0001    0000    0001    0000    0000    0000    8000    0804
0000080    8000    0804    05b8    0000    05b8    0000    0005    0000
0000090    1000    0000    0001    0000    05b8    0000    95b8    0804
00000a0    95b8    0804    010c    0000    0114    0000    0006    0000
00000b0    1000    0000    0002    0000    05cc    0000    95cc    0804
00000c0    95cc    0804    00d0    0000    00d0    0000    0006    0000
00000d0    0004    0000    0004    0000    0148    0000    8148    0804
00000e0    8148    0804    0020    0000    0020    0000    0004    0000
00000f0    0004    0000    e550    6474    0514    0000    8514    0804
0000100    8514    0804    0024    0000    0024    0000    0004    0000
0000110    0004    0000    e551    6474    0000    0000    0000    0000
0000120    0000    0000    0000    0000    0000    0000    0006    0000
0000130    0004    0000                                                
0000134

 文件头段第一个表(0x00000034-0x00000053)内容:

                           0006    0000    0034    0000    8034    0804
0000040    8034    0804    0100    0000    0100    0000    0005    0000
0000050    0004    0000    

 刚好有8个成员,每个成员4个字节。

 p_type值为0x00000006,表示文件头表入口。p_offset值为0x00000034,表示其偏移地址。p_vaddr值为0x08048034,表示虚拟地址。p_paddr值为0x08048034,表示物理地址。p_filesz值为0x00000100,表示文件中段的大小256个字节。p_memsz值为0x00000100,表示内存中段大小为256个字节。p_flags值为0x00000005,表示该段可读并且可执行。p_align值为0x00000004表示该段是4字节对齐。这个段表刚好对应文件头段的256个字节。

其他内容不再详细分析,感兴趣的可以自己探索研究

你可能感兴趣的:(C语言)