灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:2382回复:0

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

楼主#
更多 发布于:2012-09-10 18:53

注:这里的最小是指我能做到的
最终大小: 142字节
介绍
这篇文章可以算是我在Ubuntu Linux上尝试创建一个最小的x86 ELF二进制Hello World文件的记录,你也可以把它当作一篇指南,我的尝试先是从c开始,然后转向x86汇编,最后以16进制编辑器搞定,但我的最终成果实际上只能打印"Hi World",这纯粹是为了让最终的数字看着更顺眼一些而已,最终的x86 ELF二进制虽然已经被破坏的不成样子,但最重要的是它仍然可以照常运行。
开始

如果你也想跟我一起来试试,那你要做的第一件事就应该是先配置环境:
安装Ubuntu (或随便别的你喜欢的发行版)
运行: sudo apt-get install g++ gcc nasm
查看系统版本
[pre]user@computer:~$ lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription:    Ubuntu 8.04.1Release:        8.04Codename:       hardyuser@computer:~$ uname -aLinux ryanh-desktop 2.6.24-19-generic #1 SMP Wed Jun 18 14:43:41 UTC 2008 i686 GNU/Linuxuser@computer:~$ gcc --versiongcc (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 NOwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.user@computer:~$ nasm -versionNASM version 0.99.06-20071101 compiled on Nov 15 2007[/pre]我的尝试从C开始,下面是我写的C程序,chello.c
view plaincopy to clipboardprint?

#include<STDIO.H>
intmain() {
printf("HiWorldn"); return0;
}

[pre]#include int main(){  printf ("Hi Worldn");  return0;}[/pre]编译:
[pre]user@computer:~$ gcc -o chello chello.cuser@computer:~$ ./chelloHi World[/pre]我最初得到的可执行文件大小为6363字节,你可以使用readelf来查看ELF文件的头信息,命令:
[pre]user@computer:~$ readelf -h chelloELF 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[/pre]ldd也是个很有用的命令,它可以显示这个c程序链接了哪些动态库:
[pre]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)[/pre]file命令可以告诉你这个文件的基本信息
[pre]user@computer:~$ file chellochello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.8, dynamically linked (uses shared libs), not stripped[/pre]我们看到file命令返回了"not stripped",这就是说这个二进制包含了用于调试的符号信息,让我们使用strip命令给它做个瘦身:


user@computer:~$strip-schello

[pre]user@computer:~$ strip -s chello[/pre]经过瘦身之后,现在这个二进制的大小变成了2984字节,还是没法接受,是时候做出艰难决定了,于是我放弃了C以及printf,转而使用nasm x86汇编,下面是hello.asm:
[pre]        SECTION .datamsg:    db "Hi World",10len:    equ $-msg    SECTION .text        global mainmain:    mov    edx,len    mov    ecx,msg    mov    ebx,1    mov    eax,4    int    0x80    mov    ebx,0    mov    eax,1    int    0x80[/pre]编译
[pre]user@computer:~$ nasm -f elf hello.asmuser@computer:~$ gcc -o hello hello.o -nostartfiles -nostdlib -nodefaultlibsuser@computer:~$ strip -s hellouser@computer:~$ ./helloHi World[/pre]strip之前是770字节,之后是448字节,但是这个二进制仍然包含一些无用的头部和section信息,现在找个你最顺手的16进制编辑器打开这个二进制,我一般用curses hexeditor和ghex2。

52_3710_6e0e6696104f25c.png[删除]
删掉0xAD后面的内容后,现在大小变成了173字节

52_3710_dba1e8e7a443e2f.png[删除]

52_3710_4c0592c047a84aa.png[删除]
可以看到0x7后面有一块无用空间,所有我们把保存Hi World字符串的数据块从0xA4-0xAC移到了0x7然后把0x86对字符串的从0xA4改为新的地址0x7,最后删除0xA2和0xA3.

52_3710_4bdf9ba6bf15d03.png[删除]
现在文件大小应该变成了164字节,是时候进入最终环节了,剩下的部分我需要做些解释,基本上,我要做的就是不断尝试改变ELF的头部,但是避免出现segfault fault,我加了许多jmp并完全破坏了原本的可执行文件,尽管如此,它还是可以运行的,这里是一些有用的技巧: 在x86汇编中 0xD9D0,也就是nop操作符,如果你需要填充空白时这个指令很有用,另外0xEB后面跟一个有符号字节来完成相对跳转,你真的应该看看intel x86的汇编指令文档 A-M N-Z。
view plaincopy to clipboardprint?

typedefstruct{
unsignedchare_ident[EI_NIDENT]; Elf32_Halfe_type;
Elf32_Halfe_machine; Elf32_worde_version;
Elf32_Addre_entry; Elf32_Offe_phoff;
Elf32_Offe_shoff; Elf32_worde_flags;
Elf32_Halfe_ehsize; Elf32_Halfe_phentsize;
Elf32_Halfe_phnum; Elf32_Halfe_shentsize;
Elf32_Halfe_shnum; Elf32_Halfe_shtrndx;
}Elf32_Ehdr;

[pre]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;[/pre]
52_3710_838465eb3f28f83.gif[删除]
结论
最终大小: 142 字节
helloworld.tar.gz
我确信肯定还有办法让它更小,应该还是可以从头部移掉一些没用的数据,但是我不想花太多时间钻研ELF的头部格式,另一个办法或许是使用a.out格式来代替ELF格式。


喜欢0 评分0
游客

返回顶部