前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >9个基本的GNU binutils 工具【Linux-Command-line】

9个基本的GNU binutils 工具【Linux-Command-line】

作者头像
QRosie
修改2019-11-20 17:25:13
4.1K0
修改2019-11-20 17:25:13
举报

二进制分析是计算机行业中被低估的技能。

图片来源:opensource.com
图片来源:opensource.com

想象一下,尽管无法访问软件的源代码,但仍然能够理解软件的实现方式,在其中找到漏洞,并且(更好的是)修复了错误。 凡此种种都源于二进制形式。 听起来像是拥有超能力,不是吗?

你也可以拥有这样的超级能力,GNU二进制实用程序(binutils)是一个很好的起点。 GNU binutils是二进制工具的集合,默认情况下,这些工具安装在所有Linux发行版中。

二进制分析是计算机行业中被低估的技能。 它主要由恶意软件分析师,反向工程师和在底层软件上工作的人员使用。

本文探讨了一些可用的binutils工具。 我正在使用的是RHEL,但是这些示例可以在任何Linux发行版上运行。

请注意,某些打包命令(例如rpm)可能在基于Debian的发行版中不可用,因此请在适用时使用等效的dpkg命令。

软件开发101

在开源世界中,我们很多人都专注于源代码形式的软件。 当软件的源代码随时可用时,很容易获得源代码的副本,打开你喜欢的编辑器,喝杯咖啡,然后开始探索。

但是源代码不是在CPU上执行的代码。 它是在CPU上执行的二进制或机器语言指令。 二进制或可执行文件是编译源代码时获得的。 熟练的调试人员通常会通过了解这种差异来获得优势。

汇编101

在深入研究binutils软件包本身之前,最好先了解编译的基础知识。

编译是将程序从某种编程语言(C / C ++)的源代码或文本形式转换为机器代码的过程。

机器代码是CPU(通常被称为硬件)可以理解的1和0的序列,因此可以由CPU执行或运行。 该机器码以特定格式保存到文件,通常称为可执行文件或二进制文件。 在Linux(当使用Linux Binary Compatibility时,还有BSD)上,这称为ELF(可执行和可链接格式)。

在呈现给定源文件的可执行文件或二进制文件之前,编译过程将经历一系列复杂的步骤。 以该源程序(C代码)为例。 打开你喜欢的编辑器,然后键入以下程序:

步骤1:使用cpp进行预处理

C preprocessor(cpp)用于扩展所有宏并包括头文件。 在此示例中,头文件“stdio.h”将包含在源代码中。 “stdio.h”是一个头文件,其中包含有关程序内使用的printf函数的信息。 cpp在源代码上运行,并将生成的指令保存在名为“hello.i”的文件中。 使用文本编辑器打开文件以查看其内容。 打印“hello world”的源代码在文件的底部。

步骤2:使用gcc进行编译

在此阶段,无需创建目标文件就将步骤1中的预处理源代码转换为汇编语言指令。 它使用GNU Compiler Collection (gcc)。 在“hello.i”文件上运行带有“-S”选项的gcc命令后,它将创建一个名为“hello.s”的新文件。 该文件包含C程序的汇编语言说明。

您可以使用任何编辑器或cat命令查看内容。

步骤3:用as组装

汇编程序的目的是将汇编语言指令转换为机器语言代码,并生成扩展名为“.o”的目标文件。 使用GNU汇编程序“as”,因为它在所有Linux平台上默认都可用。

现在,你有了ELF格式的第一个文件,然而当前你还不能执行它。 稍后,你将看到目标文件和可执行文件之间的区别。

步骤4:链接Id

当链接目标文件以创建可执行文件时,这是编译的最后阶段。 可执行文件通常需要外部函数,这些函数通常来自系统库(libc)。

可以使用ld命令直接调用链接器; 但是,此命令有些复杂。 相反,你可以将gcc编译器与“-v”(verbose)标志一起使用,以便了解链接的运作方式。 (你需要探索如何使用ld命令进行链接)

运行此命令后,你应该看到一个名为“a.out”的可执行文件:

在a.out上运行file命令表明它确实是ELF可执行文件:

运行可执行文件,查看它是否如源代码所示:

答案是肯定的! 只为在屏幕上打印“Hello World”,屏幕后发生了很多事情。 可以想像在更复杂的程序中会发生什么。

探索binutils工具

此练习为使用binutils软件包中的工具提供了良好的背景。 我的系统binutils版本为2.27-34, 考虑到Linux发行版的多样,你的版本可能有不同之处。

binutils软件包提供了以下可用工具:

上面的编译练习已经探索了其中两个工具:as命令用作汇编程序,而ld命令用作链接程序。 继续阅读以了解其他七个以上粗体突出显示的GNU binutils软件包工具。

readelf:显示有关ELF文件的信息

上面的练习提到了术语“目标文件”和“可执行文件”。 使用该练习中的文件,用“-h”(header)选项输入“readelf”,以便将文件的ELF标题转储到屏幕上。 请注意,以“.o”扩展名结尾的目标文件显示为“Type:REL(Relocatable file)”:

如果尝试执行此文件,将收到一条错误消息,指出无法执行。 这仅表示它尚不具备在CPU上执行所需的信息。

请记住,你首先需要使用chmod命令在目标文件上添加"x"或“executable bit”,否则将出现“Permission denied”错误。

如果对a.out文件尝试相同的命令,则会看到其类型为“EXEC”(Executable file,可执行文件)。

如前所示,该文件可以直接由CPU执行:

readelf命令提供了大量有关二进制文件的信息。 在这里,它告诉你它是ELF64位格式,这意味着它只能在64位CPU上执行,而不能在32位CPU上运行。 它还告诉你它应在X86-64(Intel / AMD)架构上执行。 二进制文件的入口点是地址0x400430,它只是C源程序中main function的地址。

在其他已知的系统二进制文件(如ls)上尝试使用readelf命令。 请注意,由于安全原因更改了位置无关可执行文件(PIE),因此在RHEL 8或Fedora 30及更高版本的系统上,你的输出(尤其是Type :)可能会有所不同。

使用ldd命令了解ls命令依赖于哪些系统库,如下所示:

在libc库文件上运行readelf以查看它是哪种文件。 正如它指出的那样,它是一个DYN(共享对象文件),这意味着它不能被直接执行。 必须由内部库提供的任意功能的可执行文件使用它。

size:列出部分大小和总大小

size命令仅适用于目标文件和可执行文件,因此,如果您尝试在简单的ASCII文件上运行它,则会出现错误,提示“File format not recognized(无法识别文件格式)”。

现在,从上面的练习中对目标文件和可执行文件运行size。 请注意,根据size命令的输出,可执行文件(a.out)的信息比目标文件(hello.o)的信息多得多:

但text,data和bss sections又意味着什么?

text sections引用二进制文件的代码部分,其中包含所有可执行指令。 data sections是所有初始化数据所在的位置,bss是所有未初始化数据存储的位置。

将size与其他一些可用的系统二进制文件进行比较。

对于ls命令:

查看size命令的输出,可知gcc和gdb是比ls更大的程序:

字符串:打印文件中可打印字符的字符串

通常在字符串命令中添加“-d”标志,用以仅显示数据部分中的可打印字符。

“hello.o”是一个目标文件,其中包含打印出文本“Hello World”的说明。 因此,strings命令的唯一输出是“Hello World”。

另一方面,在a.out(可执行文件)上运行strings,会显示链接阶段二进制文件中包含的其他信息:

回想一下,编译是将源代码指令转换为机器代码的过程。 机器代码仅由1和0组成,人类难以阅读。 因此,它有助于将机器代码表示为汇编语言指令。 汇编语言是什么样的? 请记住,汇编语言是特定于体系结构的; 我使用的是Intel或x86-64架构,如果你使用ARM架构编译相同的程序,指导说明将有所不同。

objdump:显示目标文件中的信息

可以从二进制文件中转出机器语言指令的另一个binutils工具称为“objdump”。

使用“-d”选项,该选项可从二进制文件中反汇编所有汇编指令。

该输出乍一看似乎令人生畏,但在你继续前,请花一点时间理解它。 回想一下,“.text”部分包含所有机器代码指令。 汇编说明可以在第四列中看到(即push,mov,callq,pop,retq)。 这些指令作用于寄存器,寄存器是CPU内置的存储器位置。 本示例中的寄存器是rbp,rsp,edi,eax等,每个寄存器都有特殊含义。

现在,在可执行文件(a.out)上运行objdump,然后查看得到的结果。 可执行文件上objdump的输出可能很大,因此我使用grep命令将其缩至main function:

注意,这些指令与目标文件“hello.o”相似,但是其中包含一些其他信息:

  • 目标文件“hello.o”具有以下指令:callq e
  • 可执行文件“a.out”包含以下指令以及地址和函数:callq 400400 <puts @ plt>

上面的汇编指令正在调用puts函数。请记住,你在源代码中使用了printf函数。编译器插入了对puts库函数的调用,以期将“Hello World”输出到屏幕。

查看puts上方行的说明:

  • 目标文件“hello.o”的指令为mov:mov $ 0x0,%edi
  • 可执行文件“a.out”的指令mov具有实际地址($ 0x4005d0)而不是$ 0x0:mov $ 0x4005d0,%edi

该指令将二进制文件中位于地址$ 0x4005d0的所有内容移动到名为edi的寄存器中。

该存储位置的内容中还有什么?是的,你猜对了:它只不过是文本“Hello,World”。你如何确定?

使用readelf命令可以将二进制文件(a.out)的任何部分转储到屏幕上。以下操作要求它将.rodata(只读数据)转储到屏幕上:

你可以在右侧看到文本“ Hello World”,在左侧看到其二进制地址。 它是否与你在上面的mov指令中看到的地址匹配? 是的,的确匹配。

strip:从目标文件中弃置符号

该命令通常用于将二进制文件运送给客户之前,以减小二进制文件的大小。

请记住,由于重要信息已从二进制文件中删除,因此它会阻碍调试过程。 但是,二进制文件依然可以顺利执行。

在你的a.out可执行文件上运行它,并注意会发生什么。 首先,通过运行以下命令确保二进制文件未被剥离(not stripped):

另外,在运行strip命令之前,请跟踪二进制文件中最初的字节数:

现在,在你的可执行文件上运行strip命令,并使用file命令确保它可以正常工作:

剥离二进制文件后,此小程序的大小从以前的8440字节减小到6296。 这样一个小型程序的空间能被极大节省,难怪大型程序经常被剥离。

addr2line:将地址转换为文件名和行号

addr2line工具只是在二进制文件中查找地址,并将其与C源代码程序中的行进行匹配。 很酷,对吧?

为此编写另一个测试程序; 只有这一次才能确保使用gcc的“-g”标志进行编译,这将为二进制文件添加其他调试信息,并且包含行号(在此处的源代码中提供)也将有所帮助:

使用“-g”标志进行编译并执行。 这里还没有惊喜:

现在使用objdump标识函数开始的内存地址。 您可以使用grep命令来过滤出所需的特定行。 功能的地址在下面突出显示:

现在,使用addr2line工具从二进制文件映射这些地址,以匹配C源代码的地址:

可见40051d在源文件atest.c中的第6行开始,这是function1的起始大括号“{”开始的行。 使function2和main的输出匹配。

nm:列出目标文件中的符号

使用上面的C程序测试nm工具。 使用gcc快速编译并执行。

现在运行nm和grep,获取有关函数和变量的信息:

你可以看到这些函数在text部分中标记为“T”,代表符号,而变量标记为“D”,其代表初始化data部分中的符号。

想象一下在没有源代码的二进制文件上运行此命令有多大用处? 这使你可以窥视内部并了解使用了哪些函数和变量。 当然,除非二进制文件已被剥离,否则它们将不包含任何符号,因此nm命令不会很有帮助,如在此处看到的:

结论

GNU binutils工具为有兴趣分析二进制文件的任何人提供了许多选项,这只是其作用的冰山一角。 阅读每种工具的手册页,了解更多相关使用信息。

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系外文翻译前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 软件开发101
  • 汇编101
    • 步骤1:使用cpp进行预处理
      • 步骤2:使用gcc进行编译
        • 步骤3:用as组装
          • 步骤4:链接Id
          • 探索binutils工具
            • readelf:显示有关ELF文件的信息
              • size:列出部分大小和总大小
                • 字符串:打印文件中可打印字符的字符串
                  • objdump:显示目标文件中的信息
                    • strip:从目标文件中弃置符号
                      • addr2line:将地址转换为文件名和行号
                        • nm:列出目标文件中的符号
                        • 结论
                        相关产品与服务
                        数据保险箱
                        数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档