试验环境: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个字节。
其他内容不再详细分析,感兴趣的可以自己探索研究