创建Linux下可运行的超小型ELF可执行文件(4)
来源:互联网 发布:java 18.3 编辑:程序博客网 时间:2024/05/16 19:02
p { margin-bottom: 0.21cm; }
如果你现在就停止阅读ELF规范,那么你本可以发现另一些规则的:1)ELF文件的不同块可以位于文件中的任何位置,除了ELFheader必须位于文件最开始部分,所以可以把一些部分进行重叠;2)header里的一些字段并没有真正被使用。
具体地说,我正在想header中的16字节长的标识符字段尾部的一串零。它们只是纯粹的填充,旨在为ELF规范将来的扩展预留空间。所以操作系统并不关心那个地方有什么信息。我们已经把任何东西都加载到内存中,我们的程序代码只有7字节。。。
那么我们能把程序代码放到ELFheader里去吗?
;tiny.asm
BITS32
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
84a.out
我们是否可以对programheader table做同样的事情呢?它和ELFheader重叠可能吗?
ELFheader的最后8字节和programheader table的最开始的8字节完全是一样的。
那么:
;tiny.asm
BITS32
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 $ - $$
Andsure 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
76a.out
重叠这个方法已经用到头了,除非我门能够改变结构的内容来匹配更多的字段。。。
到底Linux会检查多少这里面的字段呢?比如,Linux真会去看字段e_machine是否包含3(代表intel386),或者它仅仅是假设就是这样?
事实上,Linux确实会去检测字段e_machine。但是,有相当数量的其他字段被默默地忽略了。
接下来就是ELFheader中关键的和非关键的字段。第一个四字节必须包含魔数,否则Linux不会去识别这个文件。在字段e_ident里的其他三字节不会被检测,那意味着我们有不少于12字节的连续空间可以填充任何信息。e_type必须被设置成2,来标示其为可执行文件,e_machine必须为3,e_version就像是e_ident中的版本号,完全忽略掉了。这也可以理解,目前只有唯一一个ELF标准版本。e_entry也必须是有效的,因为它指向程序的开始。很显然,e_phoff必须包含programheader table在文件中的正确的偏移,e_phnum必须包含programheadertable中的正确的项数,e_flags据记载现在没有为intel使用,所以我们可以自由地使用它。e_ehsize用于验证ELFheader有期待的长度,但是Linux并没有用它。e_phentsize用于验证programheader table中表项的长度。这个字段在比较老的内核中没有用,但是现在它必须被正确地设置。ELFheader里的其他信息是关于sectionheader table,它们对于可执行文件不起作用。
现在让我们来考虑下programheadertable表项。字段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会忽略我们存储在这里的任何垃圾。
总结起来,有点余地发挥。特别是,仔细观察后发现ELFheader中的大部分必须字段在前半部,后半部可以自由挥霍。基于这点考虑,我们能重叠那两个结构再多一点点:
;tiny.asm
BITS32
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 $ - $$
正如你现在看到的,programheader table的头20字节和ELFheader的最后20字节重叠了。在ELFheader中有两部分需要注意,第一个是e_phnum字段,它刚好和p_paddr字段相同,然而在programheadertable中它被明确忽略了。另一个是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
64a.out
确实能行!正如所预期的,又少了12字节!
我们注意到p_memsz标示为内存段分配的内存量,显然它至少要跟p_filesz一样大,但是如果它更大些也无妨。毕竟我们申请多少并不意味着我们必须使用多少。
另外,与我们的期望相反,executable位能够从p_flags字段中去掉。看起来readable和executable是冗余的:两者都可以代表另一个。
想到这两点,我们可以重新组织文件:
;tiny.asm
BITS32
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的值相同,他标示了programheader table在文件中的偏移值。程序已经被移到ELFheader的更低的部分,开始与e_shoff字段,结束于e_flags字段。
加载地址也被改变到一个更低的值,这能保证e_entry字段中的值足够小,这很好因为它也是p_memsz。p_filesz的改变需要解释下。因为我们没有设置p_flags字段中的write位,Linux不允许我们定义p_memsz比p_filesz大,因为它不能零初始化那些额外的字节因为它们不可写。既然我们在保证programheadertable对齐的条件下不能改变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
52a.out
看起来就算文件的长度并不是整个ELFheader的长度,Linux仍然能玩得转,并用零填充缺失的字节。我们有不少于7个零在文件末尾,所以我们可以把它们从文件映像中去除。
;tiny.asm
BITS32
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
45a.out
最后45字节的文件比最小的用标准工具创建的ELF可执行文件大小的八分之一还要小,比最小的用纯C代码编写的文件的十五分之一还要小。当然,文件里一半的值都违反了ELF标准。
另一方面,这个可执行文件里的每一个字节都是有意义的并且是正当的。有多少你最近创建的可执行文件能够这么说?
http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
- 创建Linux下可运行的超小型ELF可执行文件(4)
- 创建Linux下可运行的超小型ELF可执行文件(1)
- 创建Linux下可运行的超小型ELF可执行文件(2)
- 创建Linux下可运行的超小型ELF可执行文件(3)
- 怎样创建真正很小的Linux下的ELF可执行文件
- 怎样创建真正很小的Linux下的ELF可执行文件
- Linux下ELF可执行文件装载与运行
- Linux下的ELF可执行文件学习总结
- 在linux平台上创建超小的ELF可执行文件
- 在linux平台上创建超小的ELF可执行文件
- 在linux平台上创建超小的ELF可执行文件
- 在linux平台上创建超小的ELF可执行文件
- 在Linux平台上创建超小的ELF可执行文件
- Linux下的ELF可执行文件的格式解析
- Linux下的ELF可执行文件的格式解析
- elf,out,coff 三种Linux下可执行文件的历史渊源
- 怎样创建真正很小的Linux下的ELF可执行文件————X86-64 Ubuntu实践
- Linux系统下可执行文件的运行过程
- 谈新手对CString的使用 dai2255(原作)
- 额
- 小红点使用
- 创建Linux下可运行的超小型ELF可执行文件(3)
- rand和srand函数用法
- 创建Linux下可运行的超小型ELF可执行文件(4)
- 宏定义,想说爱你不容易!!
- UCGUI 消息机制实现分析
- JAVA中关于异常需要注意的地方
- 【翠字营原创】 应用程序在读写SQLite数据是否需要自己加锁保障?
- Flex 遍历组件的实现
- java
- 读取Excel
- 数据库设计模式