歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> 關於Unix

在linux平台上創建超小的ELF可執行文件

在linux平台上創建超小的ELF可執行文件 前言: 有些時候,文件的大小是很重要的,從這片文章中,也探討了ELF文件格式內部的工作 情況與 LINUX 的操作系統。該片文章向我們展示了如何構造一個超小的ELF可執行文件。 在linux平台上創建超小的ELF可執行文件 前言 在linux平台上創建超小的ELF可執行文件 前言: 有些時候,文件的大小是很重要的,從這片文章中,也探討了ELF文件格式內部的工作 情況與LINUX的操作系統。該片文章向我們展示了如何構造一個超小的ELF可執行文件。

在linux平台上創建超小的ELF可執行文件 前言: 有些時候,文件的大小是很重要的,從這片文章中,也探討了ELF文件格式內部的工作 情況與LINUX的操作系統。該片文章向我們展示了如何構造一個超小的ELF可執行文件。 文章中給出的這些example都是運行在intel 386體系的LINUX上。其他系統體系上或許也有同樣的 效果,但我不感肯定。 我們的匯編代碼使用的是Nasm寫的,它的風格類似於X86匯編風格。 NASM軟件是免費的,可以從下面得到 http://www.web-sites.co.uk/nasm/ -------------------------------------------------------------------------------- 看看下面一個很小的程序例子,它唯一做的事情就是返回一個數值到操作系統中。 UNIX系統通常返回0和1,這裡我們使用42作為返回值。 [alert7@redhat]# set -o noclobber && cat > tiny.c << EOF /* tiny.c */ int main(void) { return 42; } EOF [alert7@redhat]# gcc -Wall tiny.c [alert7@redhat]# ./a.out ;echo $? 42 再用gdb看看,這個程序實在很簡單吧 [alert7@redhat]# gdb a.out -q (gdb) disass main Dump of assembler code for function main: 0x80483a0

: push %ebp 0x80483a1 : mov %esp,%ebp 0x80483a3 : mov x2a,%eax 0x80483a8 : jmp 0x80483b0 0x80483aa : lea 0x0(%esi),%esi 0x80483b0 : leave 0x80483b1 : ret 看看有多大 [alert7@redhat]# wc -c a.out 11648 a.out 在原作者的機子上3998,在我的rh 2.2.14-5.0上就變成11648,好大啊,我們需要 使它變的更小。 [alert7@redhat]# gcc -Wall -s tiny.c [alert7@redhat]# ./a.out ;echo $? 42 [alert7@redhat]# wc -c a.out 2960 a.out 現在變成2960,小多了. gcc -Wall -s tiny.c實際上等價於 gcc -Wall tiny.c strip a.out 拋棄所有的標號 [alert7@redhat]# wc -c a.out 11648 a.out [alert7@redhat]# strip a.out [alert7@redhat]# wc -c a.out 2960 a.out 下一步,我們來進行優化。 [alert7@redhat]# gcc -Wall -s -O3 tiny.c [alert7@redhat]# wc -c a.out 2944 a.out 我們看到,只比上面的小16個字節,所以以優化指令來減小大小是比較困難的。 很不幸,C程序在編譯的時候編譯器會增加一些額外的代碼,所以接下來我們使用匯編來寫程序。 如上一個程序,我們需要返回代碼為42,我們只需要把eax設置為42就可以了。程序的 返回狀態就是存放在eax中的,從上面一段disass main出來的匯編代碼我們也應該知道。 [alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF ; tiny.asm BITS 32 GLOBAL main SECTION .text main: mov eax, 42 ret EOF 編譯並測試 [alert7@redhat]# nasm -f elf tiny.asm [alert7@redhat]# gcc -Wall -s tiny.o [alert7@redhat]# ./a.out ; echo $? 42 現在看看匯編代碼有什麼不同,看看它的大小 [alert7@redhat]# wc -c a.out 2892 a.out 這樣又減小了(2944-2892)52個字節. 但是,只要我們使用main()接口,就還會有許多額外的代碼。 linker還會為我們加一個到OS的接口。事實上就是調用main().所以我們如何來去掉我們不需要的 代碼呢。 linker默認使用的實際入口是標號_start. gcc聯接時,它會自動包括一個_start的例程,設置argc和argv, ....,最後調用main(). 所以讓我們來看看,是否可以跳過這個,自己定義_start例程。 [alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF ; tiny.asm BITS 32 GLOBAL _start SECTION .text _start: mov eax, 42 ret EOF [alert7@redhat]# nasm -f elf tiny.asm [alert7@redhat]# gcc -Wall -s tiny.o tiny.o: In function `_start': tiny.o(.text+0x0): multiple definition of `_start' /usr/lib/crt1.o(.text+0x0): first defined here /usr/lib/crt1.o: In function `_start': /usr/lib/crt1.o(.text+0x18): undefined reference to `main' collect2: ld returned 1 exit status 如何做才可以編譯過去呢? GCC有一個編譯選項--nostartfiles -nostartfiles 當linking時,不使用標准的啟動文件。但是通常是使用的。 我們要的就是這個,再來: [alert7@redhat]# nasm -f elf tiny.asm [alert7@redhat]# gcc -Wall -s -nostartfiles tiny.o [alert7@redhat]# ./a.out ; echo $? Segmentation fault (core dumped) 139 gcc沒有報錯,但是程序core dump了,到底發生了什麼? 錯就錯在我們把_start看成了一個C的函數,然後試著從它返回。事實上它根本不是一個函數。 它僅僅是一個標號,它是被linker使用的一個程序入口點。當程序運行,它也就直接被調用。 假如我們來看,將看到在堆棧頂部的變量值為1,它的確非常的不象一個地址。事實上,在 堆棧那位置是我們程序的argc變量,之後是argv數組,包含NULL元素,接下來是envp環境變量。 所以,那個根本就不是返回地址。 因此,_start要退出,就要調用exit()函數。 事實上,我們實際調用的_exit()函數,因為exit()函數所要做的額外事情太多了,因為我們跳過了 lib庫的啟動代碼,所以我們也可以跳過LIB庫的shutdown代碼。 好了,再讓我們試試。調用_exit()函數,它唯一的參數就是一個整形。所以我們需要push一個數到 堆棧裡,然後調用_exit(). (應該這樣定義:EXTERN _exit) [alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF ; tiny.asm BITS 32 EXTERN _exit GLOBAL _start SECTION .text _start: push dword 42 call _exit EOF [alert7@redhat]# nasm -f elf tiny.asm [alert7@redhat]# gcc -Wall -s -nostartfiles tiny.o [alert7@redhat]# ./a.out ; echo $? 42 yeah~~,成功了,來看看多大 [alert7@redhat]# wc -c a.out 1312 a.out 不錯不錯,又減少了將近一半,:),有沒有其他所我們感興趣的gcc選項呢? 在-nostartfiles就有一個很另人感興趣的選項: -nostdlib 在linking的時候,不使用標准的LIB和啟動文件。那些東西都需要自己指定傳給linker. 這個值得研究一下: [alert7@redhat]# gcc -Wall -s -nostdlib tiny.o tiny.o: In function `_start': tiny.o(.text+0x6): undefined reference to `_exit' collect2: ld returned 1 exit status _exit()是一個庫函數,但是加了-nostdlib 就不能使用了,所以我們必須自己處理, 首先,必須知道在linux下如何制造一個系統調用。 -------------------------------------------------------------------------------- 象其他操作系統一樣,linux通過系統調用來向程序提供基本的服務。 這包括打開文件,讀寫文件句柄,等等...... LINUX系統調用接口只有一個指令:int 0x80.所有的系統調用都是通過該接口。 為了制造一個系統調用,eax應該包含一個數字(該數字表明了哪個系統調用),其他寄存器 保存著參數。 假如系統調用使用一個參數,那麼參數在ebx中; 假如使用兩個參數,那麼在ebx,ecx中 假如使用三個,四個,五個參數,那麼使用ebx,ecx,esi 從系統調用返回時, eax 將包含了一個返回值。 假如錯誤發生,eax將是一個負值,它的絕對值表示錯誤的類型。 在/usr/include/asm/unistd.h中列出了不同的系統調用。 快速看一下將看到exit的系統調用號為1。它只有一個參數,該值會返回給父進程,該值會 被放到ebx中。 好了,現在又可以開工了:) [alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF ; tiny.asm BITS 32 GLOBAL _start SECTION .text _start: mov eax, 1 mov ebx, 42 int 0x80 EOF [alert7@redhat]# nasm -f elf tiny.asm [alert7@redhat]# gcc -Wall -s -nostdlib tiny.o [alert7@redhat]# ./a.out ; echo $? 42 看看大小 [alert7@redhat]# wc -c a.out 416 a.out 現在可真是tiny,呵呵,那麼還能不能更小呢? 如何使用更短的指令呢? 看看下面兩段匯編代碼: 00000000 B801000000 mov eax, 1 00000005 BB2A000000 mov ebx, 42 0000000A CD80 int 0x80 00000000 31C0 xor eax, eax 00000002 40 inc eax 00000003 B32A mov bl, 42 00000005 CD80 int 0x80 很明顯從功能上講是等價的,但是下面一個比上面一個節約了5個字節。 使用gcc大概已經不能減少大小了,下面我們就使用linker--ld [alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF ; tiny.asm BITS 32 GLOBAL _start SECTION .text _start: xor eax,eax inc eax mov bl,42 int 0x80 EOF [alert7@redhat]# nasm -f elf tiny.asm [alert7@redhat]# ld -s tiny.o [alert7@redhat]# wc -c a.out 412 a.out 小了4個字節,應該是5個字節的,但是另外的一個字節被用來考慮對齊去了。 是否到達了極限了呢,能否更小? hm.我們的程序代碼現在只有7個字節長。是否ELF文件還有405字節的額外的負載呢 ?他們都是 些什麼? 使用objdump來看看文件的內容: [alert7@redhat]# objdump -x a.out | less a.out: no symbols a.out: file format elf32-i386 a.out architecture: i386, flags 0x00000102: EXEC_P, D_PAGED start address 0x08048080 Program Header: LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12 filesz 0x00000087 memsz 0x00000087 flags r-x ..... 如上,完整的.text節為7個字節大,剛好如我們剛才所說。 但是還有其他的節,例如".comment",誰安排它的呢?".comment"節大小為28個字節。 我們現在不知道.comment節到底是什麼東西,但是可以大膽的說,它是不必須的。 .comment節在文件偏移量為00000087 (16進制) 我們來看看是什麼東西 [alert7@redhat]# objdump -s a.out a.out: file format elf32-i386 Contents of section .text: 8048080 31c040b3 2acd80 1.@.*.. Contents of section .bss: 8049087 00 . Contents of section .comment: 0000 00546865 204e6574 77696465 20417373 .The Netwide Ass 0010 656d626c 65722030 2e393800 embler 0.98. 哦,是nasm自己的一段信息,或許我們應該使用gas....... 假如我們: [alert7@redhat]# set -o noclobber && cat > tiny.s << EOF .globl _start .text _start: xorl %eax, %eax incl %eax movb , %bl int x80 EOF [alert7@redhat]# gcc -s -nostdlib tiny.S [alert7@redhat]# ./a.out ; echo $? 42 [alert7@redhat]# wc -c a.out 368 a.out [譯者注:在作者機子上這裡大小沒有變化,但在我的系統上,這裡變成了368 (跟作者的機子上一樣了),比前面的所以的都要小 ] 再用一下objdump,會有些不同: Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000007 08048074 08048074 00000074 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000000 0804907c 0804907c 0000007c 2**2 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000000 0804907c 0804907c 0000007c 2**2 ALLOC 沒有了com.net節,但是多了兩個無用的節,用來存儲不存在的數據。而且那些節居然還是0長度。 他們使文件大小變大。 所以它們都是沒有用的,我們如何來去掉它們呢? 我們需要准備一些elf文件格式的知識。雖然我也已經翻譯過《ELF文件格式》 , 在http://www.xfocus.org/上可以找到,但是翻譯的很垃圾,早已招人唾罵過了, 所以還是推薦大家看英文原版文檔,而且是強烈推薦。 -------------------------------------------------------------------------------- elf文件格式英文文檔下載地址: ftp://tsx.mit.edu/pub/linux/packages/GCC/ELF.doc.tar.gz. 或者 http://www.muppetlabs.com/~breadbox/software/ELF.txt. 基本的,我們需要知道如下知識: 每一個elf文件都是以一個ELF header的結構開始的。該結構為52個字節長,並且包含了一個 信息部分,這些信息部分描述了文件的內容。例如,前16個字節包含了一個“標識符”,它 包含了ELF文件的魔術數,但字節的標記表明是32位的還是64位的,小端序還是大端序,等等。 在elf header包含的其他的信息還有,例如:目標體系;ELF文件是否是可執行的還是OBJECT 文件還是一個共享的庫;程序的開始地址;program header table和section header table 在文件的偏移量。 兩個表可以出先在文件的任何地方, 但是以前經常是直接跟在ELF HEADER後面,後來出現在 文件的末尾或許是靠近末尾。兩個表有相試的功能,都是為了甄別文件的組成。但是, section header table更關注的是識別在程序中不同部分在什麼地方,然而,program header table描述的是哪裡和如何把那些部分轉載到內存中。 簡單的說,section header table 是被編譯器(compiler)和連接器(linker)使用,program header table是被程序轉載器(loader)使用。對object 文件,program header talbe是 可選的,實際上從來也沒有出現過。同樣的,對於可執行文件來說,section header table 也是可選的,但是它卻總是存在於可執行文件中。 因此,對於我們的程序來說,seciton header table是完全沒有用的,那些sections也不會 影響到程序內存的映象。 那麼,到底如何去掉它們呢? 我們必須自己來構造程序的ELF HEADER. 你也可以查看ELF文檔和/usr/include/linux/elf.h得到相關信息,一個空的ELF可執行文件應該 象如下: BITS 32 org 0x08048000 ehdr: ; Elf32_Ehdr db 0x7F, "ELF", 1, 1, 1 ; e_ident times 9 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 header ,沒有section header table ,一個program header table 包含了 一個入口。該入口指示程序轉載器把完整的文件裝載到內存(一般的是包含自己的ELF header 和 program header table)開始地址為0x08048000(這是可執行文件裝載的默認地址)的地方,並且 開始執行_start處代碼,_start緊跟著program header table.沒有.data段,沒有.bss段 沒有.comment段。 好了,現在我們的程序就變成這樣了: [alert7@redhat]# cat tiny.asm ; tiny.asm org 0x08048000 ehdr: ; Elf32_Ehdr db 0x7F, "ELF", 1, 1, 1 ; e_ident times 9 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: mov bl, 42 xor eax, eax inc eax int 0x80 filesize equ $ - $$ [alert7@redhat]# nasm -f bin -o a.out tiny.asm [alert7@redhat]# chmod +x a.out [alert7@redhat]# ./a.out ; echo $? 42 再看看大小: [alert7@redhat]# wc -c a.out 93 a.out 真是奇跡,才93個字節大小了。 假如我們明白在可執行文件中的每個字節,我們或許還可以更小,也許很是極限了哦:) -------------------------------------------------------------------------------- 你可能已經注意到了: 1)ELF文件的不同部分允許被定位在任何地方(除了ELF header,它必須放在文件的開始), 並且它們可以交疊。 2)事實上一些字段到目前還沒有被用到。 在鑒別文件字段最後有9個字節為0,我們的代碼只有7個字節長,所以我們試圖把代碼放入 鑒別文件字段最後9個字節中,還有2個剩余。.... [alert7@redhat]# cat tiny.asm ; tiny.asm BITS 32 org 0x08048000 ehdr: ; Elf32_Ehdr db 0x7F, "ELF" ; e_ident db 1, 1, 1, 0 _start: mov bl, 42 xor eax, eax inc eax int 0x80 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 filesize equ $ - $$ [alert7@redhat]# nasm -f bin -o a.out tiny.asm [alert7@redhat]# chmod +x a.out [alert7@redhat]# ./a.out ; echo $? 42 [alert7@redhat]# wc -c a.out 84 a.out 現在我們的程序只有一個elf header和一個program header table入口,為了裝載和運行程序, 這些是我們必要的。所以現在我們不能減少了!除非.... 我們使elf header和program header table一部分重合或者說是交疊,有沒有可能呢? 答案當然是有的,注意我們的程序,就會注意到在elf header最後8個字節和program header table 前8個字節是一樣的,所以... [alert7@redhat]# cat tiny.asm ; tiny.asm BITS 32 org 0x08048000 ehdr: db 0x7F, "ELF" ; e_ident db 1, 1, 1, 0 _start : mov bl, 42 xor eax, eax inc eax int 0x80 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 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 $ - $$ [alert7@redhat]# nasm -f bin -o a.out tiny.asm [alert7@redhat]# chmod +x a.out [alert7@redhat]# ./a.out ; echo $? 42 [alert7@redhat]# wc -c a.out 76 a.out 現在已經不能夠再更多的重疊那兩個結構了,因為兩個結構的字節沒有再相同的了。 但是,我們可以再構造這兩個結構,使它們有更多的相同部分。 到底linux會檢查多少字段呢?例如,它會檢查e_machine字段嗎? 事實上很另人驚訝,一些字段居然被默默的忽略了。 因此:哪些東西才是ELF header中最重要的呢?最前的四個字節當然是的,它包含了一個 魔術數,否則linux不會繼續處理它。在e_ident字段的其他3個字節不被檢查,那就意味著 我們有不少於12個連續的字節我們可以設置為任意的值。e_type必須被設置為2(用來表明 是個可執行文件),e_machine必須為3。就象e_ident中的版本號一樣,e_version被完全的 忽略。(這樣做可以理解,因為現在只有一個版本的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入口的 大小。但是只有在2.2.17以後的2.2系列內核中這個字段才是被檢查的。早於2.2的和2.4.0的 內核是忽略它的。 program header table又是如何呢? p_type必須是1(即PT_LOAD),表明這是個可載入的段。p_offset是開始裝載的文件偏移量。 同樣的,p_vaddr是正確的裝載地址。注意:我們沒有要求把它裝載到0x08048000. 可用的地址為0-0x80000000,並且要頁對齊。文檔上說p_paddr被忽略,因此這個字段更是可 用的。p_filesz 指示了從文件中裝載到內存中有多少字節,p_memsz指示了需要多大的內存段。 因此,他們的值應該是相關的。p_flags指示了給於內存段什麼權限。可設置讀,寫,執行, 其他位也可能被設置,但是我們只需要最小權限。最後,p_align給出了對齊需求。該字段主要 使用在當重定位段包含了與位置無關的代碼時,豈今為止,可執行文件將被LINUX忽略這個字段。 根據分析,我們從中可以看出一些必要的字段,一些無用的字段,這樣,我們就可以重疊更多的 字數了。 [alert7@redhat]# cat tiny.asm ; tiny.asm◆ BITS 32 org 0x00200000 db 0x7F, "ELF" ; e_ident db 1, 1, 1, 0 _start: mov bl, 42 xor eax, eax inc eax int 0x80 db 0 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的前12個字節重疊在ELF header的最後12個字節裡。 相當的吻合。ELF header重復中只有兩部分會有麻煩。一是e_phnum字段,相對應的是p_paddr 是會被忽略。第二個是e_phentsize字段,它和p_vaddr前兩個字節相一致,為了這個相一致, 使用了非標准的加載地址0x00200000,那麼前面的兩個字節就是0x0020. [alert7@redhat]# nasm -f bin -o a.out tiny.asm [alert7@redhat]# chmod +x a.out [alert7@redhat]# ./a.out ; echo $? 42 [alert7@redhat]# wc -c a.out 64 a.out well,現在大小為64字節了 如果我們使 program header table完全放在ELF header中,那麼,呵呵,大小就可以更小了, 但是這樣做行嗎? 是的,是可能的。使program header table從第四個字節就開始,精心構造可執行的ELF文件。 我們注意到: 第一P_memsz指出了為內存段分配多少內存。明顯的,它必須至少跟P_filesz一樣大, 當然更大是沒有關系的。 第二, 可執行位可以從p_flags字段中丟棄,linux會為我們設置它的。為什麼這樣會工作呢? 作者說不知道,又猜測了原因說是否因為入口指針指向了該段? [★譯者注: 但我知道,linux根本就沒有為我們設置p_flags字段中的可執行位,可以工作, 只是因為Intel體系上根本就不具有執行保護功能,就是這個原因,才使得有人有 必要設計了類似堆棧不可運行的內核補丁程序。 ] [alert7@redhat]# cat tiny.asm ; 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 filesize ; 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,這個4也是e_phoff字段的值,它給出了program header table在文件中 的偏移量。代碼被放在從e_shoff 開始到e_flags內部結束。 注意到:裝載地址被改變了更低了。只是為了保持e_entry的值到一個比較合適的小值,它剛好 也是P_mensz的數值。 [alert7@redhat]# nasm -f bin -o a.out tiny.asm [alert7@redhat]# chmod +x a.out [alert7@redhat]# ./a.out ; echo $? 42 [alert7@redhat]# wc -c a.out 52 a.out 現在,程序代碼本身和program header table完全嵌入了ELF header,我們的可執行文件現在和 elf header一樣大。而且可以正常運行。 最後,我們不禁還要問,是否到達了最小的極限呢?畢竟,我們需要一個完整的ELF header,否則 linux不會給我們運行的機會。 真的是這樣嗎 ? 錯了,我們還可以運用最後一招卑鄙的哄騙技術了。 如果文件大小還沒有整個ELF header大的話,linux還是會運行它的。並且把那些少的字節填充為 0。我們在文件的最後有不少於7個0,可以丟棄。 [alert7@redhat]# cat tiny.asm ; 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 filesize ; 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 $ - $$ [alert7@redhat]# nasm -f bin -o a.out tiny.asm [alert7@redhat]# chmod +x a.out [alert7@redhat]# ./a.out ; echo $? 42 [alert7@redhat]# wc -c a.out 45 a.out 討論到此,一個elf可執行文件最小大小為45 bytes,我們被迫終止我們的討論了。 -------------------------------------------------------------------------------- 一個45字節大小的文件比一個用標准工具創建的最小可執行文件的1/8還要小,比用純C代碼 創建的1/50還要小。 這片文章中的一半ELF字段變量違反了標准的ELF規范, 以上程序中打上◆ 的程序,會使readelf core dump [alert7@redhat]# readelf -a a.out ELF Header: Magic: 7f 45 4c 46 01 01 01 00 b3 2a 31 c0 40 cd 80 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 179 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x200008 Start of program headers: 32 (bytes into file) Start of section headers: 1 (bytes into file) Flags: 0x0 Size of this header: 0 (bytes) Size of program headers: 32 (bytes) Number of program headers: 1 Size of section headers: 0 (bytes) Number of section headers: 64 Section header string table index: 0 readelf: Error: Unable to read in 0 bytes of section headers Program Header: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x00200000 0x00000001 0x00040 0x00040 R E 0x1000 There is no dynamic segment in this file. Segmentation fault (core dumped) 呵呵,居然出現了可愛的core dumped [alert7@redhat]# ls -l /usr/bin/readelf -rwxr-xr-x 1 root root 132368 Feb 5 2000 /usr/bin/readelf 不是帶s位的,也就懶的去看它到底哪裡出問題了。 創建的這種超小的elf文件的確比較畸形,連objdump都不能dump它們了。 [alert7@redhat]# objdump -a a.out objdump: a.out: File format not recognized

Copyright © Linux教程網 All Rights Reserved