现在的位置: 首页 > 综合 > 正文

手工打造ELF文件

2013年04月29日 ⁄ 综合 ⁄ 共 5726字 ⁄ 字号 评论关闭

看见手工打造PE文档,在学习PE格式之初,帮了很多忙。
呵呵,这会在研究linux下的执行文件的时候就萌发的手写ELF文件的想法。于是就有了如下文字。
论坛很早之前有hangj发过研究贴,他比我研究的要透彻,想学习的可以看他的帖子也可以搜索网上的文章,有很多都值得学习的。
我这个只能算是学习的笔记了。
因为是手写,写不了庞大的数据只能就简了,已经剔除了很多ELF中重要的概念。 如,节区,节表。
手写ELF文档(由于开源,比起PE应该是容易不少)
首先是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;

前16个字节是属于标识ELF的一些信息
(unsigned char e_ident[EI_NIDENT])
----------------------B.J.H split'line----------------------------------
0000-0000:是magicnum:7Fh (和pe格式的MZ 或者 PE 相似。我说不清)  
0001-0003:标识符45h 4fh 46h
0004-0004:01 这位是标明文件的类型,如果是0为非法,1为32位目标,2为64位目
          在这里为32位目标文件。
0005-0005:01 这位是处理器的编码方式,01表示小端编码,02表示大端编码,0是非法数据编码。
0006-0006:01 这位是头部版本号。
0007-000f:00 00 00 00 00 00 00 00 00
          标记 e_ident 中未使用字节的开始。初始化为 0。
-----------------------B.J.H split'line---------------------------------
然后是e_type,用来指明文件类型是三种object文件中的哪种

0010-0011:02 00
           这里,1是可重定位的目标文件(gcc c得到的是这样的文件)
               2代表可执行文件,
               3表示共享目标文件(gcc shared得到的文件如此),同样,
               0是指未知的目标文件。其它的值含义如下

        ET_CORE         4  Core file
        ET_LOPROC  0xff00  Processor-specific
        ET_HIPROC  0xffff  Processor-specific

-----------------------B.J.H split'line---------------------------------

下面是e_machine,用以指定系统的体系结构

0012-0013:03 00
                这里的3是指Intel 80386结构,据某篇文档说,ia32的结构上这位是必须指定为EM_386的,即值为3。其它取值含义如下:          
          给出文件的目标体系结构类型
          名称       取值 含义
          EM_NONE 0
          EM_M32   1  AT&T WE 32100
          EM_SPARC 2  SPARC
          EM_386   3  Intel 80386
          EM_68K   4  Motorola 68000
          EM_88K   5  Motorola 88000

-----------------------------------------B.J.H split'line---------------------------------
第14到17位是e_version,指明目标文件版本
0014-0017:01 00 00 00
           取1为当前版本EV_CURRENT,取0为非法版本EV_NONE
-----------------------------------------B.J.H split'line---------------------------------
接下来是e_entry,程序入口的虚拟地址。如果目标文件没有程序入口,可以为 0
0018-001b:60 00 40 80 入口地址(具体添充方法见最后)
-----------------------------------------B.J.H split'line---------------------------------
 然后是e_phoff和e_shoff,指程序头部表格和节区表格的偏移量, 以字节计算,如果没有的话,可以为0。这里是这样的
001c-001f:34 00 00 00  ;程序头部表现在在ELF头部表结束后第一个字节开始 即34h 字节处
0020-0023: 00 00 00 00  ;没有节表
后面接e_flags,是与文件和处理器相关的标志。这里是
0024-0027:00 00 00 00  ;flag的名字来自于EF_<machine>_<flag>。
                          看下机器信息“Machine Information”部分的flag的定义
              (我自己也没有看,我看到大部分成学都是空的应该是不要紧的参数)。
----------------------------------------B.J.H split'line---------------------------------
然后就是e_ehsize了,说明了ELF头部的大小(字节)
0028-0029:34 00
----------------------------------------B.J.H split'line---------------------------------
剩下的是e_phentsize,e_phnum,和e_shentsize,e_shnum,每个2字节长。
002a-002b:20 00  表明程序头部大小。
002c-002d:01 00  项目数
002e-002f:00 00  节区头部大小 不使用节表了,应该是22h字节。
0030-0031:00 00  节区数目
----------------------------------------B.J.H split'line---------------------------------
 最后是节区头部表格中与名称字串相关的表项索引。如果没有节区名称字符串表,此参数可为SHN_UNDEF
0032-0033:00 00
到此位置我们ELF即为合法的了,呵呵。实际上这里只是一个文件头,下面我要让它成为正真的程序。
----------------------------------------B.J.H split'line---------------------------------
下面是 Program Header  程序头  

代码:
  typedef struct {
    Elf32_Word p_type;
          //  Name             Value
          //  ====             =====
          //  PT_NULL              0
          //  PT_LOAD              1
          //  PT_DYNAMIC           2
          //  PT_INTERP            3
          //  PT_NOTE              4
          //  PT_SHLIB             5
          //  PT_PHDR              6
          //  PT_LOPROC   0x70000000
          //  PT_HIPROC   0x7fffffff

    Elf32_Off  p_offset;
          //  该成员给出了该段的驻留位置相对于文件开始处的偏移。

    Elf32_Addr p_vaddr;
          //  该成员给出了该段在内存中的首字节地址。

    Elf32_Addr p_paddr;
    
    Elf32_Word p_filesz;
          //  文件映像中该段的字节数;它可能是 0 。
    Elf32_Word p_memsz;
          //  内存映像中该段的字节数;它可能是 0 。
    Elf32_Word p_flags;
    Elf32_Word p_align;
          //  该成员给出了该段在内存和文件中排列值。
          //  0 和 1 表示不需要排列。否则, p_align 必须为正的 2 的幂,
          //  并且 p_vaddr 应当等于 p_offset 模 p_align 。
  } Elf32_Phdr;

0034-0037:01 00 00 00 ;p_type : PT_LOAD ;段的加载类型(下面是解释数据为1原因。)
                                          此类型数组元素给出一个可加载的段,
                      段的大小由 p_filesz 和 p_memsz 描述。
                      文件中的字节被映射到内存段开始处。
                      如果 p_memsz 大于p_filesz,“剩余”的字节要清零。
                      也就是P_filesz大小决定了加载的有效数据大小
                            p_memsz 决定需要加载数据的大小
                      p_filesz 不能大于 p_memsz。
                      可加载的段在程序头部表格中根据p_vaddr成员按升序排列。
                      其他参数解释查相关资料。
0038-003b:00 00 00 00 ;p_offset ;该段的偏移量,此段是从开始处开始加载的。偏移 0h
003c-003f:00 00 48 80 ; p_vaddr ;加载到内存地址,此项是固定。不同于PE
0040-0043:00 00 48 80 ;p_paddr (ignored) ;物理的不好操作就同上了
0044-0047:20 00 00 00 ;p_filesz ;解释过了。
0048-004b:20 00 00 00 ;p_memsz  ;同上
004c-004f:00 00 00 00 ;p_flags : 以下两个值是照搬的,有待以后继续研究
0050-0053:00 00 00 00 ;p_align

 这个是一个输出"ok!"字样的机器码。
 B8 04 00 00 00 BB 01 00 00 00 68 6F 6B 21 00 8B CC BA 03 00 00 00 CD 80 B8 01 00 00 00 CD 80
 
 完整的机器码如下
 

代码:
 7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00 
 02 00 03 00 01 00 00 00 60 00 48 80 34 00 00 00
 00 00 00 00 00 00 00 00 34 00 20 00 01 00 00 00
 00 00 00 00 01 00 00 00 00 00 00 00 00 00 48 80 
 00 00 48 80 7F 00 00 00 7F 00 00 00 05 00 00 00 
 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
 B8 04 00 00 00 BB 01 00 00 00 68 6F 6B 21 00 8b 
 CC BA 03 00 00 00 CD 80 B8 01 00 00 00 CD 80 00
 

抱歉!评论已关闭.