计算机操作系统实验_解析ELF文件
ELF文件结构描述

ELF文件结构描述3.4 ELF文件结构描述我们已经通过SimpleSection.o的结构大致了解了ELF文件的轮廓,接着就来看看ELF 文件的结构格式。
图3-4描述的是ELF目标文件的总体结构,我们省去了ELF一些繁琐的结构,把最重要的结构提取出来,形成了如图3-4所示的ELF文件基本结构图,随着我们讨论的展开,ELF文件结构会在这个基本结构之上慢慢变得复杂起来。
ELF目标文件格式的最前部是ELF文件头(ELF Header),它包含了描述整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。
紧接着是ELF文件各个段。
其中ELF文件中与段有关的重要结构就是段表(Section Header Table),该表描述了ELF文件包含的所有段的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限及段的其他属性。
接着将详细分析ELF文件头、段表等ELF关键的结构。
另外还会介绍一些ELF中辅助的结构,比如字符串表、符号表等,这些结构我们在本节只是简单介绍一下,到相关章节中再详细展开。
3.4.1 文件头我们可以用readelf命令来详细查看ELF文件,代码如清单3-2所示。
ELF文件头结构及相关常数被定义在“/usr/include/elf.h”里,因为ELF文件在各种平台下都通用,ELF文件有32位版本和64位版本。
它的文件头结构也有这两种版本,分别叫做“Elf32_Ehdr”和“Elf64_Ehdr”。
32位版本与64位版本的ELF文件的文件头内容是一样的,只不过有些成员的大小不一样。
为了对每个成员的大小做出明确的规定以便于在不同的编译环境下都拥有相同的字段长度,“elf.h”使用typedef定义了一套自己的变量体系,如表3-3所示。
我们这里以32位版本的文件头结构“Elf32_Ehdr”作为例子来描述,它的定义如下:让我们拿ELF文件头结构跟前面readelf输出的ELF文件头信息相比照,可以看到输出的信息与ELF文件头中的结构很多都一一对应。
解读ELF文件

}Elf32_Ehdr;
结构的各个成员的含义如注释中所解释的。对ELF文件,有两个视图,一个是从装载运行角度的,另一个是从连接角度的。从装载运行角度,我们关注的是程序头表,由程序头表的指引把ELF文件加载进内存运行它。从连接的角度,我们关注节头表,由节头表的指引把各个节连接组装起来。e_type的值与这两个视图相联系,由它我们可以知道能够从哪个视图去解读。如果e_type=1,表明它是重定位文件,可以从连接视图去解读它;如果e_type=2,表明它是可执行文件,至少可以从装载运行视图去解读它;如果e_type=3,表明它是共享动态库文件,同样可以至少从装载运行视图去解读它;如果e_type=4,表明它是Coredump文件,可以从哪个视图去解读依赖于具体的实现。
从连接视图来解读,其中有一节的内容是一些以零结尾的字符串。e_shstrndx给出该节在节头表中的表项索引。这些字符串是各节的名字。
了解了这些后,我们可以分别从两个视图来解读ELF文件了。先看连接视图,于是我们
Elf32_Ehdre_hdr;
voiSET);
我们看看节头表是什么样的,因为节头表的各个表项给出了如何从连接视图解读ELF文件的路径图。节头表的每个表项是一个如下的结构:
typedefstruct
{
Elf32_Wordsh_name;/*节名索引*/
Elf32_Wordsh_type;/*节类型*/
Elf32_Wordsh_flags;/*加载和读写标志*/
按照这两个视图,整个ELF文件的内容这样来组织:首先是ELF文件头,也就是上面的Elf32_Ehdr结构。或者对64位的ELF文件,是Elf64_Ehdr结构。ELF文件头位于文件开始处,无论e_type的值是什么,它是必须有的。其次是程序头表,对可执行文件(e_type=2)和动态库文件(e_type=3),它是必须有的。对重定位文件(e_type=1),程序头表的有无是可选的。例如用gcc的-c选项生成的.o文件,就没有程序头表。但无论如何,e_phoff和e_phnum、e_phentsize给出了ELF文件的程序头表信息。没有程序头表时它们的值为零。然后就是就是节头表,对可执行文件和动态库文件,它的有无是可选的,对重定位文件,它是必须有的。
ELF分析

4.2.1可执行文件ELF分析1.在Linux下ELF文件主要有三种类型阴:(1)可重定位文件(RelocatableFile)包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据"(2)可执行文件(ExecutableFile)包含适合于执行的一个程序,此文件规定了exec()如何创建一个程序的进程映像"(3)共享目标文件(SharedObjectFile)包含可在两种上下文中链接的代码和数据"首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件"其次,动态链接器(DynamicLinker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像"2.ELF文件的组织结构文件ELF参与程序的连接(建立一个程序)和程序的执行(运行一个程序),编译器和链接器将其视为节头表(seetionheadertable)描述的一些节(seetion)的集合,而加载器则将其视为程序头表(programheadertable)描述的段(segmellt)的集合,通常一个段可以包含多个一节"可亚定位文件都包含一个节头表,可执行文件都包含一个程序头表"共享文件两者都包含有"为此,ELF文件格式同时提供了两种看待文件内容的方式,反映了不同行为的不同要求"下图4.1显示了ELF文件的组织结构"Elf头在程序的开始部位,作为引路表描述整个ELF的文件结构,其信扁!大致分为四部分:一是系统相关信息, 二是目标文件类型, 三是加载相关信息, 四是链接相关信息其中系统相关信息包括elf文件魔数(标识elf文件),平台位数,数据编码方式,elf头部版本,硬件平台emaehine,目标文件版本eversion,处理器特定标志e_ftags:这些信息的引入极大增强了elf文件的可移植性,使交叉编译成为可能"目标文件类型用e一yPe的值表示, 可重定位文件为1, 可执行文件为2, 共享文件为3; 加载相关信息有:程序入口e_entry,程序头表偏移量e夕hoff,elf头部长度e_ehsize,程序头表中一个条目的长度e夕hentsize,程序头表条目数目e一hnum;链接相关信息有:节头表偏移量e_shoff,节头表中一个条目的长度eShentsize,节头表条目个数eShnum,节头表字符索引eShstmdx"可使用readelf一hfilename来察看文件头的内容"程序头表(Programheadertable)告诉系统如何建立一个进程映像,它是从加载执行的角度来看待elf文件"从它的角度来看,elf文件被分成许多段,elf文件中的代码!链接信息和注释都以段的形式存放"每个段都在程序头表中有一个表项描述,包含以下属性:段的类型,段的驻留位置相对于文件开始处的偏移,段在内存中的首字节地址,段的物理地址,段在文件映像中的字节数,段在内存映像中的字节数,段在内存和文件中的对齐标记"可用readelf一1filename察看程序头表中的内容"节头表(sectionheadertable)描述程序节,为编译器和链接器服务"它把elf文件分成了许多节,每个节保存着用于不同目的的数据,这些数据可能被前面的程序头重复使用,完成一次任务所需的信息往往被分散到不同的节里"由于节中数据的用途不同,节被分成不同的类型,每种类型的节都有自己组织数据的方式"每一个节在节头表中都有一个表项描述该节的属性,节的属性包括小节名在字符表中的索引,类型,属性,运行时的虚拟地址,文件偏移,以字节为单位的大小,小节的对齐等信息,可使用:eadelf一5filename来察看节头表的内容"3.ELF文件的加载过程相对可执行文件有三个重要的概念:编译(comPile)!连接(1ink,也可称为链接!联接)!加载(load)"源程序文件被编译成目标文件,多个目标文件被连接成一个最终的可执行文件,可执行文件被加载到内存中运行"因为本文重点是讨沦可执行文件格式,因此加载过程也相对重点讨论"下面是LINUX平台下ELF文件加载过程的一个简单描述[39]"(1)内核首先读ELF文件的头部,然后根据头部的数据指示分别读入各种数据结构,找到标记为可加载(loadable)的段,并调用函数mmapo把段内容加载到内存中"在加载之前,内核把段的标记直接传递给mmapo,段的标记指示该段在内存中是否可读!可写,可执行"显然,文本段是只读可执行,而数据段是可读可写"这种方式是利用了现代操作系统和处理器对内存的保护功能"著名的Shdlcodc的编写技巧则是突破此保护功能的一个实际例子"(2)内核分析出ELF文件标记为PT--INTERP的段中所对应的动态连接器名称,并加载动态连接器"现代L州UX系统的动态连接器通常是/lib/ld一linux.so.2, 相关细节在后面有一详细描述"(3)内核在新进程的堆栈中设置一些标记一值对,以指示动态连接器的相关操作"(4)内核把控制传递给动态连接器"(5)动态连接器检查程序对外部文件(共享库)的依赖性,并在需要时对其进行加载"(6)动态连接器对程序的外部引用进行重定位,通俗的讲,就是告诉程序其引用的外部变量/函数的地址,此地址位于共享库被加载在内存的区间内"动态连接还有一个延迟定位的特性,即只在真正需要引用符号时才重定位,这对提高程序运行效率有极大帮助"(7)动态连接器执行在ELF文件中标记为.init的节的代码,进行程序运行的初始化"在早期系统中,初始化代码对应函数_init(Void)(函数名强制固定),在现代系统中,则对应形式为!oid_attribute((eonstruetor))ini仁funetion(void){,,}其中函数名为任意"(8)动态连接器把控制传递给程序,从ELF文件头部中定义的程序进入点开始执行"在a.out格式和ELF格式中,程序进入点的值是显式存在的,在COFF格式中则是由规范隐含定义"4.2.3传统加壳方法分析从上面的分析可以看出,在ELF文件的加载过程中,首先将文件读入内存,然后根据ELF头部表查找文件的入口地址,找到入口地址然后执行程序,传统的加壳方式是:将程序的入口地址修改成另外一个地址,一般是ELF文件的某些空闲节区,然后执行反跟踪分析处理,如果发现可执行程序被跟踪调试则退出,否则继续执行代码段解密工作,解密完成后转入正常的程序入口地址执行"下图4.3 是传统加壳方法的简单示意图"当系统将加壳后ELF文件读入内存中,加壳后的ELF文件的处理流程如下: (1)读取程序的入口地址,该入口地址是修改后的入口地址(处于B位置),真正的入口地址被放置在D位置位置"(2)程序跳转到B位置执行"(3)执行后面的常用处理例程,例如反跟踪检测和代码混淆等,如果这些检测通过,则解密程序的主体(A部分),接着顺序执行到D位置"(4)根据D位置存放的真正程序入口地址,然后跳转到程序主体部分,运行解密后的真正程序"传统的方法有很大的局限性,首先外壳程序(就是常用处理例程)是存放在ELF文件空闲区域,但并不是每个文件的空闲区域都是相同的,如果外壳程序功能较多,那就意味着需要更多的空闲区域,很有可能会出现空闲区域不足的情况"其次在对ELF文件进行加壳处理过程中,必须对ELF格式非常熟悉且谨慎处理,微小的疏忽就将导致加壳程序或者加壳后的程序无法运行的情况"同时传统加壳方式的扩展性不好,很难具有通用性"4.3一种新的ELF加壳方法针对上面对于加壳原理的分析和常用加壳技术的了解,在这里提出了一种新的加壳方法即:SRELF(ShellingandRe一eonstruetedExeeutableandLinkil:9Forlnat)" SRELF方法的最大特点就是R(重新构造)"该方法是将目标elf文件中的核心部分提取出来,再和准备好的解密或解压缩程序,反静态分析和反动态跟踪等程序结合在一起,按照e!f文件标难格式一重新构造一个全新的clf可执行文件"它将加壳的各项功能程序和目标程序很好的融合为一体"4.3.1SRELF方法的设计在这里SRELF加壳程序将采用多层次加密!强密码保护机制和内存中可执行文件解密的方式来确保ELF文件的安全性"SRELF加壳方法需要实现下而的功能: (l)防止系统调用跟踪(SystemCallTraeing)"(2)防止库调用跟踪(LibraryCallTraeing)"(3)防1卜程序执行路径跟踪(ExeeutionpathTraeing)"(4)防止应用级调试跟踪(ApplieationLevelDebugging)"(5)防止内存镜像(Memo仃Dumping)"(6)关键数据加密(Ene仃ptimportantcode)"从上面可以看出SRELF程序需要解决6类问题,上述问题的解决思路如下:(l)针对功能1到4"在Linux下实现跟踪的原理都是基于基于Ptrace()调试的API,所以只要使得这个API函数对于加密的二进制文件无效,这样就将会有效阻止对于程序的跟踪分析,采用这些工具将很难攻击加壳的ELF程序"(2)针对内存镜像是基于这样的设想,对程序进行内存镜像的前提条件是:在内存中运行的程序必须是明文方式,所以可采用加壳程序使程序运行时部分解密来实现"(3)如何解决关键数据加密"系统采用加密算法对于ELF的核心部分进行加毖1.加壳程序的文件结构设计采用SRELF加密后的文件结构如下所示:如上图所示,S旺LF的结构其实是:=SRELF头8=加密后的程序>"其中S贬LF头由ELF头!程序头和SRELF常用处理例程这三部分组成"在编译SRELF命令时,生成了一个很大的数组s灿ea走binll,其实就是一个通用的ELF头十程序头十SRELF常用处理例程,当加密程序的时候,只需要对数组里的某些字段稍作修改即可"其中SR卫LF的常用处理例程采用分层结构,分别由代码混淆层!反跟踪分析层和块加密层组成"2.SRELF的加解密过程(l)加密过程sRELF首先以加密的文件作为对象,将S灿ead_bin[1作为加密后文件的ELF头十程序头十SRELF常用例程,然后按照SRELF的命令选项,对文件进行逐层的处理,分别是代码混淆!反跟踪分析和块加密处理"对于每层的处理,都会在SRELF头中保存相应的标志和信息(例如加密时的随机序列,shal哈希值),然后将结果放到最后一部分中"在这里采用主机的某些特征(fillgerprillt)和密码作为密钥KEY,首先算出密钥的SHAI哈希值,然后从随机发生序列器中获得160位的随机数,并与哈希值异或得到KEYZ,然后用KEYZ对-需要程序进行RC4加密"将加密之前的明文SHAI 哈希值保存到Srhead头的相应部分,这样做密码验证时就能够判断密码的正确性"(2)解密过程首先去掉第一层保护,接下来处理运行程序的栈结构,并将系统控制权交给SRELF的处理函数"处理函数会对反跟踪分析进行处理,如果通过的话接下来是fingcrprint的检测,如果通过检验就将加密的程序解密,然后调用s贬LF中自带的Loader,将解密后的程序映射一到内存中,如果解密的程序需要inte甲rcter,loader 也会负责将interp咒ter装入到内存中,最后将控制权交给illterpreter或者解密后的FIF"3.SRELF的加载器(Loader)由于解密以后的程序与SRELF的例程函数是位于同一进程空间的,因此如果想要执行解密后的程序,例程就需要自己装载程序,在一般情况下程序是由内核来完成装载任务的"SRELF是通过bejemap来实现的,如果加密之前的程序是动态链接的,还需调用be皿apinte印reter来装载解释器(inte印reter)#4.SRELF的实现流程图下图4.5是SRELF的实现流程图"图4.5SRELF方法实现程序框图4.3.2SRELF方法性能分析1.安全性首先SRELF的方法理论上可以使用任意一种加密或压缩算法来对程序.text段的二进制代码进行加密或压缩"从加解密角度来说,SRELF的安全性比其他方法要高"其次SRELF方法没有对加壳功能程序的大小进行限制,所以在二进制代码中可以插入足够的花指令,并加上高复杂度的加密或压缩算法,想要进行静态的分析的难度会极大"再次SRELF方法是通过重构elf文件,完全改变了elf文件中的内容,在Start位置上的内容将不再是原来的内容,这对想要通过静态分析代码来破解软件的破解者来说又增加了难度"SRELF对反动态跟踪也给予了充分的支持"和反静态跟踪一样,SRELF在其中加入了充分的反动态跟踪指令,想当与对elf文件又增加了一层保护措施"2.透明性当加壳的elf文件运行时,由于解码的过程是在内存中完成的,所以对于用户来说是感觉不到程序被加了保护壳,这个过程对用户来说是透明的"3.伪装性一般加壳后的可执行文件在结构和大小上是有一定程度变化的"经过SRELF方法加壳后的elf文件仍然保持了elf文件默认的文件结构,所以大多数人都是无法看出加壳前后"1f文件的区别"而且SRELF不需要改变程序入口地址,这样即便是使用特定工具也分辨不出来"就像elf文件没有经过特殊处理,这样给加壳后的可执行文件增加了伪装"4.扩展性由于SRELF理论上没有对壳程序大小进行限制,这使得它具备了良好的扩展性"因此随着技术的史新,可以同步更新SRELF中的加密方式!反静态分析和反动态跟踪等方法,这样SRELF将具有良好的发展性,不会迅速被淘汰"5.3!5软件保护模块第四章提出了SRELF加壳方法,该方法重点在于/提取目标文件中的核心部分0和/将目标文件的核心部分和加壳部分整合0这两个部分功能的实现"所以在此,主要阐述这两部分的实现方法"1.提取}8标文件中的核心部分核心部分包括可执行指令,动态链接表等,段或节的信息"提取这些部分也就是将这些部分单独复制出来,用以后面代码整合中使用"这里以.text节为例,说明如何提取核心部分"该节保存着程序的币文或者说是可执行指令,可以说它是这部分的核心"根据elf文件头中的信息,首先定位到程序头表"在程序头表中再定位到代码段,整个代码段都是需要的核心部分"这时再定位到.text位置,将其中的数据代码复制出来"其他段或节都以同样的方法获得"这里只给出定位.text 的程序代码"V oidGetElfCore(FILE*fP)/*获取程序头表和节头表的起始地址*/Phdr=(EI仍2_Phdr*)((unsignedlong)ehdr+ehdr一>e夕ho均:shdr=(EI招2_Shdr*)((unsignedlong)ehdr+ehdr一>屯sho均;/*定位文本段*/while(l){if(判断是否是文本段){不是文本段,则将指针指向下一个段}Break;刀若是文本段,跳出循环2.将目标文件的核心部分和加壳部分整合这部分功能是整个SRELF的核心"它不是简单的将各个程序段或节一一对应相加"它需要进行精确的计算,来正确的设置elf文件头,程序头表,节头表等的数据"这样新生成的elf文件才能正常运行"这里还是以.text节为例"首先要分别计算出加壳部分.text节占物理文件的大小和载入内存所占内存的大小,目标文件核心部分.text节占物理文件的大小和载入内存所占内存的大小"然后将目标文件核心部分.text节内容续写到加壳部分.text节的后面"再将程序头表,节头表中.text 属性中与大小相关的参数对应设置成新计算出的大小"到此为止,.text部分的整合完成"其他段或节都以同样的方法整合"这里只给出.text的整合伪代码" VOidIntegrate(char*sre,ehar*des){刀通过GetElfCore函数将目标文件的.text段的地址赋值给Src//将准备好的壳程序的.text段的地址赋值给desxntegrate_segment(ehar*sre.ehar*des);//将目标程序的.text段续写在壳程序的.text段之后Set_EI仁Attribute(ehar*sre,ehar*des);//重新设置elf头和程序段表和节表中的参数.............}。
elf 结构解析

elf 结构解析一、ELF文件的基本结构1. ELF文件头(ELF Header):ELF文件的入口点,包含了文件的基本信息和描述,如魔数、文件类型、入口地址等。
2. 程序头表(Program Header Table):描述了ELF文件的各个段(Segment)的加载和运行时的属性,如可执行代码段、数据段、只读段等。
3. 节头表(Section Header Table):描述了ELF文件的各个节(Section)的属性和位置信息,如代码节、数据节、字符串节等。
4. 节区(Section):包含了ELF文件的各种数据和代码,如程序的实际指令、全局变量、局部变量等。
5. 字符串表(String Table):存储了ELF文件中使用的所有字符串,如符号表中的符号名、节名等。
6. 符号表(Symbol Table):记录了ELF文件中定义和引用的各个符号的信息,如函数、变量等。
7. 重定位表(Relocation Table):用于修正程序中的跳转地址,使得程序可以正确地链接和执行。
二、ELF文件加载和链接过程1. 加载:当操作系统执行一个ELF可执行文件时,将会执行加载过程。
操作系统会按照程序头表中的描述,将各个段加载到内存中的相应位置,建立虚拟地址和物理地址之间的映射关系。
2. 符号解析:在加载过程中,操作系统会解析符号表,建立符号名与符号地址之间的映射关系。
这样,在程序执行过程中,可以通过符号名来访问对应的函数或变量。
3. 重定位:由于不同的目标文件可能会引用其他目标文件中的符号,所以在加载过程中,操作系统会根据重定位表的信息,修正程序中的跳转地址,确保程序可以正确地链接和执行。
三、ELF文件的应用1. 可执行文件:ELF格式的可执行文件可以直接在操作系统中运行,例如Linux系统中的可执行文件就是以ELF格式存储的。
2. 共享库:ELF格式的共享库包含了一组可被多个可执行文件共享的代码和数据,可以在程序运行时动态加载和链接。
elf格式文件解析

ELF可执行文件的解析与加载ELF文件格式简介1.1前言ELF-可执行链接格式最初是由UNIX系统实验室(USL)作为应用程序二进制接口(ABI)开发和发行。
工具接口标准委员会TIS已经将ELF作为运行在Intel32位架构之上的各类型操作系统的可导出对象文件格式标准。
ELF标准为开发者提供了一组横跨多运行环境的二进制接口定义来组织软件开发。
1.2对象文件1.2.1 介绍本部分描述了ABI对象文件格式,也称之为ELF。
有三种主要类型的对象文件:1. 可重组(relocatable)文件包含了适合用来链接其他对象文件的代码和数据,从而创建出可执行或可共享的对象文件;2. 可执行(executable)文件包含了用于执行的程序,该文件规定了exec如何创建一个程序的进程映像;3. 可共享对象(shared object)文件包含了用来在两个上下文之间链接的代码和数据。
首先,链接器ld将该文件和其他的可重组文件或可共享对象文件进行处理后,创建出新对象文件,其次,动态链接器将该新对象文件与可执行文件或共享对象组合,来共同创建一个进程映像;经过汇编器以及链接器创建成的对象文件,其是在处理器上可直接执行的程序的二进制代表。
本部分主要描述文件格式以及其如何用来构建程序。
后一部分也描述了对象文件,集中在程序执行所必须的信息上。
1.2.1.1 文件格式在程序链接和程序执行过程都涉及到对象文件。
出于方便和效率,对象文件格式图从链接和运行两个视角来展示文件的内容。
ELF header位于文件的开始处,其用来描述文件的组织结构。
Section包含了大量的对象文件信息,从链接的视角来看就是指令、数据、符号表、重组信息等等。
Segment和Program是从程序执行视角来观看的,这将在下部分讲解。
如果存在Program Header table的话,其将告诉操作系统如何创建进程映像。
用来创建进程映像(执行程序)的文件必须包含program header table。
解析ELF可执行文件-C语言

解析ELF可执⾏⽂件-C语⾔解析代码/* gcc elfparse.c -o elsparse */#include <stdio.h>#include <string.h>#include <errno.h>#include <elf.h>#include <unistd.h>#include <stdlib.h>#include <sys/mman.h>#include <stdint.h>#include <sys/stat.h>#include <fcntl.h>int main(int argc, char **argv) {int fd, i;uint8_t *mem;struct stat st;char *StringTable, *interp;Elf64_Ehdr *ehdr;Elf64_Shdr *shdr;Elf64_Phdr *phdr;if (argc < 2) {printf("Usage: %s <executable>\n", argv[0]);exit(0);}if ((fd = open(argv[1], O_RDONLY)) < 0) {perror("open");exit(-1);}if (fstat(fd, &st) < 0) {perror("fstat");exit(-1);}/*** 使⽤mmap函数将可执⾏⽂件映射到内存中** NULL: 由系统决定映射区的起始地址** st.st_size: 映射区的⼤⼩** PROT_READ: 映射区的保护标志** MAP_PRIVATE: 建⽴⼀个写时拷贝的私有映射** fd: ⽂件描述符** 0: 将从此偏移处的⽂件位置开始读取*/mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);if (mem == MAP_FAILED) {perror("mmap");exit(-1);}/* 若上述步骤成功执⾏,则此时mem就是可执⾏⽂件映射的起始位置 *//* 获取⽂件头 */ehdr = (Elf64_Ehdr *) mem;/* 获取段表 */phdr = (Elf64_Phdr *) (&mem[ehdr->e_phoff]);/* 获取节表 */shdr = (Elf64_Shdr *) (&mem[ehdr->e_shoff]);/* 检查该⽂件是否为ELF映像 */if (mem[0] != 0x7f || strncmp(&mem[1], "ELF", 3)) {fprintf(stderr, "%s is not an ELF file\n", argv[1]);exit(-1);}/* 确保为可执⾏⽂件 */if (ehdr->e_type != ET_EXEC) {fprintf(stderr, "%s is not an executable\n", argv[1]);exit(-1);}printf("ELF header:\n\n");printf("%-36s 0x%x\n", " Program Entry point:", ehdr->e_entry);printf("%-36s %d (bytes)\n", " Size of program header:", ehdr->e_phentsize);printf("%-36s %d\n", " Number of Program headers:", ehdr->e_phnum);printf("%-36s %d (bytes)\n", " Size of section header:", ehdr->e_shentsize);printf("%-36s %d\n", " Number of Section headers:", ehdr->e_shnum);printf("%-36s %d\n\n", " Section header string table index:", ehdr->e_shstrndx);/* 存储节名的字符串表 */StringTable = &mem[shdr[ehdr->e_shstrndx].sh_offset];printf("Section header list: \n\n");for (i = 1; i < ehdr->e_shnum; i++)printf("%-26s 0x%x\n", &StringTable[shdr[i].sh_name], shdr[i].sh_addr);printf("\n Program header list: \n\n");for (i = 0; i < ehdr->e_phnum; i++) {switch (phdr[i].p_type) {case PT_LOAD:/* ⼀般PT_LOAD类型的段有代码段和数据段,可执⾏的为代码段 */if (phdr[i].p_flags & PF_X)printf("Text segment: 0x%x\n", phdr[i].p_vaddr);elseprintf("Data segment: 0x%x\n", phdr[i].p_vaddr);break;case PT_INTERP:/* 程序解释器的位置,例如 ld-linux.so.2 */interp = strdup((char *)&mem[phdr[i].p_offset]);printf("Interpreter: %s\n", interp);break;case PT_NOTE:printf("Note segment: 0x%x\n", phdr[i].p_vaddr);break;case PT_DYNAMIC:printf("Dynamic segment: 0x%x\n", phdr[i].p_vaddr);break;case PT_PHDR:printf("Phdr segment: 0x%x\n", phdr[i].p_vaddr);break;}}free(interp);exit(0);}测试⽤例// gcc -no-pie example.c -o example#include <stdio.h>void foo() {printf("this is function foo\n");}void your_name(char *filename) {printf("your filename is %s\n", filename);}int main(int argc, char **argv) {foo();your_name(argv[0]);}注意使⽤如下命令来⽣成ELF可执⾏⽂件,若没有加-no-pie参数⽣成的是共享⽬标⽂件gcc -no-pie -example.c -o example结果./elfparse example参考资料《Linux⼆进制分析》 - Ryan O’Neill。
ELF文件格式分析

ELF文件格式分析ELF(Executable and Linkable Format)是一种常见的可执行文件格式,用于在UNIX和类UNIX系统上存储和执行程序。
在本文中,我们将详细分析ELF文件格式的结构和各个部分的作用。
首先是标识部分,它占据ELF文件的最开始位置,有以下几个字段:1. Magic Number:ELF文件的前四个字节,用于识别文件类型。
对于32位系统,它的值是0x7F、'E'、'L'和'F';对于64位系统,它的值是0x7F、'E'、'L'和'F'以及0x022.类型:指定了ELF文件的类型,比如可执行文件、目标文件、共享库等等。
3.类型机器:指定了目标机器的体系结构,如x86、ARM等等。
4.版本:指定了ELF文件的版本号。
5.入口点:对于可执行文件,指定程序的入口点。
6.程序头表和节头表的偏移地址和大小。
接下来是文件头部分,它描述了ELF文件的整体结构和布局:1.类型:表示ELF文件的类型,如可重定位文件、可执行文件、共享目标文件等。
2.目标机器:标识目标机器的类型。
3.版本:指定了ELF文件的版本号。
4.入口点:指定程序的入口点。
5.程序头表偏移地址和节头表偏移地址。
6.程序头表项的大小、数量和节头表项的大小、数量。
7.字符串表索引,用于找到文件中的字符串。
最后是节头表部分,它包含了所有节(Section)的相关信息,比如代码段、数据段等等。
每个节头表项包含以下字段:1.节的名称:用于标识节的名称。
2.节的类型:描述节的属性,如代码段、数据段、符号表等。
3.节的标志:描述节的属性,如可写、可执行等。
4.节的虚拟地址:指定节在内存中的虚拟地址。
5.节的文件偏移地址:指定节在文件中的偏移地址。
6.节的大小:指定节的大小。
7.链接和信息:用于指定其他与节相关的信息。
通过分析ELF文件的结构,我们可以获得有关程序入口点、机器类型、节的信息等重要的元数据。
计算机操作系统实验_解析ELF文件

西北工业大学操作系统实验实验报告一、实验目的熟悉可执行链接文件(ELF)的结构,了解GeekOS将ELF格式的可执行程序加载到内存,建立内核线程并运行的实现技术。
二、实验要求1.修改Project1项目中的/GeekOS/elf.c文件:在函数Parse_ELF_Executable()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头),获取可执行文件长度、代码段、数据段等信息,并打印输出。
并且,填充Exe_Format 数据结构中的值域。
2.掌握GeekOS在核心态运行可执行程序的原理,绘制出可执行程序在内核中加载、运行的流程图(需反映关键函数的调用关系)。
3.回答实验讲义P125页的思考题。
三、实验过程及结果1、修改Project1项目中的/GeekOS/elf.c文件:在函数Parse_ELF_Executable()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头),获取可执行文件长度、代码段、数据段等信息,并打印输出。
并且,填充Exe_Format 数据结构中的值域。
答:修改Project1项目中的/GeekOS/elf.c文件:在函数Parse_ELF_Executable()中添加代码,如下:==============elf.c===================int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength, struct Exe_Format *exeFormat){int i;elfHeader *head=(elfHeader*)exeFileData;programHeader *proHeader=(programHeader *)(exeFileData+head->phoff);KASSERT(exeFileData!=NULL);KASSERT(exeFileLength>head->ehsize+head->phentsize*head->phnum);KASSERT(head->entry%4==0);exeFormat->numSegments=head->phnum;exeFormat->entryAddr=head->entry;for(i=0;i<head->phnum;i++){exeFormat->segmentList[i].offsetInFile=proHeader->offset;exeFormat->segmentList[i].lengthInFile=proHeader->fileSize;exeFormat->segmentList[i].startAddress=proHeader->vaddr;exeFormat->segmentList[i].sizeInMemory=proHeader->memSize;exeFormat->segmentList[i].protFlags=proHeader->flags;proHeader++;}return 0;}==============elf.c===================运行结果如图:2、掌握GeekOS在核心态运行可执行程序的原理,绘制出可执行程序在内核中加载、运行的流程图(需反映关键函数的调用关系)。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
西北工业大学操作系统实验实验报告
一、实验目的
熟悉可执行链接文件(ELF)的结构,了解GeekOS将ELF格式的可执行程序加载到内存,建立内核线程并运行的实现技术。
二、实验要求
1.修改Project1项目中的/GeekOS/elf.c文件:在函数Parse_ELF_Executable()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头),获取可执行文件长度、代码段、数据段等信息,并打印输出。
并且,填充Exe_Format 数据结构中的值域。
2.掌握GeekOS在核心态运行可执行程序的原理,绘制出可执行程序在内核中加载、运行的流程图(需反映关键函数的调用关系)。
3.回答实验讲义P125页的思考题。
三、实验过程及结果
1、修改Project1项目中的/GeekOS/elf.c文件:在函数Parse_ELF_Executable()中添加代码,分析ELF格式的可执行文件(包括分析得出ELF文件头、程序头),获取可执行文件长度、代码段、数据段等信息,并打印输出。
并且,填充Exe_Format 数据结构中的值域。
答:修改Project1项目中的/GeekOS/elf.c文件:在函数Parse_ELF_Executable()中添加代码,如下:
==============elf.c===================
int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength, struct Exe_Format *exeFormat)
{
int i;
elfHeader *head=(elfHeader*)exeFileData;
programHeader *proHeader=(programHeader *)(exeFileData+head->phoff);
KASSERT(exeFileData!=NULL);
KASSERT(exeFileLength>head->ehsize+head->phentsize*head->phnum);
KASSERT(head->entry%4==0);
exeFormat->numSegments=head->phnum;
exeFormat->entryAddr=head->entry;
for(i=0;i<head->phnum;i++)
{
exeFormat->segmentList[i].offsetInFile=proHeader->offset;
exeFormat->segmentList[i].lengthInFile=proHeader->fileSize;
exeFormat->segmentList[i].startAddress=proHeader->vaddr;
exeFormat->segmentList[i].sizeInMemory=proHeader->memSize;
exeFormat->segmentList[i].protFlags=proHeader->flags;
proHeader++;
}
return 0;
}
==============elf.c===================
运行结果如图:
2、掌握GeekOS在核心态运行可执行程序的原理,绘制出可执行程序在内核中加载、运行的流程图(需反映关键函数的调用关系)。
答:ELF(Executable and linking format)文件是UNIX系统实验室作为应用程序二进制接口而开发的可执行文件,是x86 Linux系统下的一种常用目标文件(object file)格式。
ELF文件格式如下表:
ELF文件在磁盘中的映象和在内存中的执行程序镜像的对应关系如下图:
表1 ELF目标文件格式
ELF 文件在磁盘中的映象和在内存中的执行程序镜像的对应关系如下图:
Code offset
Data offset
Data size
Code size
ELF 文件镜像
图1 ELF 文件和内存中的可执行文件镜像
流程图如下:
四、实验分析
思考题:输出个字符串为什么要如此大费周章?在 a.exe中直接调用Print 函数是否可行?
答:不可行,直接打印Print(s1)则找不到字符串s1的正确位置,字符串变量名即代表地址信息,程序被加载进入内存时有了基址,需要通过基址加偏移量来找到字符串在内存中的正确位置,所以要调用ELF_Print函数。
五、所遇问题及解决方法
答:实验中遇到的问题首先就是代码难以阅读理解,对汇编陌生是一个主要的问题,其次就是代码之间的关联较多,层次较多,代码量也比较大,导致在阅读源码的过程中造成了很大的困难。
最后在老师和同学们的帮助下,勉强理解了代码的基本结构以及实现的功能。
通过此次实验,我由原来对操作系统只有一个粗浅的概念的认识的水平上升到对操作系统有比较深刻印象和理解的程度。
不仅概念得到了进一步的理解,更对geekos在核心态下运行用户程序的原理有了更进一步的理解。