您的位置:首页 > 运维架构 > Linux

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

2010-10-03 17:01 288 查看
p { margin-bottom: 0.21cm; }
如果你现在就停止阅读

ELF

规范,那么你本可以发现另一些规则的:

1)ELF

文件的不同块可以位于文件中的任何位置,除了

ELF
header

必须位于文件最开始部分,所以可以把一些部分进行重叠;

2)header

里的一些字段并没有真正被使用。

具体地说,我正在想

header

中的

16

字节长的标识符字段尾部的一串零。它们只是纯粹的填充,旨在为

ELF

规范将来的扩展预留空间。所以操作系统并不关心那个地方有什么信息。我们已经把任何东西都加载到内存中,我们的程序代码只有

7

字节。。。

那么我们能把程序代码放到

ELF
header

里去吗?

;
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

我们是否可以对

program
header table

做同样的事情呢?它和

ELF
header

重叠可能吗?

ELF
header

的最后

8

字节和

program
header table

的最开始的

8

字节完全是一样的。

那么:

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

And
sure enough, Linux doesn't mind our parsimony one bit:

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

代表

intel
386)

,或者它仅仅是假设就是这样?

事实上,

Linux

确实会去检测字段

e_machine

。但是,有相当数量的其他字段被默默地忽略了。

接下来就是

ELF
header

中关键的和非关键的字段。第一个四字节必须包含魔数,否则

Linux

不会去识别这个文件。在字段

e_ident

里的其他三字节不会被检测,那意味着我们有不少于

12

字节的连续空间可以填充任何信息。

e_type

必须被设置成

2

,来标示其为可执行文件,

e_machine

必须为

3



e_version

就像是

e_ident

中的版本号,完全忽略掉了。这也可以理解,目前只有唯一一个

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

中表项的长度。这个字段在比较老的内核中没有用,但是现在它必须被正确地设置。

ELF
header

里的其他信息是关于

section
header table

,它们对于可执行文件不起作用。

现在让我们来考虑下

program
header
table

表项。字段

p_type

必须包含数字

1

,来标示它为一个可加载段,

p_offset

也需要有正确的文件偏移来开始加载。同样地,

p_vaddr

需要包含合适的加载地址。注意我们没有被要求必须加载到内存映像

0x08048000

处。任何位于

0x00000000



0x80000000

之间的页对齐的地址都可以。

p_paddr

被忽略,所以是肯定可以自由使用的。

p_filesz

标示文件中的多少字节要被加载到内存中,

p_memsz

标示内存段需要有多大,

p_flags

标示内存段的权限设置,它必须是

readable(4)

,否则,它就没有用,它也必须是

executable(1)

,否则就不能被执行。其他位也可以被设置,但是必须至少包含这两位。最后,

p_align

给出了内存段的位对齐需求。这个字段主要用于重定位包含

pic

的段时,所以对于可执行文件

Linux

会忽略我们存储在这里的任何垃圾。

总结起来,有点余地发挥。特别是,仔细观察后发现

ELF
header

中的大部分必须字段在前半部,后半部可以自由挥霍。基于这点考虑,我们能重叠那两个结构再多一点点:

;
tiny.asm

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

正如你现在看到的,

program
header table

的头

20

字节和

ELF
header

的最后

20

字节重叠了。在

ELF
header

中有两部分需要注意,第一个是

e_phnum

字段,它刚好和

p_paddr

字段相同,然而在

program
header
table

中它被明确忽略了。另一个是

e_phentsize

字段,它和

p_vaddr

字段的头半截相同。者可以通过选择一个非标注你的加载地址,只要其头半截为

0x0020

就可以了。

$
nasm -f bin -o a.out tiny.asm

$
chmod +x a.out

$
./a.out ; echo $?

42

$
wc -c a.out

64
a.out

确实能行!正如所预期的,又少了

12

字节!

我们注意到

p_memsz

标示为内存段分配的内存量,显然它至少要跟

p_filesz

一样大,但是如果它更大些也无妨。毕竟我们申请多少并不意味着我们必须使用多少。

另外,与我们的期望相反,

executable

位能够从

p_flags

字段中去掉。看起来

readable



executable

是冗余的:两者都可以代表另一个。

想到这两点,我们可以重新组织文件:

;
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
_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_flags



5

变到

4

,与

e_phoff

的值相同,他标示了

program
header table

在文件中的偏移值。程序已经被移到

ELF
header

的更低的部分,开始与

e_shoff

字段,结束于

e_flags

字段。

加载地址也被改变到一个更低的值,这能保证

e_entry

字段中的值足够小,这很好因为它也是

p_memsz



p_filesz

的改变需要解释下。因为我们没有设置

p_flags

字段中的

write

位,

Linux

不允许我们定义

p_memsz



p_filesz

大,因为它不能零初始化那些额外的字节因为它们不可写。既然我们在保证

program
header
table

对齐的条件下不能改变

p_flags

字段的值,你可能会想唯一的解决方案就是把

p_memsz

下移至与

p_filesz

相等。尽管如此,还有另一种解决方案,增加

p_filesz

使之等于

p_memsz

。那意味这它俩都比实际文件大的多,但是它能让加载器避免写只读内存,这是它所关注的。

$
nasm -f bin -o a.out tiny.asm

$
chmod +x a.out

$
./a.out ; echo $?

42

$
wc -c a.out

52
a.out

看起来就算文件的长度并不是整个

ELF
header

的长度,

Linux

仍然能玩得转,并用零填充缺失的字节。我们有不少于

7

个零在文件末尾,所以我们可以把它们从文件映像中去除。

;
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
_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 $ - $$

...
we can, incredibly enough, still produce a working executable:

$
nasm -f bin -o a.out tiny.asm

$
chmod +x a.out

$
./a.out ; echo $?

42

$
wc -c a.out

45
a.out

最后

45

字节的文件比最小的用标准工具创建的

ELF

可执行文件大小的八分之一还要小,比最小的用纯

C

代码编写的文件的十五分之一还要小。当然,文件里一半的值都违反了

ELF

标准。

另一方面,这个可执行文件里的每一个字节都是有意义的并且是正当的。有多少你最近创建的可执行文件能够这么说?
http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: