创建Linux下可运行的超小型ELF可执行文件(3)

来源:互联网 发布:java 18.3 编辑:程序博客网 时间:2024/05/16 16:05

p { margin-bottom: 0.21cm; }

我们的程序本身只有7字节,难道ELF真的需要361字节的额外空间吗?

我们用objdump来看一下文件内容:

$objdump -x a.out | less

让我们看看块列表:

Sections:

IdxName Size VMA LMA File off Algn

0.text 00000007 08048080 08048080 00000080 2**4

CONTENTS,ALLOC, LOAD, READONLY, CODE

1.comment 0000001c 00000000 00000000 00000087 2**0

CONTENTS,READONLY

完整的.text节显示是7字节,这说明完全控制我们程序的机器码很安全。

但是.comment节是做什么用的呢?它有28字节!我们并不确定.comment节是做什么用的,但是看起来它并不是必要的代码。。。

我们可以看看.comment节到底存储了什么内容,在文件偏移0x00000087处,使用hexdump查看:

00000080:31C0 40B3 2ACD 8000 5468 6520 4E65 7477 1.@.*...The Netw

00000090:6964 6520 4173 7365 6D62 6C65 7220 302E ide Assembler 0.

000000A0:3938 0000 2E73 796D 7461 6200 2E73 7472 98...symtab..str

谁能想到nasm会暗中做这些事?也许我们该换用gasAT&T

;tiny.s

.globl_start

.text

_start:

xorl%eax, %eax

incl%eax

movb$42, %bl

int$0x80

 

$gcc -s -nostdlib tiny.s

$./a.out ; echo $?

42

$wc -c a.out

368a.out

一样的结果!

但是,通过objdump可以发现文件内容不同了:

Sections:

IdxName Size VMA LMA File off Algn

0.text 00000007 08048074 08048074 00000074 2**2

CONTENTS,ALLOC, LOAD, READONLY, CODE

1.data 00000000 0804907c 0804907c 0000007c 2**2

CONTENTS,ALLOC, LOAD, DATA

2.bss 00000000 0804907c 0804907c 0000007c 2**2

ALLOC

又多了两节,虽然他们的长度为零,但是仍然导致了额外的空间。

对于这些额外消耗,我们怎样避免呢?

要回答这个问题,我们需要理解ELF文件格式。

关于intel-386架构下ELF文件格式的正式描述可以参考http://refspecs.freestandards.org/elf/elf.pdf。你也可以参考普通文本格式的1.0http://www.muppetlabs.com/~breadbox/software/ELF.txt。这些规范覆盖了很多方面,所以如果你不想自己读完全部的文档,我也可以理解。基本上,我们需要懂得如下内容就可以了:

 

每一个ELF文件都是以一个叫做ELFheader的结构体开始的。这个结构体有52字节长,它包含一些描述文件内容的信息。比如,第一个16字节包含文件的魔数签名(7F45 4C 46),还有1字节的标志位表明是32位还是64位,大尾还是小尾编码,等等。ELFheader里的其他字段包含信息比如目标处理器架构,文件类型喂可执行,目标文件还是共享目标文件,程序的起始地址,programheader tablesectionheader table的位置。

 

Programheader tablesectionheader table能够出现在文件的任何位置,但是前者一般紧跟在ELFheader之后,后者一般出现在文件末尾或者接近末尾的地方。这两个表完成相似的目的,都是为了标示文件中的组件块。尽管如此,Sectionheader table倾向于标示程序中各种块在文件中的位置,而programheader table描述这些块怎样被加载到内存中的什么位置。简单地讲,sectionheader table对编译器和链接器有用,而programheader table对程序加载器有用。Programheader table对于目标文件来说是可选的,在实际应用中从来不出现。同样地,sectionheader table对于可执行文件是可选的,但是在实际应用中总是出现。

 

那么,这就是我们第一个问题的答案。程序中的一些过度的空间消耗用于完全没必要的sectionheader table,和一些同样对程序的内存映像毫无帮助的没有用的块。

 

接下来,我们转向第二个问题:怎样除掉这些无用块呢?

 

没有标准的工具用于制作不包含sectionheader table之类的可执行文件。如果想做这样的事,那只能靠我们自己了。

 

但是那并不意味着我们必须打开二进制编辑器手写十六进制代码。nasm有一种普通二进制文本输出格式,那正好适合我们用。我们所需要的全部信息就是一个空ELF可执行文件的内存映像,然后把我们的程序填进去。

 

我们能够看ELF规范,和/usr/include/linux/elf.h,并参考标准工具创建的可执行文件,来推断一个空的ELF可执行文件应该是什么样。但是,如果你不是很有耐心,那么可以用下面提供的:

BITS32

 

org 0x08048000

 

ehdr: ; Elf32_Ehdr

db 0x7F, "ELF", 1, 1, 1, 0 ; e_ident

times8 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 $ - $$

这个映像包含一个ELFheader,标示文件是intel386可执行文件,没有sectionheaderprogramheader table包含一个元素。前文中讲过programheader引导程序加载器把整个文件加载到内存映像中地址0x08048000处,这是默认的可执行文件的加载地址。接着就开始执行_start处的代码,它紧随programheader table之后。没有.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

91a.out

 

原创粉丝点击