ELF 是指 Executable and Linkable Foramt ,是 LINUX 下最常用的可执行文件格式。https://en.wikipedia.org/wiki/Executable_and_Linkable_Format 有一个大略的介绍。 man elf 可以得到更详细的介绍。
本文试图只用 echo 的命令,而不使用其他任何命令,构造一个 hello world 的二进制可执行程序。这个程序的意义是
- 深入了解 ELF 的文件格式。
- 了解机器指令
- 了解 linux 系统调用
首先,按照文档构造 ELF 文件的 64个字节的头部。
echo -ne
这里为了简化,只有一个 program header ,而没有 section header 。需要注意的是 e_entry ,这个是程序入口地址,是 0x78 。后面可以看到,0x78 是第一个机器指令的偏移量。
这个 program herader table 起始于文件偏移量 0x40 处,即 e_phoff,也就是说,64 字节的 ELF header 后面紧跟着一个 program header table,而这个表格里面只有一项 program header。我们开始构造 56 字节的 program header 。
echo -ne
首先指定类型是 PT_LOAD ,这样,加载器就会把整个可执行程序映射到一个虚拟内存上。
p_flag = 0x5 指明虚拟内存的属性是只读的,是可执行的。
最后,我们写入机器指令。
echo -ne
这段代码,可以参考 man syscalls, man syscall 。等效的 c 代码就是
sys_call
为了简化,这里没有任何函数调用。生成的可执行程序如下:
% bash hello.sh | xxd
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0300 3e00 0100 0000 7800 0000 0000 0000 ..>.....x.......
00000020: 4000 0000 0000 0000 0000 0000 0000 0000 @...............
00000030: 0000 0000 4000 3800 0100 4000 0000 0000 ....@.8...@.....
00000040: 0100 0000 0500 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: b000 0000 0000 0000 b000 0000 0000 0000 ................
00000070: 0000 2000 0000 0000 ba0d 0000 0048 8d35 .. ..........H.5
00000080: 1e00 0000 48c7 c701 0000 0048 c7c0 0100 ....H......H....
00000090: 0000 0f05 48c7 c74c 0000 00b8 3c00 0000 ....H..L....<...
000000a0: 0f05 6865 6c6c 6f20 776f 726c 6421 0a00 ..hello world!..
总共 176 (0xb0) 个字节,假设这个程序叫做 a.out 。
% bash hello.sh > a.out
% chmod +x a.out
% ./a.out
hello world!
%
这里可以看到 ‘ba 0d’ 在 0x78 的偏移量处,也就是第一条机器指令的位置,所以 e_phentry = 0x78 。
整个可执行程序的长度是 176 (0xb0) 个字节, 所以第一个 program header 中的 filesz 和 memsz 都是 0xb0。
在实际使用中,我们不使用编译器,连接器,而几乎手工构造一个可执行程序,这样做意义不大。通过这个实验,我们可以解密 linux 可执行程序的魔术,让可执行程序看起来不再那么神秘了。