导读 | 想象一下,在无法访问软件的源代码时,但仍然能够理解软件的实现方式,在其中找到漏洞,并且更厉害的是还能修复错误。所有这些都是在只有二进制文件时做到的。这听起来就像是超能力,对吧?你也可以拥有这样的超能力,GNU 二进制实用程序(binutils)就是一个很好的起点。GNU binutils 是一个二进制工具集,默认情况下所有 Linux 发行版中都会安装这些二进制工具。 |
二进制分析是计算机行业中最被低估的技能。它主要由恶意软件分析师、反向工程师和使用底层软件的人使用。
本文探讨了 binutils 可用的一些工具。我使用的是 RHEL,但是这些示例应该在任何 Linux 发行版上可以运行。
[~]# cat /etc/redhat-release Red Hat Enterprise Linux Server release 7.6 (Maipo) [~]# [~]# uname -r 3.10.0-957.el7.x86_64 [~]#
请注意,某些打包(例如 rpm)在基于 Debian 的发行版中可能不可用,因此请使用等效的 dpkg 替代。
在开源世界中,我们很多人都专注于源代码形式的软件。当软件的源代码随时可用时,很容易获得源代码的副本,打开喜欢的编辑器,喝杯咖啡,然后就可以开始探索了。
但是源代码不是在 CPU 上执行的代码,在 CPU 上执行的是二进制或者说是机器语言指令。二进制或可执行文件是编译源代码时获得的。熟练的调试人员深谙通常这种差异。
在深入研究 binutils 软件包本身之前,最好先了解编译的基础知识。
编译是将程序从某种编程语言(如 C/C++)的源代码(文本形式)转换为机器代码的过程。
机器代码是 CPU(或一般而言,硬件)可以理解的 1 和 0 的序列,因此可以由 CPU 执行或运行。该机器码以特定格式保存到文件,通常称为可执行文件或二进制文件。在 Linux(和使用 Linux 兼容二进制的 BSD)上,这称为 ELF(可执行和可链接格式Executable and Linkable Format)。
在生成给定的源文件的可执行文件或二进制文件之前,编译过程将经历一系列复杂的步骤。以这个源程序(C 代码)为例。打开你喜欢的编辑器,然后键入以下程序:
#includeint main(void) { printf("Hello World\n"); return 0; }
C 预处理程序(cpp)用于扩展所有宏并将头文件包含进来。在此示例中,头文件 stdio.h 将被包含在源代码中。stdio.h 是一个头文件,其中包含有关程序内使用的 printf 函数的信息。对源代码运行 cpp,其结果指令保存在名为 hello.i 的文件中。可以使用文本编辑器打开该文件以查看其内容。打印 “hello world” 的源代码在该文件的底部。
[testdir]# cat hello.c #includeint main(void) { printf("Hello World\n"); return 0; } [testdir]# [testdir]# cpp hello.c > hello.i [testdir]# [testdir]# ls -lrt total 24 -rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c -rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i [testdir]#
在此阶段,无需创建目标文件就将步骤 1 中生成的预处理源代码转换为汇编语言指令。这个阶段使用 GNU 编译器集合(gcc)。对 hello.i 文件运行带有 -S 选项的 gcc 命令后,它将创建一个名为 hello.s 的新文件。该文件包含该 C 程序的汇编语言指令。
你可以使用任何编辑器或 cat 命令查看其内容。
[testdir]# [testdir]# gcc -Wall -S hello.i [testdir]# [testdir]# ls -l total 28 -rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c -rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i -rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s [testdir]# [testdir]# cat hello.s .file "hello.c" .section .rodata .LC0: .string "Hello World" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $.LC0, %edi call puts movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)" .section .note.GNU-stack,"",@progbits [testdir]#
汇编器的目的是将汇编语言指令转换为机器语言代码,并生成扩展名为 .o 的目标文件。此阶段使用默认情况下在所有 Linux 平台上都可用的 GNU 汇编器。
testdir]# as hello.s -o hello.o [testdir]# [testdir]# ls -l total 32 -rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c -rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i -rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o -rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s [testdir]#
现在,你有了第一个 ELF 格式的文件;但是,还不能执行它。稍后,你将看到“目标文件object file”和“可执行文件executable file”之间的区别。
[testdir]# file hello.o hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
这是编译的最后阶段,将目标文件链接以创建可执行文件。可执行文件通常需要外部函数,这些外部函数通常来自系统库(libc)。
你可以使用 ld 命令直接调用链接器;但是,此命令有些复杂。相反,你可以使用带有 -v(详细)标志的 gcc 编译器,以了解链接是如何发生的。(使用 ld 命令进行链接作为一个练习,你可以自行探索。)
[testdir]# gcc -v hello.o Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper Target: x86_64-redhat-linux Configured with: ../configure --prefix=/usr --mandir=/usr/share/man [...] --build=x86_64-redhat-linux Thread model: posix gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:[...]:/usr/lib/gcc/x86_64-redhat-linux/ LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64' /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu [...]/../../../../lib64/crtn.o [testdir]#
运行此命令后,你应该看到一个名为 a.out 的可执行文件:
[testdir]# ls -l total 44 -rwxr-xr-x. 1 root root 8440 Sep 13 03:45 a.out -rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c -rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i -rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o -rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
对 a.out 运行 file 命令,结果表明它确实是 ELF 可执行文件:
[testdir]# file a.out a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=48e4c11901d54d4bf1b6e3826baf18215e4255e5, not stripped
运行该可执行文件,看看它是否如源代码所示工作:
[testdir]# ./a.out Hello World
工作了!在幕后发生了很多事情它才在屏幕上打印了 “Hello World”。想象一下在更复杂的程序中会发生什么。
请在第二节查看实例内容“GNU binutils 里的九种武器(—实例)”
via:
作者:Gaurav Kamathe 选题:lujun9972译者:wxy 校对:wxy
本文由 原创翻译, 荣誉推出
原文来自:
本文地址://gulass.cn/gnu-binutils-%e9%87%8c%e7%9a%84%e4%b9%9d%e7%a7%8d%e6%ad%a6%e5%99%a8-%e5%9f%ba%e7%a1%80%e7%9f%a5%e8%af%86.html编辑:黑曜羽,审核员:逄增宝
Linux命令大全:
Linux系统大全: