手臂装配基础知识简介

2020-11-08 09:32:55

欢迎学习本系列关于手臂装配基础知识的教程。这是对关于ARM漏洞开发的后续教程系列的准备。在我们开始创建ARM外壳代码和构建ROP链之前,我们需要先介绍一些ARM组装基础知识。

ARM汇编基础教程系列:第1部分:ARM汇编简介第2部分:数据类型寄存器第3部分:ARM指令集第4部分:内存指令:加载和存储数据第5部分:加载和存储多个数据第6部分:条件执行和分支第7部分:堆栈和分支函数。

要按照示例操作,您需要一个基于ARM的实验环境。如果您没有ARM设备(如Raspberry PI),您可以按照本教程的要求在使用QEMU和Raspberry PI发行版的虚拟机中设置自己的实验室环境。如果您不熟悉gdb的基本调试,您可以在本教程中获得基础知识。在本教程中,重点将放在ARM 32位上,示例是在ARMv6上编译的。

本教程一般面向想要学习手臂装配基础知识的人。特别是对于那些对利用ARM平台上的写作感兴趣的人。您可能已经注意到,ARM处理器无处不在。环顾四周,我家里搭载ARM处理器的设备比英特尔处理器多得多。这其中包括手机、路由器,还有别忘了如今销量似乎呈爆炸式增长的物联网设备。话虽如此,ARM处理器已经成为世界上应用最广泛的CPU核心之一。这就引出了这样一个事实:与PC一样,物联网设备也容易受到缓冲区溢出等不当输入验证滥用的影响。鉴于基于ARM的设备的广泛使用和误用的可能性,针对这些设备的攻击变得更加常见。

然而,尽管ARM汇编语言可能是被广泛使用的最简单的汇编语言,但我们在x86安全研究方面的专家比ARM更多。那么,为什么没有更多的人关注ARM呢?也许是因为有更多的学习资源涵盖了英特尔的利用,而不是ARM。只要想想由模糊安全或Corelan团队编写的关于英特尔x86漏洞的优秀教程-这样的指南可以帮助对这一特定领域感兴趣的人获得实用知识,并获得学习这些教程所涵盖内容之外的灵感。如果您对x86利用漏洞编写感兴趣,Corelan和Fuzzysec教程是您完美的起点。在本系列教程中,我们将重点介绍汇编基础知识和ARM上的编写。

英特尔和ARM之间有很多不同之处,但主要的不同之处在于指令集。英特尔是一款CISC(复杂指令集计算)处理器,拥有更大、功能更丰富的指令集,并允许许多复杂指令访问内存。因此,与ARM相比,它具有更多的操作、寻址模式,但寄存器更少。CISC处理器主要用于普通PC、工作站和服务器。

ARM是一种RISC(精简指令集计算)处理器,因此与CISC相比,它具有简化的指令集(100条或更少的指令)和更多的通用寄存器。与英特尔不同,ARM使用仅在寄存器上运行的指令,并使用加载/存储内存模型进行内存访问,这意味着只有加载/存储指令才能访问内存。这意味着,在ARM上的特定存储器地址递增32位值需要三种指令(加载、递增和存储),以便首先将特定地址的值加载到寄存器中,在寄存器中递增,然后将其从寄存器存储回存储器。

精简指令集有其优点和缺点。其中一个优点是可以更快地执行指令,潜在地允许更快的速度(RISC系统通过减少每个指令的时钟周期来缩短执行时间)。缺点是,更少的指令意味着更多地强调用有限的指令高效地编写软件。同样需要注意的是,ARM有两种模式,ARM模式和拇指模式。Thumb指令可以是2个字节,也可以是4个字节(在第3部分:ARM指令集中有更多信息)。

在版本3之前,ARM架构是小端的。从那时起,ARM处理器变成了BI-Endian,并且具有允许可切换的端序的设置。

不仅英特尔和ARM之间存在差异,不同的ARM版本本身也存在差异。本系列教程的目的是使其尽可能通用,以便您对ARM的工作原理有一个大致的了解。一旦你理解了基本原理,就很容易了解你选择的目标ARM版本的细微差别。本教程中的示例是在32位ARMv6(Raspberry PI 1)上创建的,因此解释与该版本相关。

在我们开始研究ARM漏洞开发之前,我们首先需要了解汇编语言编程的基础知识,这需要一些背景知识才能开始欣赏它。但是为什么我们需要ARM汇编,用一种“普通的”编程/脚本语言来编写我们的漏洞还不够吗?如果我们想要能够做逆向工程,了解ARM二进制代码的程序流程,构建我们自己的ARM外壳代码,制作ARM ROP链,调试ARM应用程序,就不是这样的。

您不需要了解汇编语言的每一个小细节就能够进行逆向工程和开发,但其中一些是理解大局所必需的。本系列教程将介绍基础知识。如果您想了解更多信息,可以访问本章末尾列出的链接。

那么汇编语言到底是什么呢?汇编语言只是机器代码之上的一层薄薄的语法层,它由指令组成,这些指令以二进制表示(机器代码)编码,这是我们的计算机所理解的。那么为什么我们不直接写机器代码呢?嗯,那将是一个令人头疼的问题。出于这个原因,我们将编写组装,手臂组装,这对人类来说更容易理解。我们的计算机不能自己运行汇编代码,因为它需要机器代码。我们将用来将汇编代码汇编成机器码的工具是GNU Binutils项目中的GNU汇编程序,该程序名为Binutils,用于处理扩展名为*.s的源文件。

编写了扩展名为*.s的程序集文件后,需要使用as命令将其汇编,并将其与ld链接:

让我们从最底层开始,一直到汇编语言。在最低电平,我们的电路上有我们的电信号。信号是通过将电压切换到两个电平之一来形成的,例如0伏(‘OFF’)或5伏(‘ON’)。因为仅靠观察我们很难分辨出电路的电压是多少,我们选择用视觉表示法(数字0和1)写下通断电压模式,不仅是为了表示信号的存在或不存在,还因为0和1是二进制的数字。然后,我们将0和1的序列组合起来,形成一条机器代码指令,这是计算机处理器的最小工作单元。下面是一个机器语言指令的例子:

到目前为止还不错,但是我们记不清这些模式(0和1)各自的含义。出于这个原因,我们使用所谓的助记符,缩写来帮助我们记住这些二进制模式,其中每条机器代码指令都有一个名字。这些助记符通常由三个字母组成,但这不是必须的。我们可以用这些助记符作为指令来编写程序。这个程序被称为汇编语言程序,用于表示计算机机器代码的助记符集合被称为该计算机的汇编语言。因此,汇编语言是人类用来编写计算机程序的最低级别。指令的操作数在助记符之后。下面是一个例子:

既然我们知道汇编程序是由称为助记符的文本信息组成的,那么我们需要将其转换为机器码。如上所述,在ARM组装的情况下,GNU Binutils项目为我们提供了一个名为AS的工具。使用像ASS这样的汇编器将(ARM)汇编语言转换成(ARM)机器码的过程称为汇编。

综上所述,我们了解到计算机能够理解(响应)电压(信号)的存在或不存在,并且我们可以用0和1(位)的序列来表示多个信号。我们可以使用机器代码(信号序列)来使计算机以某种明确定义的方式做出响应。因为我们不能记住所有这些序列的意思,所以我们给它们一些缩写--助记符,并用它们来表示指令。这组助记符是计算机的汇编语言,我们使用一个名为汇编器的程序将代码从助记符表示转换为计算机可读的机器码,就像编译器对高级语言所做的那样。