Kid*_*rla 98 linux reverse-engineering
在发现几个常用命令(例如read)实际上是 Bash 内置命令(并且在提示符下运行它们时,我实际上正在运行一个两行的 shell 脚本,它只是转发到内置命令),我想看看是否相同对于true和是真的false。
好吧,它们绝对是二进制文件。
sh-4.2$ which true
/usr/bin/true
sh-4.2$ which false
/usr/bin/false
sh-4.2$ file /usr/bin/true
/usr/bin/true: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=2697339d3c19235
06e10af65aa3120b12295277e, stripped
sh-4.2$ file /usr/bin/false
/usr/bin/false: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=b160fa513fcc13
537d7293f05e40444fe5843640, stripped
sh-4.2$
Run Code Online (Sandbox Code Playgroud)
然而,我发现最令人惊讶的是它们的大小。我希望他们只是每一字节数,因为true基本上是exit 0和false是exit 1。
sh-4.2$ true
sh-4.2$ echo $?
0
sh-4.2$ false
sh-4.2$ echo $?
1
sh-4.2$
Run Code Online (Sandbox Code Playgroud)
然而,令我惊讶的是,这两个文件的大小都超过了 28KB。
sh-4.2$ stat /usr/bin/true
File: '/usr/bin/true'
Size: 28920 Blocks: 64 IO Block: 4096 regular file
Device: fd2ch/64812d Inode: 530320 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2018-01-25 19:46:32.703463708 +0000
Modify: 2016-06-30 09:44:27.000000000 +0100
Change: 2017-12-22 09:43:17.447563336 +0000
Birth: -
sh-4.2$ stat /usr/bin/false
File: '/usr/bin/false'
Size: 28920 Blocks: 64 IO Block: 4096 regular file
Device: fd2ch/64812d Inode: 530697 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2018-01-25 20:06:27.210764704 +0000
Modify: 2016-06-30 09:44:27.000000000 +0100
Change: 2017-12-22 09:43:18.148561245 +0000
Birth: -
sh-4.2$
Run Code Online (Sandbox Code Playgroud)
所以我的问题是:为什么它们这么大?除了返回码之外,可执行文件中还有什么?
PS:我使用的是 RHEL 7.4
Rui*_*iro 148
在过去,/bin/true并且/bin/false在外壳实际上脚本。
例如,在 PDP/11 Unix System 7 中:
$ ls -la /bin/true /bin/false
-rwxr-xr-x 1 bin 7 Jun 8 1979 /bin/false
-rwxr-xr-x 1 bin 0 Jun 8 1979 /bin/true
$
$ cat /bin/false
exit 1
$
$ cat /bin/true
$
Run Code Online (Sandbox Code Playgroud)
现在,至少在 中bash,true和false命令被实现为 shell 内置命令。因此,在命令行和 shell 脚本中使用false和true指令时,默认情况下不会调用任何可执行二进制文件bash。
从bash源头来看builtins/mkbuiltins.c:
字符 *posix_builtins[] =
{
"别名", "bg", "cd", "command", "**false**", "fc", "fg", "getopts", "jobs",
"kill", "newgrp", "pwd", "read", "**true**", "umask", "unalias", "wait",
(char *)NULL
};
同样根据@meuh 评论:
$ command -V true false
true is a shell builtin
false is a shell builtin
Run Code Online (Sandbox Code Playgroud)
因此,它可以以高度的确定性可以说的true和false可执行文件主要存在于从其他程序调用。
从现在开始,答案将集中在 Debian 9 / 64 位软件包中的/bin/true二进制文件上coreutils。(/usr/bin/true运行 RedHat。RedHat 和 Debian 都使用了这个 coreutils包,分析了后者的编译版本更容易掌握)。
正如在源文件中看到的那样false.c,/bin/false它使用(几乎)与 相同的源代码编译/bin/true,只是返回 EXIT_FAILURE (1),所以这个答案可以应用于两个二进制文件。
#define EXIT_STATUS EXIT_FAILURE
#include "true.c"
Run Code Online (Sandbox Code Playgroud)
因为它也可以通过具有相同大小的两个可执行文件来确认:
$ ls -l /bin/true /bin/false
-rwxr-xr-x 1 root root 31464 Feb 22 2017 /bin/false
-rwxr-xr-x 1 root root 31464 Feb 22 2017 /bin/true
Run Code Online (Sandbox Code Playgroud)
唉,答案的直接问题why are true and false so large?可能是,因为不再有如此紧迫的理由来关心他们的最佳表现。它们对bash性能不是必不可少的,不再被bash(脚本)使用。
类似的评论适用于它们的大小,对于我们现在拥有的那种硬件来说,26KB 是微不足道的。对于典型的服务器/桌面来说,空间不再是宝贵的,他们甚至不再费心为falseand使用相同的二进制文件true,因为它只是在使用coreutils.
然而,本着问题的真正精神集中精力,为什么本应如此简单和小巧的东西却变得如此之大?
各部分的实际分布/bin/true如这些图表所示;在 26KB 二进制文件中,主代码 + 数据大约占 3KB,占/bin/true.
true多年来,该实用程序确实获得了更多的原始代码,最显着的是对--version和的标准支持--help。
但是,这不是它如此大的(唯一)主要理由,而是在动态链接(使用共享库)的同时,还具有通常被coreutils链接为静态库的二进制文件使用的通用库的一部分。用于构建elf可执行文件的元数据也占二进制文件的很大一部分,因为按照今天的标准,它是一个相对较小的文件。
答案的其余部分用于解释我们如何构建以下图表,详细说明/bin/true可执行二进制文件的组成以及我们如何得出该结论。

正如@Maks 所说,二进制文件是从 C 编译的;根据我的评论,也确认它来自coreutils。我们直接指向作者 git https://github.com/wertarbyte/coreutils/blob/master/src/true.c,而不是 @Maks 的 gnu git(相同的来源,不同的存储库 - 这个存储库被选中是因为它具有coreutils库的完整来源)
我们可以在/bin/true这里看到二进制文件的各种构建块(Debian 9 - 64 位来自coreutils):
$ file /bin/true
/bin/true: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9ae82394864538fa7b23b7f87b259ea2a20889c4, stripped
$ size /bin/true
text data bss dec hex filename
24583 1160 416 26159 662f true
Run Code Online (Sandbox Code Playgroud)
那些:
在 24KB 中,大约 1KB 用于修复 58 个外部功能。
这仍然为其余代码留下大约 23KB 的空间。我们将在下面展示实际的主文件 - main()+usage() 代码大约编译了 1KB,并解释了其他 22KB 的用途。
用 进一步深入二进制文件readelf -S true,我们可以看到,虽然二进制文件为 26159 字节,但实际编译代码为 13017 字节,其余为分类数据/初始化代码。
然而,true.c这还不是全部,如果只是那个文件,13KB 似乎有点过大;我们可以看到调用的函数main()没有列在 elf 中看到的外部函数中objdump -T true;存在于以下位置的功能:
那些没有外部链接的额外功能main()是:
所以我的第一个怀疑是部分正确的,虽然库使用的是动态库,但/bin/true二进制文件很大*因为它包含一些静态库*(但这不是唯一的原因)。
编译 C 代码通常不会因为这样的空间下落不明而效率低下,因此我最初怀疑有什么地方不对劲。
额外的空间,几乎是二进制文件大小的 90%,确实是额外的库/精灵元数据。
使用Hopper对二进制进行反汇编/反编译以了解函数在哪时,可以看出true.c/usage()函数编译后的二进制代码实际为833字节,而true.c/main()函数为225字节,大约略小于 1KB。隐藏在静态库中的版本函数的逻辑大约为 1KB。
实际编译的 main()+usage()+version()+strings+vars 只使用了大约 3KB 到 3.5KB。
确实具有讽刺意味的是,由于上述原因,这些小而不起眼的公用事业规模变得更大。
相关问题:了解 Linux 二进制文件在做什么
true.c main() 与违规函数调用:
$ ls -la /bin/true /bin/false
-rwxr-xr-x 1 bin 7 Jun 8 1979 /bin/false
-rwxr-xr-x 1 bin 0 Jun 8 1979 /bin/true
$
$ cat /bin/false
exit 1
$
$ cat /bin/true
$
Run Code Online (Sandbox Code Playgroud)
二进制各部分的十进制大小:
$ size -A -t true
true :
section size addr
.interp 28 568
.note.ABI-tag 32 596
.note.gnu.build-id 36 628
.gnu.hash 60 664
.dynsym 1416 728
.dynstr 676 2144
.gnu.version 118 2820
.gnu.version_r 96 2944
.rela.dyn 624 3040
.rela.plt 1104 3664
.init 23 4768
.plt 752 4800
.plt.got 8 5552
.text 13017 5568
.fini 9 18588
.rodata 3104 18624
.eh_frame_hdr 572 21728
.eh_frame 2908 22304
.init_array 8 2125160
.fini_array 8 2125168
.jcr 8 2125176
.data.rel.ro 88 2125184
.dynamic 480 2125272
.got 48 2125752
.got.plt 392 2125824
.data 128 2126240
.bss 416 2126368
.gnu_debuglink 52 0
Total 26211
Run Code Online (Sandbox Code Playgroud)
输出 readelf -S true
$ readelf -S true
There are 30 section headers, starting at offset 0x7368:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000000254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000000274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000298 00000298
000000000000003c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000000002d8 000002d8
0000000000000588 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000000860 00000860
00000000000002a4 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000000b04 00000b04
0000000000000076 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000000b80 00000b80
0000000000000060 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000be0 00000be0
0000000000000270 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000000e50 00000e50
0000000000000450 0000000000000018 AI 5 25 8
[11] .init PROGBITS 00000000000012a0 000012a0
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000000012c0 000012c0
00000000000002f0 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 00000000000015b0 000015b0
0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 00000000000015c0 000015c0
00000000000032d9 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 000000000000489c 0000489c
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000000048c0 000048c0
0000000000000c20 0000000000000000 A 0 0 32
[17] .eh_frame_hdr PROGBITS 00000000000054e0 000054e0
000000000000023c 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000005720 00005720
0000000000000b5c 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000206d68 00006d68
0000000000000008 0000000000000008 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000206d70 00006d70
0000000000000008 0000000000000008 WA 0 0 8
[21] .jcr PROGBITS 0000000000206d78 00006d78
0000000000000008 0000000000000000 WA 0 0 8
[22] .data.rel.ro PROGBITS 0000000000206d80 00006d80
0000000000000058 0000000000000000 WA 0 0 32
[23] .dynamic DYNAMIC 0000000000206dd8 00006dd8
00000000000001e0 0000000000000010 WA 6 0 8
[24] .got PROGBITS 0000000000206fb8 00006fb8
0000000000000030 0000000000000008 WA 0 0 8
[25] .got.plt PROGBITS 0000000000207000 00007000
0000000000000188 0000000000000008 WA 0 0 8
[26] .data PROGBITS 00000000002071a0 000071a0
0000000000000080 0000000000000000 WA 0 0 32
[27] .bss NOBITS 0000000000207220 00007220
00000000000001a0 0000000000000000 WA 0 0 32
[28] .gnu_debuglink PROGBITS 0000000000000000 00007220
0000000000000034 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 00007254
000000000000010f 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
Run Code Online (Sandbox Code Playgroud)
的输出objdump -T true(外部函数在运行时动态链接)
$ objdump -T true
true: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __uflow
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 getenv
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 free
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 abort
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __errno_location
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strncmp
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 _exit
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __fpending
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 textdomain
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fclose
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 bindtextdomain
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 dcgettext
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __ctype_get_mb_cur_max
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strlen
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.4 __stack_chk_fail
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 mbrtowc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strrchr
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 lseek
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 memset
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fscanf
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 close
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 memcmp
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fputs_unlocked
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 calloc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strcmp
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.14 memcpy
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fileno
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 malloc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fflush
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 nl_langinfo
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 ungetc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __freading
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 realloc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fdopen
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 setlocale
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __printf_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 error
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 open
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fseeko
0000000000000000 w D *UND* 0000000000000000 _Jv_RegisterClasses
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_atexit
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 exit
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fwrite
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __fprintf_chk
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 mbsinit
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 iswprint
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalize
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3 __ctype_b_loc
0000000000207228 g DO .bss 0000000000000008 GLIBC_2.2.5 stdout
0000000000207220 g DO .bss 0000000000000008 GLIBC_2.2.5 __progname
0000000000207230 w DO .bss 0000000000000008 GLIBC_2.2.5 program_invocation_name
0000000000207230 g DO .bss 0000000000000008 GLIBC_2.2.5 __progname_full
0000000000207220 w DO .bss 0000000000000008 GLIBC_2.2.5 program_invocation_short_name
0000000000207240 g DO .bss 0000000000000008 GLIBC_2.2.5 stderr
Run Code Online (Sandbox Code Playgroud)
小智 37
该实现可能来自 GNU coreutils。这些二进制文件是从 C 编译的;没有特别努力使它们比默认情况下更小。
您可以尝试编译true自己的微不足道的实现,您会注意到它的大小已经只有几 KB。例如,在我的系统上:
$ echo 'int main() { return 0; }' | gcc -xc - -o true
$ wc -c true
8136 true
Run Code Online (Sandbox Code Playgroud)
当然,你的二进制文件更大。那是因为它们也支持命令行参数。尝试运行/usr/bin/true --help或/usr/bin/true --version.
除了字符串数据外,二进制文件还包括解析命令行标志等的逻辑。显然,这加起来大约有 20 KB 的代码。
作为参考,您可以在此处找到源代码:http : //git.savannah.gnu.org/cgit/coreutils.git/tree/src/true.c
ste*_*eve 26
将它们分解为核心功能并用汇编程序编写会产生更小的二进制文件。
原始的真/假二进制文件是用 C 编写的,它本质上会引入各种库 + 符号引用。如果你运行,readelf -a /bin/true这是非常明显的。
352字节用于剥离的 ELF 静态可执行文件(通过优化 asm 的代码大小来节省几个字节)。
$ more true.asm false.asm
::::::::::::::
true.asm
::::::::::::::
global _start
_start:
mov ebx,0
mov eax,1 ; SYS_exit from asm/unistd_32.h
int 0x80 ; The 32-bit ABI is supported in 64-bit code, in kernels compiled with IA-32 emulation
::::::::::::::
false.asm
::::::::::::::
global _start
_start:
mov ebx,1
mov eax,1
int 0x80
$ nasm -f elf64 true.asm && ld -s -o true true.o # -s means strip
$ nasm -f elf64 false.asm && ld -s -o false false.o
$ ll true false
-rwxrwxr-x. 1 steve steve 352 Jan 25 16:03 false
-rwxrwxr-x. 1 steve steve 352 Jan 25 16:03 true
$ ./true ; echo $?
0
$ ./false ; echo $?
1
$
Run Code Online (Sandbox Code Playgroud)
或者,使用一些讨厌/巧妙的方法(对stalkr 表示敬意),创建您自己的 ELF 标头,将其缩小到132 127字节。我们正在这里进入Code Golf领域。
$ cat true2.asm
BITS 64
org 0x400000 ; _start is at 0x400080 as usual, but the ELF headers come first
ehdr: ; Elf64_Ehdr
db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident
times 8 db 0
dw 2 ; e_type
dw 0x3e ; e_machine
dd 1 ; e_version
dq _start ; e_entry
dq phdr - $$ ; e_phoff
dq 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: ; Elf64_Phdr
dd 1 ; p_type
dd 5 ; p_flags
dq 0 ; p_offset
dq $$ ; p_vaddr
dq $$ ; p_paddr
dq filesize ; p_filesz
dq filesize ; p_memsz
dq 0x1000 ; p_align
phdrsize equ $ - phdr
_start:
xor edi,edi ; int status = 0
; or mov dil,1 for false: high bytes are ignored.
lea eax, [rdi+60] ; rax = 60 = SYS_exit, using a 3-byte instruction: base+disp8 addressing mode
syscall ; native 64-bit system call, works without CONFIG_IA32_EMULATION
; less-golfed version:
; mov edi, 1 ; for false
; mov eax,252 ; SYS_exit_group from asm/unistd_64.h
; syscall
filesize equ $ - $$ ; used earlier in some ELF header fields
$ nasm -f bin -o true2 true2.asm
$ ll true2
-rw-r--r-- 1 peter peter 127 Jan 28 20:08 true2
$ chmod +x true2 ; ./true2 ; echo $?
0
$
Run Code Online (Sandbox Code Playgroud)