[翻译]42字节可执行文件;ELF介绍;求Kx(四)
发表于:
2012-9-23 20:57
7454
[翻译]42字节可执行文件;ELF介绍;求Kx(四)
正经描述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直播授课