一个最小x86 ELF Hello World程序的诞生

2011-10-20 06:57

一个最小x86 ELF Hello World程序的诞生

by yuanyi

at 2011-10-19 22:57:39

original http://item.feedsky.com/~feedsky/heikezhi/~8608072/577089957/6713895/1/item.html

注:这里的最小是指我能做到的

最终大小: 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 
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 字节

helloworld.tar.gz

我确信肯定还有办法让它更小,应该还是可以从头部移掉一些没用的数据,但是我不想花太多时间钻研ELF的头部格式,另一个办法或许是使用a.out格式来代替ELF格式。

如果你有意见,建议或是批评欢迎给我邮件:henszey#gmail.com

-----------
本文来自:"Smallest x86 ELF Hello World",作者:henszey

想和我们一道传播黑客精神?快来加入吧!

无觅猜您也喜欢:

Hello world!

破解我吧:一次ELF文件的解构之旅

debugCSS: 超简单的CSS/(x)html诊断工具

轻松为任意程序代码添加语法高亮
无觅