注:這裏的最小是指我能做到的
最終大小: 142字節
介紹
這篇文章可以算是我在Ubuntu Linux上嘗試創建一個最小的x86 ELF二進制Hello World文件的記錄,你也可以把它當作一篇指南,我的嘗試先是從c開始,然後轉向x86彙編,最後以16進制編輯器搞定,但我的最終成果實際上只能打印"Hi World",這純粹是爲了讓最終的數字看着更順眼一些而已,最終的x86 ELF二進制雖然已經被破壞的不成樣子,但最重要的是它仍然可以照常運行。
開始
- 如果你也想跟我一起來試試,那你要做的第一件事就應該是先配置環境:
- 安裝Ubuntu (或隨便別的你喜歡的發行版)
- 運行: sudo apt-get install g++ gcc nasm
- 查看系統版本
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
我的嘗試從C開始,下面是我寫的C程序,chello.c
- #include <STDIO.H>
- int main()
- {
- printf ("Hi World\n");
- return0;
- }
編譯:
user@computer:~$ gcc -o chello chello.c user@computer:~$ ./chello Hi World
我最初得到的可執行文件大小爲6363字節,你可以使用readelf來查看ELF文件的頭信息,命令:
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也是個很有用的命令,它可以顯示這個c程序鏈接了哪些動態庫:
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命令可以告訴你這個文件的基本信息
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命令給它做個瘦身:
- user@computer:~$ strip -s chello
經過瘦身之後,現在這個二進制的大小變成了2984字節,還是沒法接受,是時候做出艱難決定了,於是我放棄了C以及printf,轉而使用nasm x86彙編,下面是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
編譯
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。
刪掉0xAD後面的內容後,現在大小變成了173字節
可以看到0x7後面有一塊無用空間,所有我們把保存Hi World字符串的數據塊從0xA4-0xAC移到了0x7然後把0x86對字符串的引用從0xA4改爲新的地址0x7,最後刪除0xA2和0xA3.
現在文件大小應該變成了164字節,是時候進入最終環節了,剩下的部分我需要做些解釋,基本上,我要做的就是不斷嘗試改變ELF的頭部,但是避免出現segfault fault,我加了許多jmp並完全破壞了原本的可執行文件,儘管如此,它還是可以運行的,這裏是一些有用的技巧: 在x86彙編中 0xD9D0,也就是nop操作符,如果你需要填充空白時這個指令很有用,另外0xEB後面跟一個有符號字節來完成相對跳轉,你真的應該看看intel x86的彙編指令文檔A-M N-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;
結論
最終大小: 142 字節
我確信肯定還有辦法讓它更小,應該還是可以從頭部移掉一些沒用的數據,但是我不想花太多時間鑽研ELF的頭部格式,另一個辦法或許是使用a.out格式來代替ELF格式。
如果你有意見,建議或是批評歡迎給我郵件:henszey#gmail.com
-----------
本文來自:"Smallest x86 ELF Hello World",作者:henszey