首页
社区
课程
招聘
[翻译]42字节可执行文件;ELF介绍;求Kx(四)
发表于: 2012-9-23 20:57 7454

[翻译]42字节可执行文件;ELF介绍;求Kx(四)

2012-9-23 20:57
7454

正经描述Intel-386架构ELF格式的文档在http://refspecs.freestandards.org/elf/elf.pdf. 该文档包括各个边角,因此,人们不愿意读是完全可以理解的。实际上,关于ELF,需要知道的差不多就是一下这些内容。

每个ELF文件都由ELF头部开始。ELF头部长度是52字节,描述了该文件的一些元信息。譬如,前16个字节包含了一个“标示符”,包含文件的“magic number”(7F 45 4C 46),还有1字节的标识信息如是32bit还是64bit,little-endian还是big-endian等等。其他信息包括该ELF文件是可执行的、目标文件还是共享库;该程序的起始地址,文件内程序头表和section头表的位置。

program header table和section header table可以在文件的任意位置,但是一般而言,program header table则位于文件的结尾。这两个表的作用相似,都拥护标记文件的哥哥部分。不同之处在于program header table用于标记文件的每个部分会被加载到内存的什么位置,而section header table标记各个部分在文件里的位置。简而言之,编译器和链接器使用的是section header table,而加载器用着的是program header table. 因为这个原因,目标文件用不着program header table,而可执行文件用不着section header table,但是它们总是存在的!

因此,我们还能够去掉的部分就是section header table以及其它一些同样没用的部分了。问题是,怎么样去掉它们?

现在的标准工具里没有哪个生成的可执行文件中会没有section header table的。我们得自己动手。当然也并不意味着我们得自己动手写二进制文件,Nasm有一个 flat 二进制输出格式,我们只需要一个空白的ELF可执行文件的镜像,然后把文件填进去进行了。只填我们的程序。

我们来看一下ELF的规范,/usr/include/linux/elf.h,以及标准工具生成的可执行文件,弄明白空的ELF可执行文件的格式。但是,如果你不耐烦这么一步步的琢磨,那么看下面的一些内容:

BITS 32
  
                org     0x08048000
  
  ehdr:                                                          ; Elf32_Ehdr
                db      0x7F, "ELF", 1, 1, 1, 0         ;   e_ident
        times 8 db      0
                dw      2                                        ;   e_type
                dw      3                                        ;   e_machine
                dd      1                                         ;   e_version
                dd      _start                                 ;   e_entry
                dd      phdr - $$                            ;   e_phoff
                dd      0                                         ;   e_shoff
                dd      0                                         ;   e_flags
                dw      ehdrsize                             ;   e_ehsize
                dw      phdrsize                             ;   e_phentsize
                dw      1                                         ;   e_phnum
                dw      0                                         ;   e_shentsize
                dw      0                                         ;   e_shnum
                dw      0                                         ;   e_shstrndx
   
  ehdrsize      equ     $ - ehdr
  
  phdr:                                                            ; Elf32_Phdr
                dd      1                                           ;   p_type
                dd      0                                           ;   p_offset
                dd      $$                                         ;   p_vaddr
                dd      $$                                         ;   p_paddr
                dd      filesize                                   ;   p_filesz
                dd      filesize                                   ;   p_memsz
                dd      5                                            ;   p_flags
                dd      0x1000                                  ;   p_align
  
  phdrsize      equ     $ - phdr
  
  _start:
  
  ; your program here
  
  filesize      equ     $ - $$

以上的镜像包含ELF头部,指明这个文件是一个intel 386可执行文件,没有section header table,program header table仅包含一项,指明加载器应该把该文件整体地加载到内存0x8048000的位置,并且从_start处开始执行。这个镜像中,没有.data,没有.bss,只有必须的部分。

那么我们新的汇编程序如下:

; tiny.asm
                org     0x08048000
  
  ;
  ; (as above)
  ;

_start:
            mov bl, 42
            xor eax, eax
            inc eax
            int 0x80

filesize equ $ - $$  

编译:
$ nasm -f bin -o a.out tiny.asm
  $ chmod +x a.out
  $ ./a.out ; echo $?
  42

我们一点点自己写出的可执行文件的大小如何呢?
$ wc -c a.out
       91 a.out

这个文件还能够进一步缩减么?

如果大家刚才认真看了ELF的规范,可能会注意到这样几个问题:1. ELF文件的不同部分可以位于ELF文件的任意位置(ELF头部除外,必须放在文件的起始位置),不同部分之间甚至可以重叠;2)头部的一些域并没有使用;

特别地,16个字节的标识域的后部分的0都是填充来着,为以后的EFL扩展做准备。我们的代码仅有7个字节长,那我们能不能把代码放在ELF头部呢?

; tiny.asm
  
  BITS 32
  
                org     0x08048000
  
  ehdr:                                                 ; Elf32_Ehdr
                db      0x7F, "ELF"                     ;   e_ident
                db      1, 1, 1, 0, 0
  _start:       mov     bl, 42
                xor     eax, eax
                inc     eax
                int     0x80
                dw      2                               ;   e_type
                dw      3                               ;   e_machine
                dd      1                               ;   e_version
                dd      _start                          ;   e_entry
                dd      phdr - $$                       ;   e_phoff
                dd      0                               ;   e_shoff
                dd      0                               ;   e_flags
                dw      ehdrsize                        ;   e_ehsize
                dw      phdrsize                        ;   e_phentsize
                dw      1                               ;   e_phnum
                dw      0                               ;   e_shentsize
                dw      0                               ;   e_shnum
                dw      0                               ;   e_shstrndx
  
  ehdrsize      equ     $ - ehdr
  
  phdr:                                                 ; Elf32_Phdr
                dd      1                               ;   p_type
                dd      0                               ;   p_offset
                dd      $$                              ;   p_vaddr
                dd      $$                              ;   p_paddr
                dd      filesize                        ;   p_filesz
                dd      filesize                        ;   p_memsz
                dd      5                               ;   p_flags
                dd      0x1000                          ;   p_align
  
  phdrsize      equ     $ - phdr
  
  filesize      equ     $ - $$

编译:

$ nasm -f bin -o a.out tiny.asm
  $ chmod +x a.out
  $ ./a.out ; echo $?
  42
  $ wc -c a.out
       84 a.out

现在为止,我们的可执行文件只不过是一个ELF的头部和program header table项了,这两项是程序装载执行所必须的,看上去实在是无可缩减了!

只不过....

我们能不能想处理程序一样,把program header table也放在ELF头中呢?让它们重叠,有没有可能?

实际上确实是可行的,ELF头的最后的八个字节和program headers table的前八个字节是一模一样的。

; tiny.asm
  
  BITS 32
  
                org     0x08048000
  
  ehdr:
                db      0x7F, "ELF"             ; e_ident
                db      1, 1, 1, 0, 0
  _start:       mov     bl, 42
                xor     eax, eax
                inc     eax
                int     0x80
                dw      2                       ; e_type
                dw      3                       ; e_machine
                dd      1                       ; e_version
                dd      _start                  ; e_entry
                dd      phdr - $$               ; e_phoff
                dd      0                       ; e_shoff
                dd      0                       ; e_flags
                dw      ehdrsize                ; e_ehsize
                dw      phdrsize                ; e_phentsize
  phdr:         dd      1                       ; e_phnum       ; p_type
                                                ; e_shentsize
                dd      0                       ; e_shnum       ; p_offset
                                                ; e_shstrndx
  ehdrsize      equ     $ - ehdr
                dd      $$                                      ; p_vaddr
                dd      $$                                      ; p_paddr
                dd      filesize                                ; p_filesz
                dd      filesize                                ; p_memsz
                dd      5                                       ; p_flags
                dd      0x1000                                  ; p_align
  phdrsize      equ     $ - phdr
  
  filesize      equ     $ - $$

编译,
$ nasm -f bin -o a.out tiny.asm
  $ chmod +x a.out
  $ ./a.out ; echo $?
  42
  $ wc -c a.out
       76 a.out

看上去这一次我们真是走到尽头了。
除非,我们能改变这些结构。那需要再来认真地看一看,Linux真正需要这些文件中哪些域?譬如,Linux是否会检验e_machine域中放的是3?还是它会默认?

令人惊讶,实际上很多域都被Linux系统忽略了。

我们来看一看哪些项是必须的,哪些项是可以忽略的。开始的4个字节必须包含magic number,不然Linux不会处理该文件。但是另外三个字节并没有被检查,意味着我们有连续12个字节可以用来存放任何内容。e_type为2,表示是可执行文件;e_machine是3。e_version被忽略(目前ELF只有一个版本)。e_entry指向程序入口,e_phoff包含program header table的偏移,e_phnum包含table的项数。e_flags,目前没用;e_ehsize用于验证大小,但是Linux没用。e_phentsize用于验证program header table的项数,目前需要。其他内容与section header table相关,和我们的程序无关。

再来看看program header table的项。p_type的值为1,表明是可装载的段。p_offset需要指明文件的正确偏移职位,p_vaddr指明的是加载位置。注意,并不一定需要装在 0x08048000;任何一个大于0x00000000,小于 0x80000000并且页对齐的位置都行。p_paddress可忽略。p_filesz指明文件多少字节应该装入内存,p_memsz指明内存段应该是多大。p_flags指明内存段的权限。应该是4(可读),以及1(可执行)。p_align指明内存段的对齐需求,当重定位有position-independent代码的时候需要,所以目前我们也用不着。

有了以上基础,新的代码出炉:

ITS 32
  
                org     0x00200000
  
                db      0x7F, "ELF"             ; e_ident
                db      1, 1, 1, 0, 0
  _start:
                mov     bl, 42
                xor     eax, eax
                inc     eax
                int     0x80
                dw      2                       ; e_type
                dw      3                       ; e_machine
                dd      1                       ; e_version
                dd      _start                  ; e_entry
                dd      phdr - $$               ; e_phoff
  phdr:         dd      1                       ; e_shoff       ; p_type
                dd      0                       ; e_flags       ; p_offset
                dd      $$                      ; e_ehsize      ; p_vaddr
                                                ; e_phentsize
                dw      1                       ; e_phnum       ; p_paddr
                dw      0                       ; e_shentsize
                dd      filesize                ; e_shnum       ; p_filesz
                                                ; e_shstrndx
                dd      filesize                                ; p_memsz
                dd      5                                       ; p_flags
                dd      0x1000                                  ; p_align
  
  filesize      equ     $ - $$

两部分重叠了20个字节,并且重叠的相当好。e_phnum域需要,而p_paddr忽略;e_phentsize域和p_vadress相同。. These are made to match up by selecting a non-standard load address for our program, with a top half equal to 0x0020.

编译运行查大小:

$ nasm -f bin -o a.out tiny.asm
  $ chmod +x a.out
  $ ./a.out ; echo $?
  42
  $ wc -c a.out
       64 a.out

接下来的问题是,我们能不能让program header table完全在ELF header中的?

直接上移很明显会有问题;但是我们注意到p_memsz指示的是分配的内存空间,它有最小需求(p_filesz),但是没上限。同时,可执行的bit位可以被覆盖。这样,新的代码是:

; tiny.asm
  
  BITS 32
  
                org     0x00010000
  
                db      0x7F, "ELF"             ; e_ident
                dd      1                                       ; p_type
                dd      0                                       ; p_offset
                dd      $$                                      ; p_vaddr
                dw      2                       ; e_type        ; p_paddr
                dw      3                       ; e_machine
                dd      _start                  ; e_version     ; p_filesz
                dd      _start                  ; e_entry       ; p_memsz
                dd      4                       ; e_phoff       ; p_flags
  _start:
                mov     bl, 42                  ; e_shoff       ; p_align
                xor     eax, eax
                inc     eax                     ; e_flags
                int     0x80
                db      0
                dw      0x34                    ; e_ehsize
                dw      0x20                    ; e_phentsize
                dw      1                       ; e_phnum
                dw      0                       ; e_shentsize
                dw      0                       ; e_shnum
                dw      0                       ; e_shstrndx
  
  filesize      equ     $ - $$

p_filesz发生了变化:为了保证对齐,p_filesz和p_memsz同样大小,和真实情况相比大了不少,但是无伤大雅。

大小为52字节。

目前文件的大小就只是ELF header的大小了,最后的一个小把戏是Linux会把ELF头部缺省位补0,我们可以把它们省去。
; tiny.asm
  
  BITS 32
  
                org     0x00010000
  
                db      0x7F, "ELF"             ; e_ident
                dd      1                                       ; p_type
                dd      0                                       ; p_offset
                dd      $$                                      ; p_vaddr
                dw      2                       ; e_type        ; p_paddr
                dw      3                       ; e_machine
                dd      _start                  ; e_version     ; p_filesz
                dd      _start                  ; e_entry       ; p_memsz
                dd      4                       ; e_phoff       ; p_flags
  _start:
                mov     bl, 42                  ; e_shoff       ; p_align
                xor     eax, eax
                inc     eax                     ; e_flags
                int     0x80
                db      0
                dw      0x34                    ; e_ehsize
                dw      0x20                    ; e_phentsize
                db      1                       ; e_phnum
                                                ; e_shentsize
                                                ; e_shnum
                                                ; e_shstrndx
  
  filesize      equ     $ - $$

45字节!

啊,题目都错了!是45字节~~


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 6
支持
分享
最新回复 (3)
雪    币: 47147
活跃值: (20450)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
感谢分享,目前你己是正式会员。
2012-9-25 14:26
0
雪    币: 68
活跃值: (53)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
多谢坛主!
2012-9-25 15:03
0
雪    币: 222
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
楼主,你好,
其实52字节才是正确的,详情可以看  libelf  里面结构体Elf32_Ehdr的定义。
2018-3-22 20:16
0
游客
登录 | 注册 方可回帖
返回
//