a.out 文g包含 7 ?sectionQ格式如下:exec headerQ执行头部,也可理解为文件头部)
text segmentQ文本段Q?br>data segment(数据D?
text relocations(文本重定位段)
data relocationsQ数据重定位D)
symbol tableQ符可Q?br>string tableQ字W串表)
执行头部的数据结构:struct exec {
unsigned long a_midmag; /* 数和其它信?*/
unsigned long a_text; /* 文本D늚长度 */
unsigned long a_data; /* 数据D늚长度 */
unsigned long a_bss; /* BSSD늚长度 */
unsigned long a_syms; /* W号表的长度 */
unsigned long a_entry; /* E序q入?*/
unsigned long a_trsize; /* 文本重定位表的长?*/
unsigned long a_drsize; /* 数据重定位表的长?*/
};
文g头部主要描述了各?section 的长度,比较重要的字D| a_entryQ程序进入点Q,代表了系l在加蝲E序q初试化各种环境后开始执行程序代码的入口。这个字D在后面讨论?ELF 文g头部中也有出现。由 a.out 格式和头部数据结构我们可以看出,a.out 的格式非常紧凑,只包含了E序q行所必须的信息(文本、数据、BSSQ,而且每个 section 的顺序是固定的。这U结构缺乏扩展性,如不能包?C"可执行文件中常见的调试信息,最初的 UNIX 黑客?a.out 文g调试使用的工h adbQ?adb 是一U机器语a调试器!
a.out 文g中包含符可和两个重定位表,q三个表的内容在q接目标文g以生成可执行文g时v作用。在最l可执行?a.out 文g中,q三个表的长度都?0。a.out 文g在连接时把所有外部定义包含在可执行程序中Q如果从E序设计的角度来看,q是一U硬~码方式Q或者可UCؓ模块之间是强藕和的。在后面的讨ZQ我们将会具体看到ELF格式和动态连接机制是如何Ҏq行改进的?/p>
a.out 是早期UNIXpȝ使用的可执行文g格式Q由 AT&T 设计Q现在基本上已被 ELF 文g格式代替。a.out 的设计比较简单,但其设计思想明显的被后箋的可执行文g格式所l承和发扬。可以参?参考资?16 和阅?参考资?15 源代码加深对 a.out 格式的理解?参考资?12 讨论了如何在"C"的红帽LINUXq行 a.out 格式文g?/p>
COFF 文g格式分析
COFF 格式?a.out 格式要复杂一些,最重要的是包含一个节D表(section table)Q因此除?.textQ?dataQ和 .bss 区段以外Q还可以包含其它的区Dc另外也多了一个可选的头部Q不同的操作pȝ可一Ҏ头部做特定的定义?/p>
COFF 文g格式如下QFile Header(文g头部)
Optional Header(可选文件头?
Section 1 Header(节头?
………
Section n Header(节头?
Raw Data for Section 1(节数?
Raw Data for Section n(节数?
Relocation Info for Sect. 1(节重定位数据)
Relocation Info for Sect. n(节重定位数据)
Line Numbers for Sect. 1(节行h?
Line Numbers for Sect. n(节行h?
Symbol table(W号?
String table(字符串表)
文g头部的数据结构:struct filehdr
{
unsigned short f_magic; /* 数 */
unsigned short f_nscns; /* 节个?*/
long f_timdat; /* 文g建立旉 */
long f_symptr; /* W号表相Ҏ件的偏移?*/
long f_nsyms; /* W号表条目个?*/
unsigned short f_opthdr; /* 可选头部长?*/
unsigned short f_flags; /* 标志 */
};
COFF 文g头部中魔C其它两种格式的意义不太一P它是表示针对的机器类型,例如 0x014c 相对?I386 q_Q?0x268 相对?Motorola 68000pd{。当 COFF 文g为可执行文gӞ字段 f_flags 的gؓ F_EXECQ?X00002Q,同时也表C此文g没有未解析的W号Q换句话_也就是重定位在连接时已l完成。由此也可以看出Q原始的 COFF 格式不支持动态连接。ؓ了解册个问题以及增加一些新的特性,一些操作系l对 COFF 格式q行了扩展。Microsoft 设计了名?PEQPortable ExecutableQ的文g格式Q主要扩展是?COFF 文g头部之上增加了一些专用头部,具体l节请参?参考资?18Q某?UNIX pȝ也对 COFF 格式q行了扩展,?XCOFFQextended common object file formatQ格式,支持动态连接,请参?参考资?5?/p>
紧接文g头部的是可选头部,COFF 文g格式规范中规定可选头部的长度可以?0Q但?LINUX pȝ下可选头部是必须存在的。下面是 LINUX 下可选头部的数据l构Qtypedef struct
{
char magic[2]; /* 数 */
char vstamp[2]; /* 版本?*/
char tsize[4]; /* 文本D长?*/
char dsize[4]; /* 已初始化数据D长?*/
char bsize[4]; /* 未初始化数据D长?*/
char entry[4]; /* E序q入?*/
char text_start[4]; /* 文本D基地址 */
char data_start[4]; /* 数据D基地址 */
}
COFF_AOUTHDR;
字段 magic ?0413 时表C?COFF 文g是可执行的,注意到可选头部中昑ּ定义了程序进入点Q标准的 COFF 文g没有明确的定义程序进入点的|通常是从 .text 节开始执行,但这U设计ƈ不好?/p>
前面我们提到QCOFF 格式?a.out 格式多了一个节D表Q一个节头条目描qC个节数据的细节,因此 COFF 格式能包含更多的节,或者说可以Ҏ实际需要,增加特定的节Q具体表现在 COFF 格式本n的定义以及稍早提及的 COFF 格式扩展。我个h认ؓQ节D表的出现可能是 COFF 格式相对 a.out 格式最大的q步。下面我们将单描q?COFF 文g中节的数据结构,因ؓ节的意义更多体现在程序的~译和连接上Q所以本文不对其做更多的描述。此外,ELF 格式?COFF格式对节的定义非常相|在随后的 ELF 格式分析中,我们省略相兌论。struct COFF_scnhdr
{
char s_name[8]; /* 节名U?*/
char s_paddr[4]; /* 物理地址 */
char s_vaddr[4]; /* 虚拟地址 */
char s_size[4]; /* 节长?*/
char s_scnptr[4]; /* 节数据相Ҏ件的偏移?*/
char s_relptr[4]; /* 节重定位信息偏移?*/
char s_lnnoptr[4]; /* 节行信息偏移?*/
char s_nreloc[2]; /* 节重定位条目?*/
char s_nlnno[2]; /* 节行信息条目?*/
char s_flags[4]; /* D|?*/
};
有一炚w要注意:LINUXpȝ中头文gcoff.h中对字段s_paddr的注释是"physical address"Q但g应该理解?节被加蝲到内存中所占用的空间长?。字Ds_flags标记该节的类型,如文本段、数据段、BSSD늭。在COFF的节中也出现了行信息Q行信息描述了二q制代码与源代码的行号之间的Ҏ关系Q在调试时很有用?br>转自Q?br>http://www.360doc.com/content/080313/18/59039_1115300.html
1. 概述
Executable and linking format(ELF)文g是x86 Linuxpȝ下的一U常用目标文?object file)格式Q有三种主要cd:
(1)适于q接的可重定位文?relocatable file)Q可与其它目标文件一起创建可执行文g和共享目标文件?/p>
(2)适于执行的可执行文g(executable file)Q用于提供程序的q程映像Q加载的内存执行?/p>
(3)׃n目标文g(shared object file),q接器可它与其它可重定位文件和׃n目标文gq接成其它的目标文gQ动态连接器又可它与可执行文g和其它共享目标文件结合v来创Z个进E映像?/p>
ELF文g格式比较复杂Q本文只是简要介l它的结构,希望能给想了解ELF文gl构的读者以帮助。具体详的资料请参阅专门的ELF文档?/p>
2. 文g格式
Z方便和高效,ELF文g内容有两个^行的视角:一个是E序q接角度Q另一个是E序q行角度Q如?所C?/p>
ELF header在文件开始处描述了整个文件的l织QSection提供了目标文件的各项信息Q如指o、数据、符可、重定位信息{)QProgram header table指出怎样创徏q程映像Q含有每个program header的入口,Section header table包含每一个section的入口,l出名字、大等信息?/p>
?
3. 数据表示
ELF数据~码序与机器相养I数据cd有六U,见表1?/p>
4. ELF文g?/p>
象bmp、exe{文件一PELF的文件头包含整个文g的控制结构。它的定义如下:
#define EI_NIDENT 16 typedef struct{ unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; }Elf32_Ehdr; |
其中E_ident?6个字节标明是个ELF文gQ?F+'E'+'L'+'F'+class +data+version+padQ。E_type表示文gcdQ?表示可执行文件。E_machine说明机器cdQ?表示386机器Q?表示MIPS机器。E_entryl出q程开始的虚地址Q即pȝ控制{Uȝ位置。E_phoff指出program header table的文件偏U,e_phentsize表示一个program header表中的入口的长度Q字节数表示Q,e_phnuml出program header表中的入口数目。类似的Qe_shoffQe_shentsizeQe_shnum 分别表示section header表的文g偏移Q表中每个入口的的字节数和入口数目。E_flagsl出与处理器相关的标志,e_ehsizel出ELF文g头的长度Q字节数表示Q。E_shstrndx表示section名表的位|,指出在section header表中的烦引?/p>
下面有个elf文g头的例子Q可以对照理解,见图2?/p>
?
5. section header
目标文g的section header table可以定位所有的sectionQ它是一个Elf32_Shdrl构的数l,Section头表的烦引是q个数组的下标。有些烦引号是保留的Q目标文件不能用这些特D的索引?/p>
Section包含目标文g除了ELF文g头、程序头表、section头表的所有信息,而且目标文gsection满几个条g:
(1)目标文g中的每个section都只有一个section头项描述Q可以存在不指示Msection的section头项?/p>
(2)每个section在文件中占据一块连l的I间?/p>
(3)Section之间不可重叠?/p>
(4)目标文g可以有非zdI间Q各Uheaders和sections没有覆盖目标文g的每一个字节,q些非活动空间是没有定义的?/p>
Section headerl构定义如下Q?/p>
typedef struct{ Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; }Elf32_Shdr; |
其中sh_name指出section的名字,它的值是后面会讲到的section header string table中的索引Q指Z个以nulll尾的字W串。Sh_type是类别,sh_flags指示该section在进E执行时的特性。Sh_addr指出若此section在进E的内存映像中出玎ͼ则给出开始的虚地址。Sh_offsetl出此section在文件中的偏UR其它字D늚意义不太常用Q在此不l述?/p>
文g的section含有E序和控制信息,pȝ使用一些特定的sectionQƈ有其固定的类型和属性(由sh_type和sh_info指出Q。下面介l几个常用到的section:“.bss”D含有占据程序内存映像的未初始化数据Q当E序开始运行时pȝ对这D|据初始ؓӞ但这个sectionq不占文件空间?#8220;.data.”?#8220;data1”D包含占据内存映像的初始化数据?#8220;.rodata”?#8220;.rodata1”D含E序映像中的只读数据?#8220;.shstrtab”D含有每个section的名字,由section入口l构中的sh_name索引?#8220;.strtab”D含有表C符可(symbol table)名字的字W串?#8220;.symtab”D含有文件的W号表,在后文专门介l?#8220;.text”D包含程序的可执行指令?/p>
6. symbol table
目标文g的符可包含定位或重定位E序W号定义和引用时所需要的信息。符可入口l构定义如下:
typedef struct{ Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; Unsigned char st_info; Unsigned char st_other; Elf32_Half st_shndx; }Elf32_Sym; |
其中st_name包含指向W号表字W串?strtab)中的索引Q从而可以获得符号名。St_value指出W号的|可能是一个绝对倹{地址{。St_size指出W号相关的内存大,比如一个数据结构包含的字节数等。St_info规定了符Lcd和绑定属性,指出q个W号是一个数据名、函数名、section名还是源文g名;q且指出该符Ll定属性是local、globalq是weak?/p>
转自Q?/p>
http://tech.ccidnet.com/art/305/20020126/9179_1.html
相对于其它文件类型,可执行文件可能是一个操作系l中最重要的文件类型,因ؓ它们是完成操作的真正执行者。可执行文g的大、运行速度、资源占用情况以及可扩展性、可UL性等与文件格式的定义和文件加载过E紧密相兟뀂研I可执行文g的格式对~写高性能E序和一些黑客技术的q用都是非常有意义的?/p>
不管何种可执行文件格式,一些基本的要素是必ȝQ显而易见的Q文件中应包含代码和数据。因为文件可能引用外部文件定义的W号(变量和函?Q因此重定位信息和符号信息也是需要的。一些辅助信息是可选的Q如调试信息、硬件信息等。基本上L一U可执行文g格式都是按区间保存上qC息,UCؓD?Segment)或节(Section)。不同的文g格式中段和节的含义可能有l微区别Q但Ҏ上下文关pd以很清楚的理解,q不是关键问题。最后,可执行文仉常都有一个文件头部以描述本文件的Ml构?/p>
相对可执行文件有三个重要的概念:~译(compile)、连?linkQ也可称为链接、联?、加?load)。源E序文g被编译成目标文gQ多个目标文件被q接成一个最l的可执行文Ӟ可执行文件被加蝲到内存中q行。因为本文重Ҏ讨论可执行文件格式,因此加蝲q程也相寚w点讨论。下面是LINUXq_下ELF文g加蝲q程的一个简单描q?/p>
1Q内栔R先读ELF文g的头部,然后Ҏ头部的数据指C分别读入各U数据结构,扑ֈ标记为可加蝲(loadable)的段Qƈ调用函数 mmap()把段内容加蝲到内存中。在加蝲之前Q内核把D늚标记直接传递给 mmap()Q段的标记指CD在内存中是否可诅R可写,可执行。显Ӟ文本D|只读可执行,而数据段是可d写。这U方式是利用了现代操作系l和处理器对内存的保护功能。著名的Shellcode(参考资?17)的编写技巧则是突破此保护功能的一个实际例子?/p> 2Q内核分析出ELF文g标记?PT_INTERP 的段中所对应的动态连接器名称Qƈ加蝲动态连接器。现?LINUX pȝ的动态连接器通常?/lib/ld-linux.so.2Q相关细节在后面有详l描q?
3Q内核在新进E的堆栈中设|一些标?值对Q以指示动态连接器的相x作?/p>
4Q内核把控制传递给动态连接器?/p>
5Q动态连接器查程序对外部文g(׃n?的依赖性,q在需要时对其q行加蝲?/p>
6Q动态连接器对程序的外部引用q行重定位,通俗的讲Q就是告诉程序其引用的外部变?函数的地址Q此地址位于׃n库被加蝲在内存的区间内。动态连接还有一个gq?Lazy)定位的特性,卛_?真正"需要引用符h才重定位Q这Ҏ高程序运行效率有极大帮助?/p>
7Q动态连接器执行在ELF文g中标Cؓ .init 的节的代码,q行E序q行的初始化。在早期pȝ中,初始化代码对应函?_init(void)(函数名强制固?Q在Cpȝ中,则对应Ş式ؓ
void
__attribute((constructor))
init_function(void)
{
……
}
其中函数名ؓL?br>
8Q动态连接器把控制传递给E序Q从 ELF 文g头部中定义的E序q入点开始执行。在 a.out 格式和ELF格式中,E序q入点的值是昑ּ存在的,?COFF 格式中则是由规范隐含定义?/p>
从上面的描述可以看出Q加载文件最重要的是完成两g事情Q加载程序段和数据段到内?q行外部定义W号的重定位。重定位是程序连接中一个重要概c我们知道,一个可执行E序通常是由一个含?main() 的主E序文g、若q目标文件、若q共享库(Shared Libraries)l成?注:采用一些特别的技巧,也可~写没有 main 函数的程序,请参阅参考资?2)一?C E序可能引用׃n库定义的变量或函敎ͼ换句话说是E序q行时必ȝ道这些变?函数的地址。在静态连接中Q程序所有需要用的外部定义都完全包含在可执行程序中Q而动态连接则只在可执行文件中讄相关外部定义的一些引用信息,真正的重定位是在E序q行之时。静态连接方式有两个大问题:如果库中变量或函数有M变化都必重新编译连接程?如果多个E序引用同样的变?函数Q则此变?函数会在文g/内存中出现多ơ,费盘/内存I间。比较两U连接方式生成的可执行文件的大小Q可以看出有明显的区别?/p> a.out 文g格式分析
a.out 格式在不同的机器q_和不同的 UNIX 操作pȝ上有d的不同,例如?MC680x0 q_上有 6 ?section。下面我们讨论的是最"标准"的格式?/p>
a.out 文g包含 7 ?sectionQ格式如下:
exec header(执行头部Q也可理解ؓ文g头部)
text segment(文本D?
data segment(数据D?
text relocations(文本重定位段)
data relocations(数据重定位段)
symbol table(W号?
string table(字符串表)
执行头部的数据结构:
struct exec {
unsigned long a_midmag; /* 数和其它信?*/
unsigned long a_text; /* 文本D늚长度 */
unsigned long a_data; /* 数据D늚长度 */
unsigned long a_bss; /* BSSD늚长度 */
unsigned long a_syms; /* W号表的长度 */
unsigned long a_entry; /* E序q入?*/
unsigned long a_trsize; /* 文本重定位表的长?*/
unsigned long a_drsize; /* 数据重定位表的长?*/
};
文g头部主要描述了各?section 的长度,比较重要的字D| a_entry(E序q入?Q代表了pȝ在加载程序ƈ初试化各U环境后开始执行程序代码的入口。这个字D在后面讨论?ELF 文g头部中也有出现。由 a.out 格式和头部数据结构我们可以看出,a.out 的格式非常紧凑,只包含了E序q行所必须的信?文本、数据、BSS)Q而且每个 section 的顺序是固定的。这U结构缺乏扩展性,如不能包?C"可执行文件中常见的调试信息,最初的 UNIX 黑客?a.out 文g调试使用的工h adbQ?adb 是一U机器语a调试?
a.out 文g中包含符可和两个重定位表,q三个表的内容在q接目标文g以生成可执行文g时v作用。在最l可执行?a.out 文g中,q三个表的长度都?0。a.out 文g在连接时把所有外部定义包含在可执行程序中Q如果从E序设计的角度来看,q是一U硬~码方式Q或者可UCؓ模块之间是强藕和的。在后面的讨ZQ我们将会具体看到ELF格式和动态连接机制是如何Ҏq行改进的?/p>
a.out 是早期UNIXpȝ使用的可执行文g格式Q由 AT&T 设计Q现在基本上已被 ELF 文g格式代替。a.out 的设计比较简单,但其设计思想明显的被后箋的可执行文g格式所l承和发扬。可以参阅参考资?16 和阅d考资?15 源代码加深对 a.out 格式的理解。参考资?12 讨论了如何在"C"的红帽LINUXq行 a.out 格式文g?/p> COFF 文g格式分析
COFF 格式?a.out 格式要复杂一些,最重要的是包含一个节D表(section table)Q因此除?.textQ?dataQ和 .bss 区段以外Q还可以包含其它的区Dc另外也多了一个可选的头部Q不同的操作pȝ可一Ҏ头部做特定的定义?/p>
COFF 文g格式如下Q?/p>
File Header(文g头部)
Optional Header(可选文件头?
Section 1 Header(节头?
………
Section n Header(节头?
Raw Data for Section 1(节数?
Raw Data for Section n(节数?
Relocation Info for Sect. 1(节重定位数据)
Relocation Info for Sect. n(节重定位数据)
Line Numbers for Sect. 1(节行h?
Line Numbers for Sect. n(节行h?
Symbol table(W号?
String table(字符串表)
文g头部的数据结构:
struct filehdr
{
unsigned short f_magic; /* 数 */
unsigned short f_nscns; /* 节个?*/
long f_timdat; /* 文g建立旉 */
long f_symptr; /* W号表相Ҏ件的偏移?*/
long f_nsyms; /* W号表条目个?*/
unsigned short f_opthdr; /* 可选头部长?*/
unsigned short f_flags; /* 标志 */
};
COFF 文g头部中魔C其它两种格式的意义不太一P它是表示针对的机器类型,例如 0x014c 相对?I386 q_Q?0x268 相对?Motorola 68000pd{。当 COFF 文g为可执行文gӞ字段 f_flags 的gؓ F_EXEC(0X00002)Q同时也表示此文件没有未解析的符P换句话说Q也是重定位在q接时就已经完成。由此也可以看出Q原始的 COFF 格式不支持动态连接。ؓ了解册个问题以及增加一些新的特性,一些操作系l对 COFF 格式q行了扩展。Microsoft 设计了名?PE(Portable Executable)的文件格式,主要扩展是在 COFF 文g头部之上增加了一些专用头部,具体l节请参阅参考资?18Q某?UNIX pȝ也对 COFF 格式q行了扩展,?XCOFF(extended common object file format)格式Q支持动态连接,请参阅参考资?5?/p>
紧接文g头部的是可选头部,COFF 文g格式规范中规定可选头部的长度可以?0Q但?LINUX pȝ下可选头部是必须存在的。下面是 LINUX 下可选头部的数据l构Q?/p> typedef struct
{
char magic[2]; /* 数 */
char vstamp[2]; /* 版本?*/
char tsize[4]; /* 文本D长?*/
char dsize[4]; /* 已初始化数据D长?*/
char bsize[4]; /* 未初始化数据D长?*/
char entry[4]; /* E序q入?*/
char text_start[4]; /* 文本D基地址 */
char data_start[4]; /* 数据D基地址 */
}
COFF_AOUTHDR;
字段 magic ?0413 时表C?COFF 文g是可执行的,注意到可选头部中昑ּ定义了程序进入点Q标准的 COFF 文g没有明确的定义程序进入点的|通常是从 .text 节开始执行,但这U设计ƈ不好?/p>
前面我们提到QCOFF 格式?a.out 格式多了一个节D表Q一个节头条目描qC个节数据的细节,因此 COFF 格式能包含更多的节,或者说可以Ҏ实际需要,增加特定的节Q具体表现在 COFF 格式本n的定义以及稍早提及的 COFF 格式扩展。我个h认ؓQ节D表的出现可能是 COFF 格式相对 a.out 格式最大的q步。下面我们将单描q?COFF 文g中节的数据结构,因ؓ节的意义更多体现在程序的~译和连接上Q所以本文不对其做更多的描述。此外,ELF 格式?COFF格式对节的定义非常相|在随后的 ELF 格式分析中,我们省略相兌论?/p>
struct COFF_scnhdr
{
char s_name[8]; /* 节名U?*/
char s_paddr[4]; /* 物理地址 */
char s_vaddr[4]; /* 虚拟地址 */
char s_size[4]; /* 节长?*/
char s_scnptr[4]; /* 节数据相Ҏ件的偏移?*/
char s_relptr[4]; /* 节重定位信息偏移?*/
char s_lnnoptr[4]; /* 节行信息偏移?*/
char s_nreloc[2]; /* 节重定位条目?*/
char s_nlnno[2]; /* 节行信息条目?*/
char s_flags[4]; /* D|?*/
};
有一炚w要注意:LINUXpȝ中头文gcoff.h中对字段s_paddr的注释是"physical address"Q但g应该理解?节被加蝲到内存中所占用的空间长?。字Ds_flags标记该节的类型,如文本段、数据段、BSSD늭。在 COFF的节中也出现了行信息Q行信息描述了二q制代码与源代码的行号之间的Ҏ关系Q在调试时很有用?/p> ELF文g格式分析
ELF 文g有三U类型:可重定位文gQ也是通常U的目标文gQ后~?o。共享文Ӟ也就是通常U的库文Ӟ后缀?so。可执行文gQ本文主要讨论的文g格式Qȝ来说Q可执行文g的格式与上述两种文g的格式之间的区别主要在于观察的角度不同:一U称接视?Linking View)Q一U称为执行视?Execution View)?/p>
首先看看ELF文g的M布局Q?/p>
ELF header(ELF头部)
Program header table(E序头表)
Segment1(D?)
Segment2(D?)
………
Sengmentn(Dn)
Setion header table(节头表,可?
D는若干个节(Section)构成,节头表对每一个节的信息有相关描述。对可执行程序而言Q节头表是可选的。参考资?1中作者谈到把节头表的所有数据全部设|ؓ0Q程序也能正运?ELF头部是一个关于本文g的\U图(road map)Q从M上描q文件的l构。下面是ELF头部的数据结构:
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* 数和相关信?*/
Elf32_Half e_type; /* 目标文gcd */
Elf32_Half e_machine; /* g体系 */
Elf32_Word e_version; /* 目标文g版本 */
Elf32_Addr e_entry; /* E序q入?*/
Elf32_Off e_phoff; /* E序头部偏移?*/
Elf32_Off e_shoff; /* 节头部偏U量 */
Elf32_Word e_flags; /* 处理器特定标?*/
Elf32_Half e_ehsize; /* ELF头部长度 */
Elf32_Half e_phentsize; /* E序头部中一个条目的长度 */
Elf32_Half e_phnum; /* E序头部条目个数 */
Elf32_Half e_shentsize; /* 节头部中一个条目的长度 */
Elf32_Half e_shnum; /* 节头部条目个?*/
Elf32_Half e_shstrndx; /* 节头部字W表索引 */
} Elf32_Ehdr;
e_ident [0]-e_ident[3]包含了ELF文g的魔敎ͼ依次?x7f?E'?L'?F'。注意,M一个ELF 文g必须包含此魔数。参考资?3中讨Z利用E序、工兗?Proc文gpȝ{多U查看ELF数的方法。e_ident[4]表示gpȝ的位敎ͼ1代表32位,2代表64位?e_ident[5] 表示数据~码方式Q?代表印W安排序(最大有意义的字节占有最低的地址)Q?代表大印W安排序(最大有意义的字节占有最高的地址)。e_ident [6]指定ELF头部的版本,当前必须?。e_ident[7]到e_ident[14]是填充符Q通常?。ELF格式规范中定义这几个字节是被忽略的,但实际上是这几个字节完全可以可被利用。如病毒Lin/Glaurung.676/666(参考资?1)讄 e_ident[7]?x21,表示本文件已被感?或者存攑֏执行代码(参考资?2)。ELF头部中大多数字段都是对子头部数据的描qͼ其意义相Ҏ较简单。值得注意的是某些病毒可能修改字段e_entry(E序q入?的|以指向病毒代码,例如上面提到的病毒Lin/Glaurung.676/666?/p>
一个实际可执行文g的文件头部Ş式如下:(利用命oreadelf)
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: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x80483cc
Start of program headers: 52 (bytes into file)
Start of section headers: 14936 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 6
Size of section headers: 40 (bytes)
Number of section headers: 34
Section header string table index: 31
紧接ELF头部的是E序头表Q它是一个结构数l,包含了ELF头表中字De_phnum定义的条目,l构描述一个段或其他系l准备执行该E序所需要的信息?/p>
typedef struct {
Elf32_Word p_type; /* D늱?*/
Elf32_Off p_offset; /* D位|相对于文g开始处的偏U量 */
Elf32_Addr p_vaddr; /* D在内存中的地址 */
Elf32_Addr p_paddr; /* D늚物理地址 */
Elf32_Word p_filesz; /* D在文g中的长度 */
Elf32_Word p_memsz; /* D在内存中的长度 */
Elf32_Word p_flags; /* D늚标记 */
Elf32_Word p_align; /* D在内存中对齐标?*/
} Elf32_Phdr;
在详l讨论可执行文gE序头表之前Q首先查看一个实际文件的输出Q?/p>
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4
INTERP 0x0000f4 0x080480f4 0x080480f4 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00684 0x00684 R E 0x1000
LOAD 0x000684 0x08049684 0x08049684 0x00118 0x00130 RW 0x1000
DYNAMIC 0x000690 0x08049690 0x08049690 0x000c8 0x000c8 RW 0x4
NOTE 0x000108 0x08048108 0x08048108 0x00020 0x00020 R 0x4
Section to Segment mapping:Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
03 .data .dynamic .ctors .dtors .jcr .got .bss
04 .dynamic
05 .note.ABI-tag
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 080480f4 0000f4 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048108 000108 000020 00 A 0 0 4
[ 3] .hash HASH 08048128 000128 000040 04 A 4 0 4
[ 4] .dynsym DYNSYM 08048168 000168 0000b0 10 A 5 1 4
[ 5] .dynstr STRTAB 08048218 000218 00007b 00 A 0 0 1
[ 6] .gnu.version VERSYM 08048294 000294 000016 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 080482ac 0002ac 000030 00 A 5 1 4
[ 8] .rel.dyn REL 080482dc 0002dc 000008 08 A 4 0 4
[ 9] .rel.plt REL 080482e4 0002e4 000040 08 A 4 b 4
[10] .init PROGBITS 08048324 000324 000017 00 AX 0 0 4
[11] .plt PROGBITS 0804833c 00033c 000090 04 AX 0 0 4
[12] .text PROGBITS 080483cc 0003cc 0001f8 00 AX 0 0 4
[13] .fini PROGBITS 080485c4 0005c4 00001b 00 AX 0 0 4
[14] .rodata PROGBITS 080485e0 0005e0 00009f 00 A 0 0 32
[15] .eh_frame PROGBITS 08048680 000680 000004 00 A 0 0 4
[16] .data PROGBITS 08049684 000684 00000c 00 WA 0 0 4
[17] .dynamic DYNAMIC 08049690 000690 0000c8 08 WA 5 0 4
[18] .ctors PROGBITS 08049758 000758 000008 00 WA 0 0 4
[19] .dtors PROGBITS 08049760 000760 000008 00 WA 0 0 4
[20] .jcr PROGBITS 08049768 000768 000004 00 WA 0 0 4
[21] .got PROGBITS 0804976c 00076c 000030 04 WA 0 0 4
[22] .bss NOBITS 0804979c 00079c 000018 00 WA 0 0 4
[23] .comment PROGBITS 00000000 00079c 000132 00 0 0 1
[24] .debug_aranges PROGBITS 00000000 0008d0 000098 00 0 0 8
[25] .debug_pubnames PROGBITS 00000000 000968 000040 00 0 0 1
[26] .debug_info PROGBITS 00000000 0009a8 001cc6 00 0 0 1
[27] .debug_abbrev PROGBITS 00000000 00266e 0002cc 00 0 0 1
[28] .debug_line PROGBITS 00000000 00293a 0003dc 00 0 0 1
[29] .debug_frame PROGBITS 00000000 002d18 000048 00 0 0 4[30] .debug_str PROGBITS 00000000 002d60 000bcd 01 MS 0 0 1
[31] .shstrtab STRTAB 00000000 00392d 00012b 00 0 0 1
[32] .symtab SYMTAB 00000000 003fa8 000740 10 33 56 4
[33] .strtab STRTAB 00000000 0046e8 000467 00 0 0 1
对一个ELF可执行程序而言Q一个基本的D|标记p_type为PT_INTERP的段Q它表明了运行此E序所需要的E序解释?/lib/ld- linux.so.2)Q实际上也就是动态连接器(dynamic linker)。最重要的段是标记p_type为PT_LOAD的段Q它表明了ؓq行E序而需要加载到内存的数据。查看上面实际输入,可以看见有两个可 LOADD,W一个ؓ只读可执?FLg为R E),W二个ؓ可读可写(Flg为RW)。段1包含了文本节.textQ注意到ELF文g头部中程序进入点的gؓ0x80483ccQ正好是指向? text在内存中的地址。段二包含了数据?dataQ此数据节中数据是可d写的Q相对的只读数据?rodata包含在段1中。ELF格式可以?COFF格式包含更多的调试信息,如上面所列出的Ş式ؓ.debug_xxx的节。在I386q_LINUXpȝ下,用命令file查看一个ELF可执行程序的可能输出是:a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped?/p>
ELF文g中包含了动态连接器的全路径Q内核定?正确"的动态连接器在内存中的地址?正确"q行可执行文件的保证Q参考资?13讨论了如何通过查找动态连接器在内存中的地址以达到颠?Subversiver)动态连接机制的Ҏ?/p>
最后我们讨论ELF文g的动态连接机制。每一个外部定义的W号在全局偏移?Global Offset Table GOT)中有相应的条?如果W号是函数则在过E连接表(Procedure Linkage Table PLT)中也有相应的条目Q且一个PLT条目对应一个GOT条目。对外部定义函数解析可能是整个ELF文g规范中最复杂的,下面是函数符可析过E的一个描q?/p>
1Q代码中调用外部函数func,语句形式为call 0xaabbccdd,地址0xaabbccdd实际上就是符号func在PLT表中对应的条目地址(假设地址为标?PLT2)?/p>
2QPLT表的形式如下
.PLT0: pushl 4(