试验环境: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:",能搜索到很多关于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 00000000010 0001 0003 0001 0000 0000 0000 0000 00000000020 010c 0000 0000 0000 0034 0000 0000 00280000030 000b 0008 8955 83e5 18ec 458b 8908 24440000040 c704 2404 0000 0000 fce8 ffff c9ff 55c30000050 e589 e483 83f0 20ec 44c7 1c24 0001 00000000060 158b 0004 0000 00a1 0000 8d00 0204 44030000070 1c24 4403 1824 0489 e824 fffc ffff 448b0000080 1c24 c3c9 0054 0000 0055 0000 6425 000a0000090 4700 4343 203a 4728 554e 2029 2e34 2e3500000a0 2032 3032 3131 3130 3732 2820 7270 726500000b0 6c65 6165 6573 0029 2e00 7973 746d 626100000c0 2e00 7473 7472 6261 2e00 6873 7473 747200000d0 6261 2e00 6572 2e6c 6574 7478 2e00 616400000e0 6174 2e00 7362 0073 722e 646f 7461 006100000f0 632e 6d6f 656d 746e 2e00 6f6e 6574 472e0000100 554e 732d 6174 6b63 0000 0000 0000 00000000110 0000 0000 0000 0000 0000 0000 0000 0000*0000130 0000 0000 001f 0000 0001 0000 0006 00000000140 0000 0000 0034 0000 0050 0000 0000 00000000150 0000 0000 0004 0000 0000 0000 001b 00000000160 0009 0000 0000 0000 0000 0000 041c 00000000170 0028 0000 0009 0000 0001 0000 0004 00000000180 0008 0000 0025 0000 0001 0000 0003 00000000190 0000 0000 0084 0000 0008 0000 0000 000000001a0 0000 0000 0004 0000 0000 0000 002b 000000001b0 0008 0000 0003 0000 0000 0000 008c 000000001c0 0004 0000 0000 0000 0000 0000 0004 000000001d0 0000 0000 0030 0000 0001 0000 0002 000000001e0 0000 0000 008c 0000 0004 0000 0000 000000001f0 0000 0000 0001 0000 0000 0000 0038 00000000200 0001 0000 0030 0000 0000 0000 0090 00000000210 0028 0000 0000 0000 0000 0000 0001 00000000220 0001 0000 0041 0000 0001 0000 0000 00000000230 0000 0000 00b8 0000 0000 0000 0000 00000000240 0000 0000 0001 0000 0000 0000 0011 00000000250 0003 0000 0000 0000 0000 0000 00b8 00000000260 0051 0000 0000 0000 0000 0000 0001 00000000270 0000 0000 0001 0000 0002 0000 0000 00000000280 0000 0000 02c4 0000 00f0 0000 000a 00000000290 000a 0000 0004 0000 0010 0000 0009 000000002a0 0003 0000 0000 0000 0000 0000 03b4 000000002b0 0065 0000 0000 0000 0000 0000 0001 000000002c0 0000 0000 0000 0000 0000 0000 0000 000000002d0 0000 0000 0001 0000 0000 0000 0000 000000002e0 0004 fff1 0000 0000 0000 0000 0000 000000002f0 0003 0001 0000 0000 0000 0000 0000 00000000300 0003 0003 0000 0000 0000 0000 0000 00000000310 0003 0004 0000 0000 0000 0000 0000 00000000320 0003 0005 0011 0000 0004 0000 0004 00000000330 0001 0003 0021 0000 0000 0000 0004 00000000340 0001 0004 0000 0000 0000 0000 0000 00000000350 0003 0007 0000 0000 0000 0000 0000 00000000360 0003 0006 0032 0000 0000 0000 0004 00000000370 0011 0003 0042 0000 0004 0000 0004 00000000380 0011 fff2 0053 0000 0000 0000 001b 00000000390 0012 0001 0059 0000 0000 0000 0000 000000003a0 0010 0000 0060 0000 001b 0000 0035 000000003b0 0012 0001 5300 6d69 6c70 5365 6365 697400003c0 6e6f 632e 7300 6174 6974 5f63 6176 2e7200003d0 3231 3232 7300 6174 6974 5f63 6176 327200003e0 312e 3232 0033 6c67 626f 6c61 695f 696e00003f0 5f74 6176 0072 6c67 626f 6c61 755f 6e690000400 7469 765f 7261 6600 6e75 3163 7000 69720000410 746e 0066 616d 6e69 0000 0000 0010 00000000420 0501 0000 0015 0000 0d02 0000 002e 00000000430 0301 0000 0033 0000 0401 0000 0046 00000000440 0c02 0000 0000444
上面的数据均为16进制数据(因为使用了-x选项),并且第一列为偏移地址。
使用下面命令来显示SimpleSection.o中各个段相关信息:
objdump -x SimpleSection.o
输出结果为:
SimpleSection.o:file format elf32-i386SimpleSection.oarchitecture: i386, flags 0x00000011:HAS_RELOC, HAS_SYMSstart address 0x00000000Sections:Idx NameSizeVMA LMA File off Algn0 .text 00000050 00000000 00000000 00000034 2**2CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE1 .data 00000008 00000000 00000000 00000084 2**2CONTENTS, ALLOC, LOAD, DATA2 .bss00000004 00000000 00000000 0000008c 2**2ALLOC3 .rodata 00000004 00000000 00000000 0000008c 2**0CONTENTS, ALLOC, LOAD, READONLY, DATA4 .comment00000028 00000000 00000000 00000090 2**0CONTENTS, READONLY5 .note.GNU-stack 00000000 00000000 00000000 000000b8 2**0CONTENTS, READONLYSYMBOL TABLE:00000000 l df *ABS*00000000 SimpleSection.c00000000 l d .text00000000 .text00000000 l d .data00000000 .data00000000 l d .bss00000000 .bss00000000 l d .rodata00000000 .rodata00000004 lO .data00000004 static_var.122200000000 lO .bss00000004 static_var2.122300000000 l d .note.GNU-stack00000000 .note.GNU-stack00000000 l d .comment00000000 .comment00000000 gO .data00000004 global_init_var00000004 O *COM*00000004 global_uinit_var00000000 gF .text0000001b func100000000 *UND*00000000 printf0000001b gF .text00000035 mainRELOCATION RECORDS FOR [.text]:OFFSET TYPE VALUE 00000010 R_386_32.rodata00000015 R_386_PC32 printf0000002e R_386_32.data00000033 R_386_32.bss00000046 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: ELF32Data:2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type:REL (Relocatable file)Machine: Intel 80386Version: 0x1Entry point address:0x0Start of program headers:0 (bytes into file)Start of section headers:268 (bytes into file)Flags: 0x0Size of this header:52 (bytes)Size of program headers: 0 (bytes)Number of program headers: 0Size of section headers: 40 (bytes)Number of section headers: 11Section header string table index: 8Section Headers:[Nr] Name Type AddrOff Size ES Flg Lk Inf Al[ 0] NULL 00000000 000000 000000 000 0 0[ 1] .text PROGBITS 00000000 000034 000050 00 AX 0 0 4[ 2] .rel.text REL 00000000 00041c 000028 089 1 4[ 3] .data PROGBITS 00000000 000084 000008 00 WA 0 0 4[ 4] .bss NOBITS00000000 00008c 000004 00 WA 0 0 4[ 5] .rodata PROGBITS 00000000 00008c 000004 00 A 0 0 1[ 6] .commentPROGBITS 00000000 000090 000028 01 MS 0 0 1[ 7] .note.GNU-stack PROGBITS 00000000 0000b8 000000 000 0 1[ 8] .shstrtab STRTAB00000000 0000b8 000051 000 0 1[ 9] .symtab SYMTAB00000000 0002c4 0000f0 1010 10 4[10] .strtab STRTAB00000000 0003b4 000065 000 0 1Key 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:OffsetInfo Type Sym.Value Sym. Name00000010 00000501 R_386_3200000000 .rodata00000015 00000d02 R_386_PC32 00000000 printf0000002e 00000301 R_386_3200000000 .data00000033 00000401 R_386_3200000000 .bss00000046 00000c02 R_386_PC32 00000000 func1There are no unwind sections in this file.Symbol table '.symtab' contains 15 entries:Num: Value Size Type Bind VisNdx Name0: 000000000 NOTYPE LOCAL DEFAULT UND 1: 000000000 FILE LOCAL DEFAULT ABS SimpleSection.c2: 000000000 SECTION LOCAL DEFAULT 1 3: 000000000 SECTION LOCAL DEFAULT 3 4: 000000000 SECTION LOCAL DEFAULT 4 5: 000000000 SECTION LOCAL DEFAULT 5 6: 000000044 OBJECT LOCAL DEFAULT 3 static_var.12227: 000000004 OBJECT LOCAL DEFAULT 4 static_var2.12238: 000000000 SECTION LOCAL DEFAULT 7 9: 000000000 SECTION LOCAL DEFAULT 6 10: 000000004 OBJECT GLOBAL DEFAULT 3 global_init_var11: 000000044 OBJECT GLOBAL DEFAULT COM global_uinit_var12: 00000000 27 FUNC GLOBAL DEFAULT 1 func113: 000000000 NOTYPE GLOBAL DEFAULT UND printf14: 0000001b 53 FUNC GLOBAL DEFAULT 1 mainNo version information found in this file.
下面分析SimpleSection.o文件内容
首先是Elf文件头,其定义为(在/usr/include/elf.h中)
#define EI_NIDENT (16)typedef struct{unsigned chare_ident[EI_NIDENT];/* Magic number and other info */Elf32_Halfe_type;/* Object file type */Elf32_Halfe_machine;/* Architecture */Elf32_Worde_version;/* Object file version */Elf32_Addre_entry;/* Entry point virtual address */Elf32_Offe_phoff;/* Program header table file offset */Elf32_Offe_shoff;/* Section header table file offset */Elf32_Worde_flags;/* Processor-specific flags */Elf32_Halfe_ehsize;/* ELF header size in bytes */Elf32_Halfe_phentsize;/* Program header table entry size */Elf32_Halfe_phnum;/* Program header table entry count */Elf32_Halfe_shentsize;/* Section header table entry size */Elf32_Halfe_shnum;/* Section header table entry count */Elf32_Halfe_shstrndx;/* Section header string table index */} Elf32_Ehdr;
大小为52个字节(16进制表示为0x34),因此SimpleSection.o前52个字节内容为ELF文件头,其二进制表示为:
0000000 457f 464c 0101 0001 0000 0000 0000 00000000010 0001 0003 0001 0000 0000 0000 0000 00000000020 010c 0000 0000 0000 0034 0000 0000 00280000030 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 <func1>:0:55 push %ebp1:89 e5mov %esp,%ebp3:83 ec 18 sub $0x18,%esp6:8b 45 08 mov 0x8(%ebp),%eax9:89 44 24 04mov %eax,0x4(%esp)d:c7 04 24 00 00 00 00 movl $0x0,(%esp)14:e8 fc ff ff ff call 15 <func1+0x15>19:c9 leave 1a:c3 ret 0000001b <main>:1b:55 push %ebp1c:89 e5mov %esp,%ebp1e:83 e4 f0 and $0xfffffff0,%esp21:83 ec 20 sub $0x20,%esp24:c7 44 24 1c 01 00 00 movl $0x1,0x1c(%esp)2b:00 2c:8b 15 04 00 00 00 mov 0x4,%edx32:a1 00 00 00 00 mov 0x0,%eax37:8d 04 02 lea (%edx,%eax,1),%eax3a:03 44 24 1cadd 0x1c(%esp),%eax3e:03 44 24 18add 0x18(%esp),%eax42:89 04 24 mov %eax,(%esp)45:e8 fc ff ff ff call 46 <main+0x2b>4a:8b 44 24 1cmov 0x1c(%esp),%eax4e:c9 leave 4f:c3 ret
代码段刚好对应SimpleSection.o紧接着ELF头的80(0x50)个字节代码。即
8955 83e5 18ec 458b 8908 24440000040 c704 2404 0000 0000 fce8 ffff c9ff 55c30000050 e589 e483 83f0 20ec 44c7 1c24 0001 00000000060 158b 0004 0000 00a1 0000 8d00 0204 44030000070 1c24 4403 1824 0489 e824 fffc ffff 448b0000080 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 2e3500000a0 2032 3032 3131 3130 3732 2820 7270 726500000b0 6c65 6165 6573 0029
实际对应于一个字符串"\0GCC: (GNU) 4.5.2 0127 (prerelease)\0"(感兴趣的话,可以自己将上面的16进制ascii值转换成字符试试,刚好与使用gcc -v命令的得到的结果一致(这个结果是使用od -c SimpleSection.o和上面使用-x选项的结果对比得到的,该命令在我的电脑上输出结果中最后一行为:gcc 版本 4.5.2 0127 (prerelease) (GCC) )
紧接着.note.GNU-Stack段(该段大小为0,所以没有对应的实质性内容)
紧接着从0x000000b8地址开始81(0x51)个字节是.shstrtab段,用来存放段的名称。对应内容为:
2e00 7973 746d 626100000c0 2e00 7473 7472 6261 2e00 6873 7473 747200000d0 6261 2e00 6572 2e6c 6574 7478 2e00 616400000e0 6174 2e00 7362 0073 722e 646f 7461 006100000f0 632e 6d6f 656d 746e 2e00 6f6e 6574 472e0000100 554e 732d 6174 6b63 00
其对应字符串为"\0.symtab\0.strtab\0.shstrtab\0.rel.text\0.data\0.bss\0.rodata\ment\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_Wordsh_name;/* Section name (string tbl index) */Elf32_Wordsh_type;/* Section type */Elf32_Wordsh_flags;/* Section flags */Elf32_Addrsh_addr;/* Section virtual addr at execution */Elf32_Offsh_offset;/* Section file offset */Elf32_Wordsh_size;/* Section size in bytes */Elf32_Wordsh_link;/* Link to another section */Elf32_Wordsh_info;/* Additional section information */Elf32_Wordsh_addralign;/* Section alignment */Elf32_Wordsh_entsize;/* Entry size if section holds table */} Elf32_Shdr;
恰好占据了40个字节。
从0x0000010c开始有11个段,每个段占40个字节大小。
第一个段为0x0000010c-0x00000133,其中内容全部为0,所以不表示任何段。
第二个段为0x00000134-0x0000015b,对应内容为:
001f 0000 0001 0000 0006 00000000140 0000 0000 0034 0000 0050 0000 0000 00000000150 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 00000000160 0009 0000 0000 0000 0000 0000 041c 00000000170 0028 0000 0009 0000 0001 0000 0004 00000000180 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 00000000190 0000 0000 0084 0000 0008 0000 0000 000000001a0 0000 0000 0004 0000 0000 0000
第五个段为0x000001ac-0x00001d4(0x2b,即第43个字节偏移处,即.bss段),对应内容为:
002b 000000001b0 0008 0000 0003 0000 0000 0000 008c 000000001c0 0004 0000 0000 0000 0000 0000 0004 000000001d0 0000 0000
第六个段为0x000001d4-0x000001fc(0x30,即第48个字节偏移处,即.rodata段),对应内容为:
0030 0000 0001 0000 0002 000000001e0 0000 0000 008c 0000 0004 0000 0000 000000001f0 0000 0000 0001 0000 0000 0000
第七个段为0x000001fc-0x00000224(0x38,即第56个字节偏移处,即.comment段),对应内容为:
0038 00000000200 0001 0000 0030 0000 0000 0000 0090 00000000210 0028 0000 0000 0000 0000 0000 0001 00000000220 0001 0000
第八个段为0x00000224-0x0000024c(0x41,即第65个字节偏移处,即.note.GNU-Stack段),对应内容为:
0041 0000 0001 0000 0000 00000000230 0000 0000 00b8 0000 0000 0000 0000 00000000240 0000 0000 0001 0000 0000 0000
第九个段为0x0000024c-0x00000274(0x11,即第17个字节偏移处,即.shstrtab段),对应内容为:
0011 00000000250 0003 0000 0000 0000 0000 0000 00b8 00000000260 0051 0000 0000 0000 0000 0000 0001 00000000270 0000 0000
第十个段为0x00000274-0x0000029c(0x01,即第1个字节偏移处,即.symtab段),对应内容为:
0001 0000 0002 0000 0000 00000000280 0000 0000 02c4 0000 00f0 0000 000a 00000000290 000a 0000 0004 0000 0010 0000
第十一个段为0x0000029c-0x000002c4(0x9,即第9个字节偏移处,即.strtab段),对应内容为:
0009 000000002a0 0003 0000 0000 0000 0000 0000 03b4 000000002b0 0065 0000 0000 0000 0000 0000 0001 000000002c0 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 000000002d0 0000 0000 0001 0000 0000 0000 0000 000000002e0 0004 fff1 0000 0000 0000 0000 0000 000000002f0 0003 0001 0000 0000 0000 0000 0000 00000000300 0003 0003 0000 0000 0000 0000 0000 00000000310 0003 0004 0000 0000 0000 0000 0000 00000000320 0003 0005 0011 0000 0004 0000 0004 00000000330 0001 0003 0021 0000 0000 0000 0004 00000000340 0001 0004 0000 0000 0000 0000 0000 00000000350 0003 0007 0000 0000 0000 0000 0000 00000000360 0003 0006 0032 0000 0000 0000 0004 00000000370 0011 0003 0042 0000 0004 0000 0004 00000000380 0011 fff2 0053 0000 0000 0000 001b 00000000390 0012 0001 0059 0000 0000 0000 0000 000000003a0 0010 0000 0060 0000 001b 0000 0035 000000003b0 0012 0001
符号表结构定义(/usr/include/elf.h):
typedef struct{Elf32_Wordst_name;/* Symbol name (string tbl index) */Elf32_Addrst_value;/* Symbol value */Elf32_Wordst_size;/* Symbol size */unsigned charst_info;/* Symbol type and binding */unsigned charst_other;/* Symbol visibility */Elf32_Sectionst_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 lO .data 00000004 static_var.1222 00000000 lO .bss 00000004 static_var2.1223 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack 00000000 l d .comment 00000000 .comment 00000000 gO .data 00000004 global_init_var 00000004 O *COM* 00000004 global_uinit_var 00000000 gF .text 0000001b func1 00000000 *UND* 00000000 printf 0000001b gF .text 00000035 main
也和使用readelf中符号表相关内容对应(readelf命令输出结果比objdump输出结果看起来更好看一些):
Symbol table '.symtab' contains 15 entries:Num: Value Size Type Bind VisNdx Name0: 000000000 NOTYPE LOCAL DEFAULT UND 1: 000000000 FILE LOCAL DEFAULT ABS SimpleSection.c2: 000000000 SECTION LOCAL DEFAULT 1 3: 000000000 SECTION LOCAL DEFAULT 3 4: 000000000 SECTION LOCAL DEFAULT 4 5: 000000000 SECTION LOCAL DEFAULT 5 6: 000000044 OBJECT LOCAL DEFAULT 3 static_var.12227: 000000004 OBJECT LOCAL DEFAULT 4 static_var2.12238: 000000000 SECTION LOCAL DEFAULT 7 9: 000000000 SECTION LOCAL DEFAULT 6 10: 000000004 OBJECT GLOBAL DEFAULT 3 global_init_var11: 000000044 OBJECT GLOBAL DEFAULT COM global_uinit_var12: 00000000 27 FUNC GLOBAL DEFAULT 1 func113: 000000000 NOTYPE GLOBAL DEFAULT UND printf14: 0000001b 53 FUNC GLOBAL DEFAULT 1 main
15个符号中分析比较重要的符号,其他部分可以自己分析。
首先是0x000002d4-0x000002e3,对应内容:
0001 0000 0000 0000 0000 000000002e0 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 00000000330 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 00000000340 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 00000000370 0011 0003
该部分st_name值为0x00000032,表示常数串中偏移量为0x00000032(50),即global_init_var的首地址。st_info值为0x11,表示全局变量。其他成员含义与上一个符号类似。
还有一个重要符号地址为0x00000374-0x00000383,对应内容:
0042 0000 0004 0000 0004 00000000380 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 00000000390 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 697400003c0 6e6f 632e 7300 6174 6974 5f63 6176 2e7200003d0 3231 3232 7300 6174 6974 5f63 6176 327200003e0 312e 3232 0033 6c67 626f 6c61 695f 696e00003f0 5f74 6176 0072 6c67 626f 6c61 755f 6e690000400 7469 765f 7261 6600 6e75 3163 7000 69720000410 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 00000000420 0501 0000 0015 0000 0d02 0000 002e 00000000430 0301 0000 0033 0000 0401 0000 0046 00000000440 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-i386RELOCATION RECORDS FOR [.text]:OFFSET TYPE VALUE 00000010 R_386_32.rodata00000015 R_386_PC32 printf0000002e R_386_32.data00000033 R_386_32.bss00000046 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_Offp_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 ofprogram headers is too large to fit into e_phnum. Instead the realvalue 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_DYNAMIC2/* 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_TLS7/* Thread-local storage segment */#define PT_NUM8/* 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_SUNWBSS0x6ffffffa/* 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_MASKPROC0xf0000000/* Processor-specific */
感兴趣的可以自己分析,并和readelf -a得到结果对比来加深理解。
例如我写的一个小测试程序:
#include <stdio.h>#include <elf.h>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 0x8048340There are 8 program headers, starting at offset 52Program Headers:Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg AlignPHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1[Requesting program interpreter: /lib/ld-linux.so.2]LOAD 0x000000 0x08048000 0x08048000 0x005b8 0x005b8 R E 0x1000LOAD 0x0005b8 0x080495b8 0x080495b8 0x0010c 0x00114 RW 0x1000DYNAMIC 0x0005cc 0x080495cc 0x080495cc 0x000d0 0x000d0 RW 0x4NOTE 0x000148 0x08048148 0x08048148 0x00020 0x00020 R 0x4GNU_EH_FRAME 0x000514 0x08048514 0x08048514 0x00024 0x00024 R 0x4GNU_STACK0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4Section to Segment mapping:Segment Sections...0001.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 00000000010 0002 0003 0001 0000 8340 0804 0034 00000000020 07e8 0000 0000 0000 0034 0020 0008 00280000030 001e 001b 0006 0000 0034 0000 8034 08040000040 8034 0804 0100 0000 0100 0000 0005 00000000050 0004 0000 0003 0000 0134 0000 8134 08040000060 8134 0804 0013 0000 0013 0000 0004 00000000070 0001 0000 0001 0000 0000 0000 8000 08040000080 8000 0804 05b8 0000 05b8 0000 0005 00000000090 1000 0000 0001 0000 05b8 0000 95b8 080400000a0 95b8 0804 010c 0000 0114 0000 0006 000000000b0 1000 0000 0002 0000 05cc 0000 95cc 080400000c0 95cc 0804 00d0 0000 00d0 0000 0006 000000000d0 0004 0000 0004 0000 0148 0000 8148 080400000e0 8148 0804 0020 0000 0020 0000 0004 000000000f0 0004 0000 e550 6474 0514 0000 8514 08040000100 8514 0804 0024 0000 0024 0000 0004 00000000110 0004 0000 e551 6474 0000 0000 0000 00000000120 0000 0000 0000 0000 0000 0000 0006 00000000130 0004 0000 0000134
文件头段第一个表(0x00000034-0x00000053)内容:
0006 0000 0034 0000 8034 08040000040 8034 0804 0100 0000 0100 0000 0005 00000000050 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个字节。
其他内容不再详细分析,感兴趣的可以自己探索研究
如果觉得《elf文件格式实例解析》对你有帮助,请点赞、收藏,并留下你的观点哦!