精华内容
下载资源
问答
  • file = open(elf_path, 'rb') # 创建 ELFFile 对象 , 该对象是核心对象 elf_file = ELFFile(file) # 打印 elf 文件头 print(elf_file.header) # 打印 程序头入口 个数 print(elf_file.num_segments()) # 打印 节区头...





    一、PyCharm 中创建 Python 程序



    在 PyCharm 的欢迎界面中 , 点击 " New Project " 按钮 , 开始创建 Python 工程 ;

    在这里插入图片描述

    选择 Python 工程安装路径 , 以及依赖的 Python SDK 版本 2.7 即可 ;

    在这里插入图片描述





    二、导入 ELFFile 依赖库



    参考 【错误记录】Python 安装依赖库报错 ( ERROR: Could not find a version that satisfies the requirement elftools ) 博客中导入依赖库过程 ;

    在这里插入图片描述





    三、 解析 ELF 文件



    工程结构 : 将要解析的 libwtcrypto.so 动态库拷贝到工程根目录 , 执行 main.py 即可完成解析 ;

    在这里插入图片描述

    完整代码示例 :

    # coding=utf-8
    # 解析 elf 文件需要导入的依赖库
    #   安装 pyelftools 库
    from elftools.elf.elffile import ELFFile
    
    
    def main():
        # 要解析的动态库路径
        elf_path = r'libwtcrypto.so'
        # 打开 elf 文件
        file = open(elf_path, 'rb')
        # 创建 ELFFile 对象 , 该对象是核心对象
        elf_file = ELFFile(file)
    
        # 打印 elf 文件头
        print(elf_file.header)
        # 打印 程序头入口 个数
        print(elf_file.num_segments())
        # 打印 节区头入口 个数
        print(elf_file.num_sections())
    
        # 遍历打印 程序头入口
        for segment in elf_file.iter_segments():
            print(segment.header)
            print(segment.header['p_align'])
    
        # 遍历打印 节区头入口
        for section in elf_file.iter_sections():
            print('name:', section.name)
            print('header', section.header)
    
        # 关闭文件
        file.close()
        pass
    
    
    if __name__ == '__main__':
        main()
    
    

    执行结果 :

    Y:\002_WorkSpace\PycharmProjects\ELF_Parser\venv\Scripts\python.exe Y:/002_WorkSpace/PycharmProjects/ELF_Parser/main.py
    Container({'e_flags': 0, 'e_shoff': 16652, 'e_phoff': 52, 'e_shnum': 21, 'e_entry': 0, 'e_version': 'EV_CURRENT', 'e_machine': 'EM_386', 'e_phnum': 7, 'e_shentsize': 40, 'e_ident': Container({'EI_DATA': 'ELFDATA2LSB', 'EI_OSABI': 'ELFOSABI_SYSV', 'EI_VERSION': 'EV_CURRENT', 'EI_CLASS': 'ELFCLASS32', 'EI_ABIVERSION': 0, 'EI_MAG': [127, 69, 76, 70]}), 'e_type': 'ET_DYN', 'e_phentsize': 32, 'e_shstrndx': 20, 'e_ehsize': 52})
    7
    21
    Container({'p_memsz': 224, 'p_flags': 4, 'p_offset': 52, 'p_type': 'PT_PHDR', 'p_align': 4, 'p_paddr': 52, 'p_filesz': 224, 'p_vaddr': 52})
    4
    Container({'p_memsz': 11872, 'p_flags': 5, 'p_offset': 0, 'p_type': 'PT_LOAD', 'p_align': 4096, 'p_paddr': 0, 'p_filesz': 11872, 'p_vaddr': 0})
    4096
    Container({'p_memsz': 4568, 'p_flags': 6, 'p_offset': 15944, 'p_type': 'PT_LOAD', 'p_align': 4096, 'p_paddr': 20040, 'p_filesz': 444, 'p_vaddr': 20040})
    4096
    Container({'p_memsz': 272, 'p_flags': 6, 'p_offset': 15956, 'p_type': 'PT_DYNAMIC', 'p_align': 4, 'p_paddr': 20052, 'p_filesz': 272, 'p_vaddr': 20052})
    4
    Container({'p_memsz': 156, 'p_flags': 4, 'p_offset': 11716, 'p_type': 'PT_GNU_EH_FRAME', 'p_align': 4, 'p_paddr': 11716, 'p_filesz': 156, 'p_vaddr': 11716})
    4
    Container({'p_memsz': 0, 'p_flags': 6, 'p_offset': 0, 'p_type': 'PT_GNU_STACK', 'p_align': 0, 'p_paddr': 0, 'p_filesz': 0, 'p_vaddr': 0})
    0
    Container({'p_memsz': 440, 'p_flags': 6, 'p_offset': 15944, 'p_type': 'PT_GNU_RELRO', 'p_align': 4, 'p_paddr': 20040, 'p_filesz': 440, 'p_vaddr': 20040})
    4
    ('name:', '')
    ('header', Container({'sh_type': 'SHT_NULL', 'sh_addralign': 0, 'sh_offset': 0, 'sh_entsize': 0, 'sh_name': 0, 'sh_flags': 0, 'sh_size': 0, 'sh_addr': 0, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.dynsym')
    ('header', Container({'sh_type': 'SHT_DYNSYM', 'sh_addralign': 4, 'sh_offset': 276, 'sh_entsize': 16, 'sh_name': 11, 'sh_flags': 2, 'sh_size': 832, 'sh_addr': 276, 'sh_link': 2, 'sh_info': 1}))
    ('name:', u'.dynstr')
    ('header', Container({'sh_type': 'SHT_STRTAB', 'sh_addralign': 1, 'sh_offset': 1108, 'sh_entsize': 0, 'sh_name': 19, 'sh_flags': 2, 'sh_size': 892, 'sh_addr': 1108, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.hash')
    ('header', Container({'sh_type': 'SHT_HASH', 'sh_addralign': 4, 'sh_offset': 2000, 'sh_entsize': 4, 'sh_name': 27, 'sh_flags': 2, 'sh_size': 364, 'sh_addr': 2000, 'sh_link': 1, 'sh_info': 0}))
    ('name:', u'.rel.dyn')
    ('header', Container({'sh_type': 'SHT_REL', 'sh_addralign': 4, 'sh_offset': 2364, 'sh_entsize': 8, 'sh_name': 33, 'sh_flags': 2, 'sh_size': 24, 'sh_addr': 2364, 'sh_link': 1, 'sh_info': 0}))
    ('name:', u'.rel.plt')
    ('header', Container({'sh_type': 'SHT_REL', 'sh_addralign': 4, 'sh_offset': 2388, 'sh_entsize': 8, 'sh_name': 42, 'sh_flags': 2, 'sh_size': 280, 'sh_addr': 2388, 'sh_link': 1, 'sh_info': 6}))
    ('name:', u'.plt')
    ('header', Container({'sh_type': 'SHT_PROGBITS', 'sh_addralign': 16, 'sh_offset': 2672, 'sh_entsize': 4, 'sh_name': 46, 'sh_flags': 6, 'sh_size': 576, 'sh_addr': 2672, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.text')
    ('header', Container({'sh_type': 'SHT_PROGBITS', 'sh_addralign': 16, 'sh_offset': 3248, 'sh_entsize': 0, 'sh_name': 51, 'sh_flags': 6, 'sh_size': 6327, 'sh_addr': 3248, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.rodata')
    ('header', Container({'sh_type': 'SHT_PROGBITS', 'sh_addralign': 4, 'sh_offset': 9576, 'sh_entsize': 1, 'sh_name': 57, 'sh_flags': 50, 'sh_size': 1177, 'sh_addr': 9576, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.eh_frame')
    ('header', Container({'sh_type': 'SHT_PROGBITS', 'sh_addralign': 4, 'sh_offset': 10756, 'sh_entsize': 0, 'sh_name': 65, 'sh_flags': 2, 'sh_size': 960, 'sh_addr': 10756, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.eh_frame_hdr')
    ('header', Container({'sh_type': 'SHT_PROGBITS', 'sh_addralign': 4, 'sh_offset': 11716, 'sh_entsize': 0, 'sh_name': 75, 'sh_flags': 2, 'sh_size': 156, 'sh_addr': 11716, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.fini_array')
    ('header', Container({'sh_type': 'SHT_FINI_ARRAY', 'sh_addralign': 4, 'sh_offset': 15944, 'sh_entsize': 0, 'sh_name': 89, 'sh_flags': 3, 'sh_size': 8, 'sh_addr': 20040, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.init_array')
    ('header', Container({'sh_type': 'SHT_INIT_ARRAY', 'sh_addralign': 1, 'sh_offset': 15952, 'sh_entsize': 0, 'sh_name': 101, 'sh_flags': 3, 'sh_size': 4, 'sh_addr': 20048, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.dynamic')
    ('header', Container({'sh_type': 'SHT_DYNAMIC', 'sh_addralign': 4, 'sh_offset': 15956, 'sh_entsize': 8, 'sh_name': 113, 'sh_flags': 3, 'sh_size': 272, 'sh_addr': 20052, 'sh_link': 2, 'sh_info': 0}))
    ('name:', u'.got')
    ('header', Container({'sh_type': 'SHT_PROGBITS', 'sh_addralign': 4, 'sh_offset': 16228, 'sh_entsize': 0, 'sh_name': 122, 'sh_flags': 3, 'sh_size': 4, 'sh_addr': 20324, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.got.plt')
    ('header', Container({'sh_type': 'SHT_PROGBITS', 'sh_addralign': 4, 'sh_offset': 16232, 'sh_entsize': 0, 'sh_name': 127, 'sh_flags': 3, 'sh_size': 152, 'sh_addr': 20328, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.data')
    ('header', Container({'sh_type': 'SHT_PROGBITS', 'sh_addralign': 4, 'sh_offset': 16384, 'sh_entsize': 0, 'sh_name': 136, 'sh_flags': 3, 'sh_size': 4, 'sh_addr': 20480, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.bss')
    ('header', Container({'sh_type': 'SHT_NOBITS', 'sh_addralign': 32, 'sh_offset': 16388, 'sh_entsize': 0, 'sh_name': 142, 'sh_flags': 3, 'sh_size': 4096, 'sh_addr': 20512, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.comment')
    ('header', Container({'sh_type': 'SHT_PROGBITS', 'sh_addralign': 1, 'sh_offset': 16388, 'sh_entsize': 1, 'sh_name': 147, 'sh_flags': 48, 'sh_size': 53, 'sh_addr': 0, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.note.gnu.gold-version')
    ('header', Container({'sh_type': 'SHT_NOTE', 'sh_addralign': 4, 'sh_offset': 16444, 'sh_entsize': 0, 'sh_name': 156, 'sh_flags': 0, 'sh_size': 28, 'sh_addr': 0, 'sh_link': 0, 'sh_info': 0}))
    ('name:', u'.shstrtab')
    ('header', Container({'sh_type': 'SHT_STRTAB', 'sh_addralign': 1, 'sh_offset': 16472, 'sh_entsize': 0, 'sh_name': 1, 'sh_flags': 0, 'sh_size': 179, 'sh_addr': 0, 'sh_link': 0, 'sh_info': 0}))
    
    Process finished with exit code 0
    
    

    将执行结果与 【Android 逆向】ELF 文件格式 ( 程序头数据 | 节区头数据 | 动态符号表 ) 博客中使用 010 Editor 工具解析的数据进行参照对比 ;





    四、 博客源码



    GitHub : https://github.com/han1202012/ELF_Parser

    CSDN 下载 :

    展开全文
  • ELF

    2021-05-21 11:25:59
    Windows中是PE(portable executable),Linux中是ELF(executable linkable format),这些都是COFF格式的变种,所以PE也称为PE-COFF 还有动态链接库、静态链接库也是采用可执行文件的格式进行存储的,准确的说,...

    目录

    一,目标文件格式

    二,ELF

    1,分段

    2,工具

    3,查看目标文件内容


     

    一,目标文件格式

    编译链接 https://blog.csdn.net/nameofcsdn/article/details/116654835

    目标文件和可执行文件的逻辑结构是类似的,所以一般采用同样的存储结构。

    Windows中是PE(portable executable),Linux中是ELF(executable linkable format),这些都是COFF格式的变种,所以PE也称为PE-COFF

    还有动态链接库、静态链接库也是采用可执行文件的格式进行存储的,准确的说,静态链接库是一些目标文件的打包。

    unix的可执行文件是a.out格式,设计很简单,后来共享库出现之后,a.out搞不定,于是COFF出现了,COFF的主要贡献是段的概念。

    ELF中,文件可分为四类:可重定位文件.o,可执行文件,共享目标文件(链接库),核心转储文件(core dump)

     

    二,ELF

    1,分段

    目标文件按照段(segment)来存储,也叫节(section),2个概念差别不大。

    目标文件中最重要的几个部分:

    • 文件头:文件属性(可执行,静态链接,动态链接等)、入口地址(如果是可执行文件)、目标硬件、目标操作系统、段表(各个段的偏移地址)等
    • 代码段:机器指令,.code or .text
    • 数据段:已初始化的全局变量和局部静态变量,.data
    • bss段:未初始化的全局变量和局部静态变量,.bss(block started by symbol)
    • 各种表:其中最重要的就是符号表

    bss段只在段表中记录大小,在符号表中记录符号,并没有实际存储,所以不占文件空间,

    目标文件被加载的时候,给bss段根据记录的大小分配内存

    PS:全局变量无论是静态的还是非静态的,放在哪个段只取决于是否初始化为非0数据,局部变量如果是静态的,也是根据是否初始化为非0数据来区分,

    那么,非静态的局部变量,为什么目标文件中没有它呢?(可以查看符号表,确实没有)

    因为,在一个函数结束编译的时候,非静态的局部变量都会从符号表中删掉,只留下栈指针等相关内容

    (如,先把栈指针移到某个位置,然后写入一个外部输入的变量,然后把它的平方值送到别的地方,编译之后只剩下这些指令,局部变量本身就没了)

    2,工具

    windows中的binutils、linux中的binutils、readelf都可以用来查看elf的内容,

    binutils中包含objdump工具,安装MinGW也可以使用C:\mingw64\bin目录中的objdump.exe、readelf.exe等工具

    3,查看目标文件内容

    代码一:

    // tmp.cpp
    
    #include<iostream>
    using namespace std;
    
    int main()
    {
        static int abcdefg[100]={1};
        cout<<abcdefg[0];
        return 0;
    }

    gcc -c .\tmp.cpp   其中-c表示只编译不链接

    objdump -h .\tmp.o  其中-h表示查看段表

    objdump -t .\tmp.o   其中-t表示查看符号表

    代码二:

    // tmp.cpp
    
    #include<iostream>
    using namespace std;
    
    int main()
    {
        static int abcdefg[100]={0};
        cout<<abcdefg[0];
        return 0;
    }

    只把数组初始值改成了0

    gcc -c .\tmp.cpp 

    objdump -h .\tmp.o 

    可以看出,数组初始化为1的话,存在data段中,初始化为0的话,存在bss段中。

     

    展开全文
  • ELF文件详解

    2021-03-28 10:18:33
    一、ELF概述 1、ELF的定义 ELF(Executable and Linkable Format)文件是一种目标文件格式,常见的ELF格式文件包括:可执行文件、可重定位文件(.o)、共享目标文件(.so)、核心转储文件等。 ELF主要用于Linux平台,...

    一、ELF概述

    1、ELF的定义

    ELF(Executable and Linkable Format)文件是一种目标文件格式,常见的ELF格式文件包括:可执行文件、可重定位文件(.o)、共享目标文件(.so)、核心转储文件等。

    ELF主要用于Linux平台,Windows下是PE/COFF格式。       

    2、ELF文件的结构

    一个完整的ELF文件一般会包括如下几个内容:ELF头、Section头、Program头和Section。

    其中由Section头组成的集合称为Section头表,由Program头组成的集合称为Program头表。注意:数个连续的头称之为头表,头表是虚拟出来的定义,文件中不存在头表,只有头。

    一个Section头指向一个Section,Section头中包括所指向Section的名字、类型、其在ELF文件中的偏移地址、大小等信息。

    一个Program头指向一个Segment,Program头中包括所指向Segment的类型、其在ELF文件中的偏移地址、大小,映射到内存的虚拟地址等信息。一个Segment由一系列连续的Section构成,连续的Section拥有相同的权限,如只读、读写、可读可执行等;

    一个ELF头内包含有:Section头表的在ELF文件中的偏移地址、单个Section头的大小、Section头表中Section头的个数;Program头表的在ELF文件中的偏移地址、单个Program头的大小、Program头表中Program头的个数;该ELF文件的类型,若是可执行文件的话,还包含的有程序的入口地址。

    3、头的表示方法及其含义

    1)变量及其大小:

    2)ELF头

    #define EI_NIDENT 16
     
    struct Elf32_Ehdr            //共52个字节    //Ehdr表示ELF header
    {
      unsigned char  e_ident[EI_NIDENT];
      Elf32_Half e_type;        //类型包括:可执行文件、可重定向文件、共享目标文件等
      Elf32_Half e_machine;     //有X86、arm之类
      Elf32_Word e_version;
      Elf32_Addr e_entry;       //可执行程序的入口地址
      Elf32_Off e_phoff;        //Program头表的偏移地址
      Elf32_Off e_shoff;        //Section头表的偏移地址
      Elf32_Word e_flags;
      Elf32_Half e_ehsize;      //本结构体的size
      Elf32_Half e_phentsize;   //单个Program头的size
      Elf32_Half e_phnum;       //Segment头表中Segment头的个数
      Elf32_Half e_shentsize;   //单个Section头的szie
      Elf32_Half e_shnum;       //Section头表中Section头的个数
      Elf32_Half e_shstrndx;    //储存Section名字集合的Section的下标,指".shstrtab"的下标
    };

    2)Section头

    struct Elf32_Shdr              //共40个字节    //Shdl表示Section header
    {
        Elf32_Word sh_name;        //所指向Section的名字,如".text"、".data"、".bss"等
        Elf32_Word sh_type;        //所指向Section的类型,如:符号表、字符串表等
        Elf32_Word sh_flags;       
        Elf32_Addr sh_addr;
        Elf32_Off sh_offset;       //所指向Section在ELF文件中的偏移量
        Elf32_Word sh_size;        //所指向Section的size
        Elf32_Word sh_link;        //和其关联的Section头的下标索引
        Elf32_Word sh_info;
        Elf32_Word sh_addralign;   //字节对齐
        Elf32_Word sh_entsize;
    };
     

    3)Program头

    struct Elf32_phdr            //32个字节    //phdr表示Program header
    {
        Elf32_Word p_type;       //如PT_LOAD表示,对应Segment可被加载到内存中
        Elf32_Off p_offset;      //Segment在ELF文件中的偏移量
        Elf32_Addr p_vaddr;      //Segment映射到内存后的虚拟地址
        Elf32_Addr p_paddr;      //Segment映射到内存后的物理地址,此时与虚拟地址相同
        Elf32_Word p_filesz;     //Segment在ELF文件中占用的size
        Elf32_Word p_memsz;      //Segment映射到内存后占用的size
        Elf32_Word p_flage;      //读、写、执行权限
        Elf32_Word p_align;      //字节对齐,p_vaddr和p_paddr对p_align取模后为0
    };

    更详细内容请参考:ELF文件格式解析

    4、实例解析

    可执行文件中Program头表是必须的,可重定向文件(.o)中Section头表是必须的,共享目标文件(.so)中两者都是必须的。

    1)可重定向文件分析

    ELF头信息如下所示:

    在此文件中,可看到其类型为REL, 即可重定向文件。其中Program头的个数为0,Section头的个数为8个,没有程序入口地址。

    下图是8个Section头的详细信息:

    其中Addr在此处被填充为了0的原因是,其目前并不需要被加载到内存中,在链接的时候才会被填充。

    根据上述各Section的偏移量及size可推断出其在该可重定向文件中空间布局,如下表所示:

    偏移量(Off)大小(size)Section备注
    0x00x34ELF头0x34表示十进制的52,刚好为ELF头的大小
    0x340x2a.text 
    0x600x38.data 
    0x980x0.bss 
    0x980x30.shstrtab 
    0xc80x140Section头表一个Section头的大小为40个字节,共8个头,大小为0x140
    0x2080x80.symtab 
    0x2880x28.strtab 
    0x2b00x10.rel.text 

    下面详述上面几种类型的Section:

    Ⅰ .shstrtab

    .shstrtab中存放着各个Section的名字。

    Ⅱ .strtab

    .symtab中存放着程序中用到的符号的名字。

    Ⅲ .bss

    程序中未初始化的全局变量都会被归类到bss段,并在程序加载的时候被初始化为0。

    在加载.bss的时候和.data一样,都属于可读可写的数据,但在ELF文件中.data需要占用一段内存空间来保存变量的初始化值,而.bss却不需要。

    也就是说,.bss只占用一个Section头的大小,而不需要对应的Section。如上表中可以看出.bss所描述Section的size为0。

    Ⅳ .rel.text

    .rel.text用于告诉链接器,哪些地方需要重定向。

    Ⅴ .symtab

    .symtab内存放着程序中用到的符号,包括变量符号、函数符号,如printf、main等。

    .symtab有如下定义:

    struct Elf32_sym                //
    {
        Elf32_Word st_name;         //符号的名字
        Elf32_Addr st_value;        //符号相对于其所在Section偏移的相对地址
        Elf32_Word st_size;         //符号的size
        unsigned char st_info;      //低四位表示符号的作用范围(全局或局部),高四位表示符号的类型(变量、函数等)
        unsigned char st_other;
        Elf32_Half st_shndx;        //该符号的值在哪个Section下存储
    };

    实例:

    以上图中的data_items为例,其Ndx为3,表示其在第3个Section,即.data。data_items的value值为00000000,表示其相对于.data的偏移地址为0,即data_itms在.data的开头。

    _start的value也为00000000,表示其在.text的开头,也即整个代码的入口是_start。

    2)可执行文件分析

    可执行文件的ELF头信息如下所示:

    相对于可重定向文件来说,其类型变为了EXEC,少了两个Section header,多了两个Program头,并且有可执行程序的入口地址。

    6个Section头如下所示:

    从图中可以看出,.text和.data的Addr不再为0,有了实际的值,这便是在链接过程中装载上的。

    .bss段因为没有使用到,所以被删除掉了。

    .rel.text在链接之后,便完成了自己的使命,也就被删除掉了。

    根据上述各Section的偏移量及size可推断出其在该可执行文件中空间布局,如下表所示:

    偏移量(Off)大小(size)Section备注
    0x00x34ELF头0x34表示十进制的52,刚好为ELF头的大小
    0x340x40Program头表一个Program头的大小为32字节,共2个头,大小为0x40
    0x740x2a.text 
    0xa00x38.data 
    0xd80x27.shstrtab 
    0x1000xf0Section头表一个Section头的大小为40个字节,共6个头,大小为0xf0
    0x1f00xa0.symtab 
    0x2900x40.strtab 

    2个Program头如下所示:

    结合Program头和Section的空间布局表可以看出,ELF头、Program头表和Section头表共同组成了第一个Segment;.data单独组成了另一个Segment。

    VirtAddr列指出第一个Segment加载到虚拟地址0x0804 8000(注意在x86平台上后面的PhysAddr列是没有意义的),第二个Segment加载到地址0x0804 90a0。

    Flg列指出第一个Segment的访问权限是可读可执行,第二个Segment的访问权限是可读可写。

    最后一列Align的值0x1000(4K)是x86平台的内存页面大小。在加载时要求文件中的一页对应内存中的一页,对应关系如下图所示:

    这个可执行文件很小,总共也不超过一页大小,但是两个Segment必须加载到内存中两个不同的页面,因为MMU的权限保护机制是以页为单位的,一个页面只能设置一种权限。

    此外还规定每个Segment在文件页面内偏移多少加载到内存页面仍然偏移多少,比如第二个Segment在文件中的偏移是0xa0,在内存页面0x0804 9000中的偏移仍然是0xa0,所以是从0x0804 90a0开始,这样规定是为了简化链接器和加载器的实现。

    从上图也可以看出.text段的加载地址应该是0x0804 8074,这也正是程序的入口地址。
     

    原来目标文件符号表中的Value都是相对地址,现在都改成绝对地址了。此外还多了三个符号__bss_start_edata_end,这些是在链接过程中添进去的,加载器可以利用这些信息把.bss段初始化为0。

    5、可执行ELF文件的装载过程

     

     

    附录

    1)Section与Segment的英文含义

    section和segment都有部分的意思,但

    section指的“部分”是不同质的,如:The TOEFL is divided into three sectiond, namely listening, structure and reading.在这里托福考试是由三部分组成的,这三部分是不一样的,即不同质的。

    而segment指的“部分”是同质的,如:I want the middle segment of the rope. 我想要中间那段绳...

    2)段错误(Segment Error)

    当程序试图访问不允许访问的内存位置,或试图以不允许的方式访问内存位置(例如尝试写入只读位置,或覆盖部分操作系统)时会发生段错误。

    常见的段错误,包括:

    1)使用未经初始化及或已经释放的指针地址

    2)访问受系统保护的内存地址

    3)写入只读的内存地址

    4)数组越界

    5)堆栈溢出

    参考资料:

    1.ELF文件解析和加载(附代码)

    2.ELF文件格式解析(完)

    3.ELF文件详解—初步认识

    展开全文
  • 1. ELF 文件类型 2. ELF 内容布局 3. ELF 数据类型 4. ELF Header

    为什么需要懂 ELF

    1. 可以理解程序是如何进行静态连接和动态连接
    2. 从进程中获取程序各种有用信息,从而制作各种底层工具

    ELF 文件类型

    ELF 对象文件主要有3种类型:

    1. relocatable file:包含数据和代码,需要连接其他文件来生成 executable file 或 shared object file。
    2. executable file:包含了可执行的程序,指定了如何在运行时创建进程镜像。
    3. shared object file:包含数据和代码,可以联合其他 relocatable file 和 shared object file 来生成新的 shared object file;或者在 executable file 运行时,和其他 shared object file 一起被合并进去,从而创建进程镜像。

    ELF 内容布局

    Object File Format
    我们主要关注 Linking View。
    Linking View 主要分成4部分:

    1. ELF Header,用来描述该对象文件的各项信息
    2. Program header table:虽然叫 table,其实就是一个 Program header 的数组,所有 Program header 都等长。
    3. Section(s): 根据 Section Header 的不同,对应的 Section 内容也不同,而且各个 Section 的长度也不一样。
    4. Section header table:虽然叫 table,其实就是一个 Section header 的数组,所有 Section header 都等长。Section header 包含了其对应 Section 的各项元数据,根据这些元数据,就能正确解读 Section 的内容。

    这4部分的布局顺序除了 ELF Header 外,其他都不是固定的,因为包含了定位元数据,所以它们的顺序可变。

    ELF 数据类型

    DataTypes
    数据类型分了32位和64位,我们主要关注64位。

    实验准备

    环境:

    $ cat /etc/os-release 
    NAME="Linux Mint"
    VERSION="18.3 (Sylvia)"
    ID=linuxmint
    ID_LIKE=ubuntu
    PRETTY_NAME="Linux Mint 18.3"
    VERSION_ID="18.3"
    HOME_URL="http://www.linuxmint.com/"
    SUPPORT_URL="http://forums.linuxmint.com/"
    BUG_REPORT_URL="http://bugs.launchpad.net/linuxmint/"
    VERSION_CODENAME=sylvia
    UBUNTU_CODENAME=xenial
    $ uname -a
    Linux helowken-mint 4.10.0-38-generic #42~16.04.1-Ubuntu SMP Tue Oct 10 16:32:20 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
    

    程序:
    library.h

    int function(int);
    

    library.c

    int a = 100;
    
    int function(int input) {
    	return input + 10;
    }
    

    program.c

    #include <stdio.h>
    #include "library.h"
    
    extern int a;
    
    char c[10];
    static char d = 'd';
    char* f = (char*) function;
    
    int main() {
    	int d = function(100) + a;
    	printf("a: %d\n", a);
    	printf("result: %d\n", d);
    }
    

    编译:
    生成 shared object file: libfunc.so

    $ gcc -shared -fPIC -o libfunc.so library.c
    

    生成 relocatable file:program.o

    $ gcc -c program.c
    

    生成 executable file 时会出错:

    $ gcc -o program program.o
    program.o: In function `main':
    program.c:(.text+0xe): undefined reference to `function'
    program.c:(.text+0x16): undefined reference to `a'
    program.c:(.text+0x21): undefined reference to `a'
    program.o:(.data+0x8): undefined reference to `function'
    collect2: error: ld returned 1 exit status
    

    因为 program.o 中包含有未知的符号,所以需要增加 libfunc.so 的连接。(后面会详细说明如何进行符号解析)
    生成 executable file:program

    $ gcc -o program program.o -L. -lfunc
    

    但运行时会出错

    $ ./program
    ./program: error while loading shared libraries: libfunc.so: cannot open shared object file: No such file or directory
    

    因为运行时,动态连接器(dynamic-linker)无法找到 libfunc.so,所以无法对符号进行解析。
    增加 libfunc.so 的连接后,运行正常。

    $ export LD_LIBRARY_PATH=.
    $ ./program
    a: 100
    result: 210
    

    目前为止,我们得到了3个文件,分别是

    1. libfunc.so: shared object file
    2. program.o: relocatable file
    3. program: executable file

    查看 ELF Header

    使用 readelf 这个命令,可以查看 ELF 的头部。

    $ readelf -h program.o
    ELF Header:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF64
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              REL (Relocatable file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x0
      Start of program headers:          0 (bytes into file)
      Start of section headers:          1048 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           0 (bytes)
      Number of program headers:         0
      Size of section headers:           64 (bytes)
      Number of section headers:         14
      Section header string table index: 11
    

    ELF Header 包含了很多信息,我们从字节级别去解读比较容易理解。

    #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;
    
    typedef struct {
            unsigned char   e_ident[EI_NIDENT]; //16 B (B for bytes)
            Elf64_Half      e_type; // 2 B
            Elf64_Half      e_machine; // 2 B
            Elf64_Word      e_version; // 4 B
            Elf64_Addr      e_entry; // 8 B
            Elf64_Off       e_phoff; // 8 B
            Elf64_Off       e_shoff; // 8 B
            Elf64_Word      e_flags; // 4 B
            Elf64_Half      e_ehsize; // 2 B
            Elf64_Half      e_phentsize; // 2 B
            Elf64_Half      e_phnum; // 2 B
            Elf64_Half      e_shentsize; // 2 B
            Elf64_Half      e_shnum; // 2 B
            Elf64_Half      e_shstrndx; // 2 B
    } Elf64_Ehdr;  // total size = 64 B
    

    这是 ELF 头部的定义,我们只关注 Elf64_Ehdr (64位系统的定义)。
    注释部分是我添加上去的,可以看出整个 ELF Header 占据 64 bytes。

    Elf64_Ehdr

    e_ident[EI_NIDENT]

    从定义可知:EI_NIDENT = 16
    以下是这16个字节的定义。
    在这里插入图片描述
    查看这16个字节:

    $ hexdump -C -n16 program.o
    00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
    00000010
    

    index = [0, 3] 字节: “7f 45 4c 46”,根据对照图,应该是 “0x7f E L F”。

    # 打印 index=[1,3] 这3个字节的 ascii 形式
    $ echo 0x45 0x4c 0x46 | xxd -r
    ELF
    

    index = 4 字节: “02”,根据对照图,可以得知,这是64位的对象文件。这个字节很重要,因为不论是数据类型还是 ELF Header 的定义,以及后面会讲到的其他信息都是划分为32位和64位的,因此必须根据这个字节来进行不同的解析。

    index = 5 字节: “01”,表示数据编码。编码方式有两种(都是2的补码形式)

    1. little endian: 低位在低地址,高位在高地址。(值为1)
    2. big endian: 高位在低地址,低位在高地址。(值为2)

    这个字节同样很重要,直接影响解读方式。由此可知 program.o 的数据编码是 little endian。

    index = 6 字节: “01”,表示当前版本,一般用于判断这个对象文件是否有效,当前只有等于1是有效的。

    index = [7, 8] 字节: “00 00”,表示系统扩展,ABI 扩展和 ABI 版本,我们先跳过。ABI 全称 Application Binary Interface,针对不同系统有不同规范,比如 i386-ABI 对应的是 x86 系列,x86-64-ABI 对应 x86-64 系列。

    index = [9, 15] 字节: 这些字节作为 padding 全部为0,用作以后的扩展,当前可以直接忽略。

    e_type

    表示对象文件的类型。
    在这里插入图片描述
    数据类型:Elf64_Half (2 bytes)

    # -C 表示16进制 + ascii 显示, -s 表示 offset, -n 表示提取字节数
    # 这里 -s16 表示跳过前面的 e_ident
    $ hexdump -C -s16 -n2 program.o
    00000010  01 00                                             |..|
    00000012
    

    因为 program.o 的数据编码方式是 little endian,所以 “01 00” 应该看成 “0x0001”,也就是1。从对照图可知,1 表示 Relocatable file。

    e_machine

    表示所需要的 CPU 架构。对照图有很多很多不同的 CPU 架构,这里只列出一部分。
    在这里插入图片描述
    数据类型:Elf64_Half (2 bytes)

    $ hexdump -C -s18 -n2 program.o
    00000012  3e 00                                             |>.|
    00000014
    

    “3e 00” + little endian = 0x003e = 3 * 16 + 14 = 62。
    从对照图可知,program.o 需要 AMD x86-64 的架构。(虽然叫 AMD x86-64,实际上 Intel 的 CPU 也支持,只不过64位架构是 AMD 先做出来,所以很多地方都会将其称作 AMD x86-64)

    e_version

    表示对象文件的版本,跟 e_ident 中的 EI_VERSION 等价。
    数据类型:Elf64_Word (4 bytes)

    $ hexdump -C -s20 -n4 program.o
    00000014  01 00 00 00                                       |....|
    00000018
    

    “01 00 00 00” + little endian = 0x1

    e_entry

    系统第一次转交控制的虚拟地址,也就是所谓的程序入口。我们写的程序,如果要运行,都有一个 main 方法(C/C++/Java/Python),但这个并非是程序入口,真正的入口是 “_start”,后面会讲到。

    数据类型:Elf64_Addr (8 bytes)

    hexdump -C -s24 -n8 program.o
    00000018  00 00 00 00 00 00 00 00                           |........|
    00000020
    

    这一串"00"表示无入口,因为 program.o 并非是可执行程序 (Executable file)。
    再试试 prorgram (Executable file)

    $ hexdump -C -s24 -n8 program
    00000018  10 06 40 00 00 00 00 00                           |..@.....|
    00000020
    

    可以看到入口地址为: “10 06 40 00 00 00 00 00” + little endian => 0x400610
    可以通过 readelf 来验证:

    $ readelf -h program
    ELF Header:
    ...
      Entry point address:               0x400610
    ...
    

    查看一下该虚拟地址对应的汇编代码:

    $ objdump -D program | grep 400610
    0000000000400610 <_start>:
      400610:	31 ed                	xor    %ebp,%ebp
    

    可以看到这个地址对应的是一个叫 _start 的方法/过程。(后面会讲到这个方法)

    e_phoff

    表示 Program header table 在文件中的 offset,如果这个 table 不存在,则值为0。

    数据类型:Elf64_Off (8 bytes)

    $ hexdump -C -s32 -n8 program.o
    00000020  00 00 00 00 00 00 00 00                           |........|
    00000028
    

    因为 program.o 是 Relocatable file,所以没有 Program header table.

    $ hexdump -C -s32 -n8 program
    00000020  40 00 00 00 00 00 00 00                           |@.......|
    00000028
    

    因为 program 是 Executable file,所以可以看到 offset 为:
    “40 00 00 00 00 00 00 00” + little endian = 0x40 = 4 * 16 = 64
    再对照一下 Elf64_Ehdr 的定义,Elf64_Ehdr 长度为 64 bytes,也就是说,Program header table 紧贴着 ELF Header。

    e_shoff

    表示 Section header table 在文件中的 offset,如果这个 table 不存在,则值为0。

    数据类型:Elf64_Off (8 bytes)

    $ hexdump -C -s40 -n8 program.o
    00000028  18 04 00 00 00 00 00 00                           |........|
    00000030
    

    可以看到 offset 为: “18 04 00 00 00 00 00 00” + little endian = 0x418 = 1048
    可以通过 readelf 来验证。

    $ readelf -h program.o
    ELF Header:
    ... 
      Start of section headers:          1048 (bytes into file)
    ...
    
    e_flags

    表示处理器特定的标志的后缀,我们不关心,跳过。

    数据类型:Elf64_Word(4 bytes)

    $ hexdump -C -s48 -n4 program.o
    00000030  00 00 00 00                                       |....|
    00000034
    
    e_ehsize

    表示 ELF Header 的大小。

    数据类型:Elf64_Half(2 bytes)

    $ hexdump -C -s52 -n2 program.o
    00000034  40 00                                             |@.|
    00000036
    

    大小为: “40 00” + little endian = 0x40 = 64,跟 Elf64_Ehdr 的定义是一致的。

    e_phentsize

    表示 Program header 的大小。(Program header 都是等长的)

    数据类型:Elf64_Half(2 bytes)

    $ hexdump -C -s54 -n2 program.o
    00000036  00 00                                             |..|
    00000038
    

    因为 program.o 没有 Program header table,所以这里的大小为0。

    $ hexdump -C -s54 -n2 program
    00000036  38 00                                             |8.|
    00000038
    

    可以看到 program 的 Program header 大小为:
    “38 00” + little endian = 0x38 = 56 (bytes)

    e_phnum

    表示有多少个 Program header。
    (Program header table 的大小:e_phentsize * e_phnum)

    数据类型:Elf64_Half(2 bytes)

    $ hexdump -C -s56 -n2 program.o
    00000038  00 00                                             |..|
    0000003a
    

    因为 program.o 没有 Program header table,所以这里的大小为0。

    $ hexdump -C -s56 -n2 program
    00000038  09 00                                             |..|
    0000003a
    

    可以看到 program 的 Program header 数目为:
    “09 00” + little endian = 0x9 = 9

    e_shentsize

    表示 Section Header 的大小。(Section Header 都是等长的)

    数据类型:Elf64_Half (2 bytes)

    $ hexdump -C -s58 -n2 program.o
    0000003a  40 00                                             |@.|
    0000003c
    

    大小为:“40 00” + little endian = 0x40 = 4 * 16 = 64 (bytes)

    e_shnum

    表示 Section Header 的数量。
    (Section header table 的大小:e_shentsize * e_shnum)

    数据类型:Elf64_Half (2 bytes)

    $ hexdump -C -s60 -n2 program.o
    0000003c  0e 00                                             |..|
    0000003e
    

    数量为:“0e 00” + little endian = 0xe = 14

    e_shstrndx

    每个 Section 都使用一个字符串作为其名字(后面会讲到),这些字符串统一存放在同一个 Section 中,这个 Section 由专门的 Section Header 来描述它的位置和大小。而这个 Section Header 存放在 Section header table 中,为了快速检索到这个 Section Header,于是使用 e_shstrndx 来保存它在 Section header table 中的索引值。

    数据类型:Elf64_Half (2 bytes)

    $ hexdump -C -s62 -n2 program.o
    0000003e  0b 00                                             |..|
    00000040
    

    索引值为:“0b 00” + little endian = 0xb = 11

    PS:e_shnum 和 e_shstrndx 如果大于某个特定值时,会使用另外的存储方式来保存这2个值。鉴于大多数情况下,这2个值都不会过大,所以当前我们不讨论特殊情况。

    到目前为止,我们已经探讨了 ELF Header 所包含的大部分信息。
    对照 readelf -h program.o 的输出,可以加深对这些元数据的理解。

    下一篇:ELF 详解2 – Section Header & Section

    展开全文
  • linux ELF文件格式分析

    2021-07-08 14:11:29
    linux ELF相关定义 /* Type for a 16-bit quantity. */ typedef uint16_t Elf64_Half; /* Types for signed and unsigned 32-bit quantities. */ typedef uint32_t Elf64_Word; typedef int32_t Elf64_Sword; /*...
  • Linux下ELF文件解析

    2021-05-09 05:15:51
    1. windows PE文件与Linux ELF文件概述在windows中可执行文件是pe文件格式,Linux中可执行文件是ELF文件,其文件格式是ELF文件格式,在Linux下的ELF文件除了可执行文件(Excutable File),可重定位目标文件...
  • ELF重定位

    2021-10-30 16:36:35
    在工作和学习中,越发发现ELF文件格式的重要性,今天简单谈谈ELF重定位。重定位技术实际上是给二进制打补丁的机制,如果使用了动态连接器,可以使用重定位在内存打热补丁。 ELF重定位重定位技术实际上是给二进制打...
  • elf文件格式分析

    2021-01-12 16:08:10
    各种讲解elf文件格式一上来就是各种数据类型,看了半天却不知道这些数据类型是干啥的,所以咱就先找个例子直接上手,这样对elf文件格式有个具体而生动的了解。然后再去看那些手册,就完全不惧了~。我们使用一个汇编...
  • ELF 格式详解

    2021-03-25 13:25:46
    ELF 全称 “Executable and Linkable Format”,即可执行可链接文件格式,目前常见的Linux、 Android可执行文件、共享库(.so)、目标文件( .o)以及Core 文件(吐核)均为此格式。文件布局 常见的ELF文件大致结构...
  • ELF加载器 为什么需要ELF 隔离应用服务和内核服务 有了ELF,我们的应用程序都可以通过编译成ELF的方式从外部加载,系统内核部分只提供关键的内核服务(内存管理/中断/调度/IPC)和系统服务(文件/网络),用户服务...
  • ELF(Executable and Linkable Format)即可执行连接文件格式,是一种比较复杂的文件格式,但其应用广泛。与linux下的其他可执行文件(a.out,cof)相比,它对节的定义和gnu工具链对它的支持使它十分灵活,它保存的足够...
  • Elf64 格式

    2021-05-13 23:07:05
    ELF文件格式ELF (Executable and Linkable Format)是一种为可执行文件,目标文件,共享链接库和内核转储(core dumps)准备的标准文件格式。 Linux和很多类Unix操作系统都使用这个格式。 让我们来看一下64位ELF文件...
  • ELF文件——动态链接

    2021-09-10 15:02:45
    链接器以ELF文件的固定格式对目标程序进行链接,程序加载器以ELF文件的固定格式对其进行解析。ELF文件的组成框架在链接器和加载器的视角中分别如下: 如上图所示,链接器以section为单位对数据进行组织,以section ...
  • ELF文件中的各个节区

    2021-08-24 23:50:13
    ELF节区信息概述: 节区名 节区说明 备注 .rodata1 和rodata类似,只存放只读数据 .comment 存放编译器版本信息,如字符串 "GCC...
  • ELF格式简介ELF是现代linux/unix流行的程序可执行链接格式,它有官方定义文档,具体的资料在Tool Interface Standard Executable and Linking Format Specification version 1.2有其对应的详细描述。我在这里不会...
  • Linux下ELF的执行过程

    2021-05-11 16:03:31
    binary() 对于elf格式,为load_elf_binary(struct linux_binprm *bprm) 这里重点讨论load_elf_binary() 首先检查会检查是否为ELF格式,然后根据ELF header(loc->elf_ex,内容即linux_binprm->buf)将Program header...
  • 而我们研究的ELF文件格式的linux_binfmt结构对象elf_format, 定义如下, 在/fs/binfmt.c中static struct linux_binfmt elf_format = { .module = THIS_MODULE, .load_binary = load_elf_binary, .load_shlib = load_...
  • 1:#include2:#include3:#include4:#include"elf_GetElfInfo.h"5:#include"elf_interface.h"6:#include"../../include/adt.h"7:8:long ELF_GetELFEnddian(SEF_ELFHEADER* pHandle)9:{10: long ret = 0;11: SEF_...
  • Linux ELF文件

    2021-02-20 13:55:52
    ELF全称:可执行链接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的。ELF文件有三种不同类型: 可重定位文件:包含代码和数据,用于链接成可执行文件或...
  • elf文件分析(一)

    2021-10-30 11:39:12
    ELF全称Executable and Linkable Format,可执行连接格式,最初由UNIX系统实验室作为应用程序二进制接口(Application Binary Interface –ABI)的一部分而制定和发布,是COFF(Common file format)格式的变种。...
  • 要求:1.分析文件头。2.通过文件头找到section header table,理解其内容。3.通过section header table找到各section。...一、基础知识ELF全称Executable and Linkable Format,可执行连接格式,ELF格式的文件...
  • C语言解析FLM(ELF)格式文件

    千次阅读 2021-04-10 15:20:36
    运行效果 flmparse.c /* * flmparse.c * * Created on: 2021年4月10日 * Author: hello */ #include <stdio.h> #include <stdint.h>...#include "elf.h" #define FILENAME "..
  • ELF PLT Hook 原理简述

    2020-12-30 11:22:49
    【无线平台】ELF PLT Hook 原理简述 简述 Android 是基于Linux的操作系统,因此在Android开发平台上,ELF是原生支持的可执行文件格式;ELF文件格式除了作为可执行文件,还可以作为共享库格式,也就是我们常见的so...
  • 一、搜索并下载 ELF.bt 模板、 二、安装 ELF.bt 模板、 三、打开 ELF 文件、
  • 扒一扒ELF文件

    2021-01-30 22:46:47
    ELF文件(Executable Linkable Format)是一种文件存储格式。Linux下的目标文件和可执行文件都按照该格式进行存储,有必要做个总结。 文章目录1. 链接举例2. ELF文件类型2.1 可重定位目标文件(.o文件)2.2 可执行...
  • ELF基本结构

    2021-05-19 00:58:58
    《程序员的自我修养——连接、...主流平台的可执行文件格式主要有Windows下的PE(Portable Executable)和Linux下的ELF(Executable Linkable Format),他们都是COFF(Common File Format)的变种。ELF文件标准里面把系统...
  • 我们在讲ELF Header的时候,讲到了section header table。它是一个section header的集合,每个section header是一个描述section的结构体。在同一个ELF文件中,每个section header大小是相同的。(其实看了源码就知道...
  • 使用objdump 和readelf 两个命令,我们可以看到elf的各个节段的 信息还有 运行时需要那些动态链接库,elf中的汇编代码等等。 我就是想用来查看 这个elf运行需要哪些 *.so 文件#includeint test(){return 0;}int main...
  • ELF (Executable and Linkable Format) Linux 上源码编译后的 .o 文件即目标文件,目标文件结构上和可执行文件格式很相似,通过链接器链接相应的库后得到可执行文件 .elf。为了描述方便,文中不区分二者的存储格式。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 104,326
精华内容 41,730
关键字:

elf