其实这篇文章没说多少东西,就是帮助我们熟悉ELF文件格式以及熟悉各种工具的使用!
注:这里的最小是指我能做到的
最终大小: 142字节
介绍
This page is a combination tutorial/documentary about my attempts at creating the smallest x86 ELF binary that would execute saying Hello World on Ubuntu Linux. My first attempts started with C then progressed to x86 assembly and finally to a hexeditor. I ended up compromising and switching to a "Hi World" app instead in order to fit the string data into the elf magic number. The final result is a completely corrupted x86 ELF Binary that still runs.
这篇文章可以算是我在Ubuntu Linux上尝试创建一个最小的x86 ELF二进制Hello World文件的记录,你也可以把它当作一篇指南,我的尝试先是从c开始,然后转向x86汇编,最后以16进制编辑器搞定,但我的最终成果实际上只能打印"Hi World",这完全是根据ELF文件头中的魔数(magic number)所占的字节数来决定的,最终的x86 ELF二进制虽然已经被破坏的不成样子,但最重要的是它仍然可以照常运行。
The first thing you need to do is get an a proper environment setup.
user@computer:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 8.04.1
Release: 8.04
Codename: hardy
user@computer:~$ uname -a
Linux ryanh-desktop 2.6.24-19-generic #1 SMP Wed Jun 18 14:43:41 UTC 2008 i686 GNU/Linux
user@computer:~$ gcc --version
gcc (GCC) 4.2.3 (Ubuntu 4.2.3-2ubuntu7)
Copyright (C) 2007 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
user@computer:~$ nasm -version
NASM version 0.99.06-20071101 compiled on Nov 15 2007
My first attempts started with C, the following is what I used for chello.c
我的尝试从C开始,下面是我写的C程序,chello.c
Code: chello.c
#include
int main() {
printf ("Hi World\n");
return 0;
}
Command: gcc
user@computer:~$ gcc -o chello chello.c
user@computer:~$ ./chello
Hi World
My initial executable was
6363 bytes.You can use readelf to dump the ELF header from the executable.
我最初得到的可执行文件大小为6363字节,你可以使用readelf来查看ELF文件的头信息,命令:
Command: readelf
user@computer:~$ readelf -h chello
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x80482f0
Start of program headers: 52 (bytes into file)
Start of section headers: 3220 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 7
Size of section headers: 40 (bytes)
Number of section headers: 36
Section header string table index: 33
ldd is useful for showing all the dynamic libraries an executable is linked to.
ldd也是个很有用的命令,它可以显示这个c程序链接了哪些动态库:
Command: ldd
user@computer:~$ ldd chello
linux-gate.so.1 => (0xb7f77000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e18000)
/lib/ld-linux.so.2 (0xb7f78000)
file命令可以告诉你这个文件的基本信息
Command: file
user@computer:~$ file chello
chello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.8, dynamically linked (uses shared libs), not stripped
我们看到file命令返回了"not stripped",这就是说这个二进制包含了用于调试的符号信息,让我们使用strip命令给它做个瘦身:
Command: strip
user@computer:~$ strip -s chello
经过瘦身之后,现在这个二进制的大小变成了2984字节,还是没法接受,是时候做出艰难决定了,于是我放弃了C以及printf,转而使用nasm x86汇编,下面是hello.asm:
file: hello.asm
SECTION .data
msg: db "Hi World",10
len: equ $-msg
SECTION .text
global main
main:
mov edx,len
mov ecx,msg
mov ebx,1
mov eax,4
int 0x80
mov ebx,0
mov eax,1
int 0x80
Compiling the asm
user@computer:~$ nasm -f elf hello.asm
user@computer:~$ gcc -o hello hello.o -nostartfiles -nostdlib -nodefaultlibs
user@computer:~$ strip -s hello
user@computer:~$ ./hello
Hi World
strip之前是770字节,之后是448字节,但是这个二进制仍然包含一些无用的头部和section信息,现在找个你最顺手的16进制编辑器打开这个二进制,我一般用curses hexeditor和ghex2。
Delete everything including and past offset 0xAD, this will drop it down to 173 bytes删掉0xAD后面的内容后,现在大小变成了173字节
Move 0xA4-0xAC to 0x7 and Change offset 0x86 from 0xA4 to its new location 0x07. Delete 0xA2 and 0xA3可以看到0x7后面有一块无用空间,所有我们把保存Hi World字符串的数据块从0xA4-0xAC移到了0x7然后把0x86对字符串的引用从0xA4改为新的地址0x7,最后删除0xA2和0xA3.
The file should be 164 bytes and now its time to enter the twilight zone... The rest is a lot to explain, basically I attempted to find what I could change in the elf head with out having it segfault on me.I added some jmps and completely corrupted the executable, however it still runs :). Here is some useful information: In x86 0xD9D0 is nop or no operation, useful for just filling space if you need to. 0xEB followed by a single signed byte is a relative jmp. Really you should read the intel docs on x86 instructions A-M N-Z .现在文件大小应该变成了164字节,是时候进入最终环节了,剩下的部分我需要做些解释,基本上,我要做的就是不断尝试改变ELF的头部,但是避免出现segfault fault,我加了许多jmp并完全破坏了原本的可执行文件,尽管如此,它还是可以运行的,这里是一些有用的技巧: 在x86汇编中 0xD9D0,也就是nop操作符,如果你需要填充空白时这个指令很有用,另外0xEB后面跟一个有符号字节来完成相对跳转,你真的应该看看intel x86的汇编指令文档A-MN-Z。
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shtrndx;
} Elf32_Ehdr;
结论
helloworld.tar.gz
I am certain that there are ways to get it even smaller. There may also be more things that can be removed from the header to increase size, but I didn't spend the enough time fully researching the ELF header format. Another option might be to use the a.out format instead of ELF may allow you to get even smaller.
Comments, suggestions, and critical criticism accepted: [email protected]
我确信肯定还有办法让它更小,应该还是可以从头部移掉一些没用的数据,但是我不想花太多时间钻研ELF的头部格式,另一个办法或许是使用a.out格式来代替ELF格式。
如果你有意见,建议或是批评欢迎给我邮件:henszey#gmail.com
参考地址:http://timelessname.com/elfbin/ http://blog.csdn.net/fisher_jiang/article/details/6892036