当前位置: 首页 >> 程序设计 >> 在linux平台上创建超小的ELF可执行文件
 

在linux平台上创建超小的ELF可执行文件

作者:      来源:     发表时间:2006-06-07     浏览次数:      字号:    


我们必须自己来构造程序的ELF HEADER.
你也可以查看ELF文档和/usr/include/linux/elf.h得到相关信息,一个空的ELF可执行文件应该
象如下:


  BITS 32
  
                org     0x08048000
  
  ehdr:                                                 ; Elf32_Ehdr
                db      0x7F, "ELF", 1, 1, 1            ;   e_ident
        times 9 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 header ,没有section header table ,一个program header table 包含了
一个入口。该入口指示程序转载器把完整的文件装载到内存(一般的是包含自己的ELF header 和
program header table)开始地址为0x08048000(这是可执行文件装载的默认地址)的地方,并且
开始执行_start处代码,_start紧跟着program header table.没有.data段,没有.bss段
没有.comment段。

好了,现在我们的程序就变成这样了:

[alert7@redhat]# cat  tiny.asm
; tiny.asm
                org     0x08048000

ehdr:                                                 ; Elf32_Ehdr
                db      0x7F, "ELF", 1, 1, 1            ;   e_ident
        times 9 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:
                mov     bl, 42
                xor     eax, eax
                inc     eax
                int     0x80

  filesize      equ     $ - $$


[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42


再看看大小:

[alert7@redhat]# wc -c a.out
     93 a.out


真是奇迹,才93个字节大小了。

假如我们明白在可执行文件中的每个字节,我们或许还可以更小,也许很是极限了哦:)


--------------------------------------------------------------------------------

你可能已经注意到了:
1)ELF文件的不同部分允许被定位在任何地方(除了ELF header,它必须放在文件的开始),
并且它们可以交叠。
2)事实上一些字段到目前还没有被用到。

在鉴别文件字段最后有9个字节为0,我们的代码只有7个字节长,所以我们试图把代码放入
鉴别文件字段最后9个字节中,还有2个剩余。....


[alert7@redhat]# cat  tiny.asm
  ; tiny.asm
  
  BITS 32
  
                org     0x08048000
  
  ehdr:                                                 ; Elf32_Ehdr
                db      0x7F, "ELF"                     ;   e_ident
                db      1, 1, 1, 0
  _start:       mov     bl, 42
                xor     eax, eax
                inc     eax
                int     0x80
        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
  
  filesize      equ     $ - $$


[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
     84 a.out


现在我们的程序只有一个elf header和一个program header table入口,为了装载和运行程序,
这些是我们必要的。所以现在我们不能减少了!除非....


我们使elf header和program header table一部分重合或者说是交叠,有没有可能呢?

答案当然是有的,注意我们的程序,就会注意到在elf header最后8个字节和program header table
前8个字节是一样的,所以...

[alert7@redhat]# cat tiny.asm
  ; tiny.asm
  
  BITS 32
  
                org     0x08048000
  
  ehdr:
                db      0x7F, "ELF"             ; e_ident
                db      1, 1, 1, 0
  _start:       mov     bl, 42
                xor     eax, eax
                inc     eax
                int     0x80
                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
  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     $ - $$


[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
     76 a.out


现在已经不能够再更多的重叠那两个结构了,因为两个结构的字节没有再相同的了。

但是,我们可以再构造这两个结构,使它们有更多的相同部分。

到底linux会检查多少字段呢?例如,它会检查e_machine字段吗?

事实上很另人惊讶,一些字段居然被默默的忽略了。

因此:哪些东西才是ELF header中最重要的呢?最前的四个字节当然是的,它包含了一个
魔术数,否则linux不会继续处理它。在e_ident字段的其他3个字节不被检查,那就意味着
我们有不少于12个连续的字节我们可以设置为任意的值。e_type必须被设置为2(用来表明
是个可执行文件),e_machine必须为3。就象e_ident中的版本号一样,e_version被完全的
忽略。(这样做可以理解,因为现在只有一个版本的ELF标准)。e_entry当然要设置为正确
的值,因为它指向程序的开始。毫无疑问,e_phoff应该是program header table在文件中
的正确偏移量,e_phnum是program header table中所包含的正确的入口数。然而,e_flags
没有被当前的Intel体系使用,所以我们应该可以重新利用。e_ehsize用来校验elf header
所期望的大小,但是LINUX忽略了它。e_phentsize同样的确认program header table入口的
大小。但是只有在2.2.17以后的2.2系列内核中这个字段才是被检查的。早于2.2的和2.4.0的
内核是忽略它的。

program header table又是如何呢?
p_type必须是1(即PT_LOAD),表明这是个可载入的段。p_offset是开始装载的文件偏移量。
同样的,p_vaddr是正确的装载地址。注意:我们没有要求把它装载到0x08048000.
可用的地址为0-0x80000000,并且要页对齐。文档上说p_paddr被忽略,因此这个字段更是可
用的。p_filesz 指示了从文件中装载到内存中有多少字节,p_memsz指示了需要多大的内存段。
因此,他们的值应该是相关的。p_flags指示了给于内存段什么权限。可设置读,写,执行,
其他位也可能被设置,但是我们只需要最小权限。最后,p_align给出了对齐需求。该字段主要
使用在当重定位段包含了与位置无关的代码时,岂今为止,可执行文件将被LINUX忽略这个字段。

根据分析,我们从中可以看出一些必要的字段,一些无用的字段,这样,我们就可以重叠更多的
字数了。

[alert7@redhat]# cat  tiny.asm
  ; tiny.asm◆
  
  BITS 32
  
                org     0x00200000
  
                db      0x7F, "ELF"             ; e_ident
                db      1, 1, 1, 0
  _start:
                mov     bl, 42
                xor     eax, eax
                inc     eax
                int     0x80
                db      0
                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     $ - $$


正如你看到的,program header table的前12个字节重叠在ELF header的最后12个字节里。
相当的吻合。ELF header重复中只有两部分会有麻烦。一是e_phnum字段,相对应的是p_paddr
是会被忽略。第二个是e_phentsize字段,它和p_vaddr前两个字节相一致,为了这个相一致,
使用了非标准的加载地址0x00200000,那么前面的两个字节就是0x0020.

[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
     64 a.out

well,现在大小为64字节了

如果我们使 program header table完全放在ELF header中,那么,呵呵,大小就可以更小了,
但是这样做行吗?

是的,是可能的。使program header table从第四个字节就开始,精心构造可执行的ELF文件。

我们注意到:
第一P_memsz指出了为内存段分配多少内存。明显的,它必须至少跟P_filesz一样大,
当然更大是没有关系的。

第二, 可执行位可以从p_flags字段中丢弃,linux会为我们设置它的。为什么这样会工作呢?
作者说不知道,又猜测了原因说是否因为入口指针指向了该段?

[★译者注:
    但我知道,linux根本就没有为我们设置p_flags字段中的可执行位,可以工作,
    只是因为Intel体系上根本就不具有执行保护功能,就是这个原因,才使得有人有
    必要设计了类似堆栈不可运行的内核补丁程序。
]


[alert7@redhat]# cat  tiny.asm
  ; tiny.asm
  
  BITS 32
  
                org     0x00001000
  
                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      filesize                ; 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_flags字段从5变为4,这个4也是e_phoff字段的值,它给出了program header table在文件中
的偏移量。代码被放在从e_shoff 开始到e_flags内部结束。

注意到:装载地址被改变了更低了。只是为了保持e_entry的值到一个比较合适的小值,它刚好
也是P_mensz的数值。

[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
     52 a.out

现在,程序代码本身和program header table完全嵌入了ELF header,我们的可执行文件现在和
elf header一样大。而且可以正常运行。

最后,我们不禁还要问,是否到达了最小的极限呢?毕竟,我们需要一个完整的ELF header,否则
linux不会给我们运行的机会。

真的是这样吗 ?

错了,我们还可以运用最后一招卑鄙的哄骗技术了。

如果文件大小还没有整个ELF header大的话,linux还是会运行它的。并且把那些少的字节填充为
0。我们在文件的最后有不少于7个0,可以丢弃。


[alert7@redhat]# cat  tiny.asm
  ; tiny.asm
  
  BITS 32
  
                org     0x00001000
  
                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      filesize                ; 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     $ - $$

[alert7@redhat]# nasm -f bin -o a.out tiny.asm
[alert7@redhat]# chmod +x a.out
[alert7@redhat]# ./a.out ; echo $?
42
[alert7@redhat]# wc -c a.out
     45 a.out


讨论到此,一个elf可执行文件最小大小为45 bytes,我们被迫终止我们的讨论了。

--------------------------------------------------------------------------------
一个45字节大小的文件比一个用标准工具创建的最小可执行文件的1/8还要小,比用纯C代码
创建的1/50还要小。

这片文章中的一半ELF字段变量违反了标准的ELF规范,

以上程序中打上◆ 的程序,会使readelf core dump
[alert7@redhat]# readelf -a a.out
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 b3 2a 31 c0 40 cd 80 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       179
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x200008
  Start of program headers:          32 (bytes into file)
  Start of section headers:          1 (bytes into file)
  Flags:                             0x0
  Size of this header:               0 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           0 (bytes)
  Number of section headers:         64
  Section header string table index: 0
readelf: Error: Unable to read in 0 bytes of section headers

Program Header:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00200000 0x00000001 0x00040 0x00040 R E 0x1000

There is no dynamic segment in this file.
Segmentation fault (core dumped)

呵呵,居然出现了可爱的core dumped

[alert7@redhat]# ls -l /usr/bin/readelf
-rwxr-xr-x    1 root     root       132368 Feb  5  2000 /usr/bin/readelf

:(不是带s位的,也就懒的去看它到底哪里出问题了。

创建的这种超小的elf文件的确比较畸形,连objdump都不能dump它们了。
[alert7@redhat]# objdump -a a.out
objdump: a.out: File format not recognized

[1] [2]

编辑 webmaster

 
 
 
评论更多>>
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •