Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

简介: 本文讲的是Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍,建议阅读本文之前,你对ARM组件的有个基本了解,本文会先为你介绍32位Linux环境中进程的内存布局,然后再介绍堆栈和堆相关内存损坏的基本原理以及调试方法。
本文讲的是 Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

前言

建议阅读本文之前,你对ARM组件的有个基本了解,本文会先为你介绍32位Linux环境中进程的内存布局,然后再介绍堆栈和堆相关内存损坏的基本原理以及调试方法。

本文中使用的示例是在ARMv6 32位处理器上编译的,如果你无法访问ARM设备,可以点击这里https://azeria-labs.com/emulate-raspberry-pi-with-qemu/创建自己的实验环境并在虚拟机中模拟Raspberry Pi发行版。这里使用的调试器是GDB(GDB增强功能)。如果你不熟悉这些工具,可以点击这里https://azeria-labs.com/debugging-with-gdb-introduction/,查看如何使用GDB和GEF进行调试。

进程的内存布局

每次启动程序时,都会保留该程序的内存区域,然后再将该区域分割成多个区域。所以我感兴趣的部分是:

1.程序映像

2.堆

3.栈

在下图中,我可以看到这些部分是如何在进程内存中被布置的。用于指定内存区域的地址会根据环境的不同而不同,特别是在使用ASLR时,我在本文中也仅仅是举一个例子进行说明:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

程序映像区基本上都是保存加载到内存中的程序可执行文件,这个内存区域可以分为多个段:.plt,.text,.got,.data,.bss等,这些是最相关的。例如,.text包含程序的可执行部分,其中包含所有的汇编指令.data和.bss保存应用程序中使用的变量或指针,.plt和.got存储各种导入函数的特定指针,用于共享库。从安全的角度来说,如果攻击者进行了.text部分的完整性重写,就可以执行任意代码。同样,过程链接表(.plt)和全局偏移表(.got)的损坏也可能在特定情况下导致执行任意代码。

应用程序使用栈和堆区域来存储和操作在执行程序期间使用的临时数据或变量,这些区域通常被攻击者利用,因为栈和堆区域中的数据通常可以通过用户的输入修改,如果不能正确处理,可能会导致内存损坏,我将在本文后面说明这种情况。

除了内存映射之外,我还需要了解与不同内存区域相关联的属性。存储区域的属性可以是以下属性之一,也可以是它们之间的随意组合:Read, Write, eXecute。

Read属性允许程序从特定区域读取数据,同样,Write属性允许程序将数据写入特定的存储器区域,并执行该存储区域中的指令。我可以看到GEF中的进程内存区域(GDB强烈推荐的扩展名)如下所示:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

vmmap命令输出中的堆区(Heap section)仅在使用了一些堆相关功能后才会出现,这样我就看到了malloc函数用于在堆区域中创建的一个缓冲区。所以如果你想尝试这个,你需要调试一个使malloc调用的程序。

另外,在Linux中,我可以通过访问进程特定的文件来检查进程的内存布局:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

大多数程序的编译方式是使用共享库,这些库不是程序映像的一部分(即使可以通过静态链接来包含它们),因此必须动态地引用。我看到在进程的内存布局中加载的库(libc,ld等)。大致来说,共享库被加载到内存中的某个位置(在进程控制之外),由于为了节省内存,我的程序只是为该内存区域创建虚拟的“链接”,而无需在程序的每个实例中加载相同的库。

引入内存损坏

内存损坏是软件错误的一种形式,允许以程序员不想要的方式修改内存。在大多数情况下,可以利用此条件执行任意代码,禁用安全机制等。这是通过制作和注入改变正在运行的程序的某些内存部分的有效载荷来完成的。以下列表包含最常见的内存损坏类型或漏洞:

1.  缓冲区溢出

1.1 栈溢出 

1.2 堆溢出

2. 悬垂指针

3. 格式化字符串

在本文中,我将尝试使用熟悉的缓冲区溢出内存损坏漏洞的基础知识。在我将要介绍的例子中,内存损坏漏洞的主要原因是不正确的用户输入验证,有时它会与逻辑缺陷相结合。程序输入或恶意有效载荷可能以用户名,要打开的文件,网络数据包等形式出现,并且通常可能受到用户的影响。如果程序员没有对潜在有害的用户输入采取安全措施,那么目标程序通常会遇到与内存有关的漏洞。

缓冲区溢出

缓冲区溢出是一种非常普遍、非常危险的漏洞,在各种操作系统、应用软件中广泛存在。利用缓冲区溢出攻击,可以导致程序运行失败、系统宕机、重新启动等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。

缓冲区溢出通常是由编程错误引起的,允许用户提供比可用的目标变量更多的数据。例如,当使用易受攻破的函数(如gets,strcpy,memcpy或其他)以及用户提供的数据时,就会发生这种情况。这些函数不但不会检查用户数据的长度,还可能导致写入过去分配的缓冲区。为了更好地理解,我的研究将基于栈和堆的缓冲区溢出。

栈溢出

栈溢出,顾名思义,是影响堆栈的内存损坏。虽然在大多数情况下,堆栈的任意破坏很可能会导致程序崩溃,精心制作的栈缓冲区溢出可能会导致任意代码执行。下图显示了Stack如何破坏图解:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

如上图所示,栈框架(专用于特定函数的一小部分栈)可以具有各种组件:用户数据,前栈帧指针(previous frame pointer),前链接寄存器(previous Link Register)等。如果用户也提供了受控变量的大部分数据,FP和LR字段可能会被覆盖。这会打破程序的执行,因为用户在当前函数完成后会破坏应用程序返回或跳转的地址。

要检查它在实践中的运行,我可以使用以下这个例子:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

我的示例程序使用的是长度为8个字符的变量缓冲区,用户输入的函数“gets”,它将变量缓冲区的值设置为用户提供的任何输入值,该程序的反汇编代码如下所示:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

这里我怀疑内存损坏可能会在函数获取完成之后发生,为了验证这一点,我在调用获取函数的一个指令之后放置了一个中断点,地址为0x0001043c。为了减少干扰,我配置了GEF的布局,只显示代码和栈(见下图中的命令)。一旦设置了断点,我将继续执行程序,并以7 A作为用户的输入命令。之所以我使用7 A,是因为空字节将被函数“gets”自动附加:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

当我验证我示例的栈后,我看到栈框架并没有被损坏。这是因为用户提供的输入符合预期的8字节缓冲区,并且栈框架中的前FP和LR值不会被破坏。现在让我试着输入16 A,看看会发生什么。

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

在第二个例子中,可以看到,当我为函数“gets”提供太多的数据时,它不会停止在目标缓冲区的边界,并且保持写入“down the Stack”,这导致我以前的FP和LR值被破坏。当我继续运行程序时,会发生程序崩溃,因为在当前函数的结尾处,FP和LR的先前值会从堆栈“P”“R”和PC寄存器强制程序跳转到地址0x41414140(由于切换到Thumb模式,最后一个字节自动转换为0x40),这就是非法地址。下图显示了崩溃时寄存器的值(看看$pc)。

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

堆溢出

首先,堆是一个更复杂的内存位置,主要是因为它的管理方式与栈不同。为了让说明变得简,我要先声明一个事实:放置在堆存储部分中的每个对象都被打包成具有两部分的“chunk”:头和用户数据(有时被用户完全控制)。在堆的情况下,只有当用户能够写出比预期更多的数据时,才会发生内存损坏。在这种情况下,损坏可能发生在 块的边界内或超出两个(或更多) 块的边界。比如下面的例子。

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

如上图所示,当用户有能力向u_data_1提供更多数据并跨越u_data_1和u_data_2之间的边界时,就会发生块内堆溢出。这样,当前对象的字段或属性被破坏。如果用户提供的数据比当前堆可容纳的还要多,则就会从块间溢出并导致相邻块的损坏。

块内堆溢出(Intra-chunk Heap overflow)

为了说明块内堆栈溢出在实践中如何运行,我可以使用下面的例子,并用“-O”(优化标志)来编译一个较小的二进制程序,以方便大家查看:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

上述程序会执行以下操作:

1.定义具有两个字段的数据结构(u_data)

2.创建一个类型为u_data的对象(在堆内存区域)

3.为对象的数字字段分配一个静态值

4.提示用户为该对象的名称字段提供一个值

5.根据数字字段的值打印字符串

所以在这5种情况下,我也怀疑在函数“gets”之后可能会发生损坏,于是我反汇编目标程序的主要函数来获取断点的地址:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

这样,我就在函数”gets”完成之后设置地址0x00010498的断点。由于我配置的GEF仅向我显示代码,所以我运行该程序并提供7A作为用户输入:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

一旦找到突破点,我就会快速查找程序的内存布局,以便找到其中的堆。我使用vmmap命令,看到我的堆从地址0x00021000开始。鉴于我的对象(objA)是程序创建的第一个也是唯一的,我从一开始就开始分析堆:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

上图显示了我分析堆的一些细节,该块用一个头(8字节)和用户数据部分(12个字节)存储我的对象。我看到名称字段正确地存储了提供的7 A的字符串,并由一个空字节终止。数字字段存储0x4d2(十进制为1234)。我会输入8A,重复这些步骤,。

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

在输入8A再检查堆时,我看到数字的字段已经损坏(现在是0x400而不是0x4d2)。空字节终止符覆盖了该字段的一部分(最后一个字节)。这将导致块内堆内存损坏。不过,在这种情况下,这种损坏的影响并不是毁灭性的,而是可预测的。在逻辑上, else语句并不能达到代码,因为数字的字段是静态的。然而,我刚刚观察到的内存损坏却可以使得else语句达到该代码。这可以通过下面的示例容易地确认:

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

块间堆溢出(Inter-chunk Heap overflow)

为了说明一个块之间的堆溢出在实践中如何运行,在下面的例子,我可以不适用优化标志(optimization flag)来编译。

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

上图的过程类似于以前的过程,即在函数”gets”之后设置一个断点,运行程序,提供7 A,最后调查堆。

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

一旦找到突破点,我就能检查堆。在这种情况下,我有两个块,如下图所示,some_string在它的边界内,some_number等于0x4d2。

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

现在,让我来试试16 A,看看会发生什么。

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

你可能已经猜到,提供太多的输入会导致溢出并发生相邻块的损坏。 在这种情况下,确实,经过验证,我看到我的用户输入损坏了头部和some_number字段的第一个字节。 被破坏后,我可以达到代码部分的some_number,但是按着逻辑,不应该达到这个代码段。

Linux环境中堆栈和堆相关内存损坏的基本原理和调试方法介绍

总结

读完本文,你应该会熟悉进程内存布局和堆栈相关内存损坏的基础知识, 在下一篇中,我会继续介绍其他内存损坏,比如悬垂指针和格式化字符串。 




原文发布时间为:2017年7月21日
本文作者:luochicun
本文来自云栖社区合作伙伴嘶吼,了解相关信息可以关注嘶吼网站。
相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
5天前
|
存储 安全 iOS开发
内存卡怎么格式化?6个格式化方法供你选
随着使用时间的增加,内存卡可能会因为数据积累、兼容性或是文件系统损坏等原因需要进行格式化。那么怎样正确格式化内存卡呢?格式化内存卡的时候需要注意什么呢?本文会给大家提供详细的步骤,帮助大家轻松完成格式化内存卡的操作。
|
18天前
|
Ubuntu Linux Shell
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
(已成功解决)Linux环境报错—bash: wget: command not found;常见Linux发行版本,Linux中yum、rpm、apt-get、wget的区别;Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
195 68
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
|
15天前
|
消息中间件 Java Kafka
【手把手教你Linux环境下快速搭建Kafka集群】内含脚本分发教程,实现一键部署多个Kafka节点
本文介绍了Kafka集群的搭建过程,涵盖从虚拟机安装到集群测试的详细步骤。首先规划了集群架构,包括三台Kafka Broker节点,并说明了分布式环境下的服务进程配置。接着,通过VMware导入模板机并克隆出三台虚拟机(kafka-broker1、kafka-broker2、kafka-broker3),分别设置IP地址和主机名。随后,依次安装JDK、ZooKeeper和Kafka,并配置相应的环境变量与启动脚本,确保各组件能正常运行。最后,通过编写启停脚本简化集群的操作流程,并对集群进行测试,验证其功能完整性。整个过程强调了自动化脚本的应用,提高了部署效率。
【手把手教你Linux环境下快速搭建Kafka集群】内含脚本分发教程,实现一键部署多个Kafka节点
|
2月前
|
监控 JavaScript Java
Node.js中内存泄漏的检测方法
检测内存泄漏需要综合运用多种方法,并结合实际的应用场景和代码特点进行分析。及时发现和解决内存泄漏问题,可以提高应用的稳定性和性能,避免潜在的风险和故障。同时,不断学习和掌握内存管理的知识,也是有效预防内存泄漏的重要途径。
172 52
|
2月前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
57 6
|
2月前
|
缓存 Ubuntu Linux
Linux环境下测试服务器的DDR5内存性能
通过使用 `memtester`和 `sysbench`等工具,可以有效地测试Linux环境下服务器的DDR5内存性能。这些工具不仅可以评估内存的读写速度,还可以检测内存中的潜在问题,帮助确保系统的稳定性和性能。通过合理配置和使用这些工具,系统管理员可以深入了解服务器内存的性能状况,为系统优化提供数据支持。
50 4
|
2月前
|
关系型数据库 MySQL Linux
Linux环境下MySQL数据库自动定时备份实践
数据库备份是确保数据安全的重要措施。在Linux环境下,实现MySQL数据库的自动定时备份可以通过多种方式完成。本文将介绍如何使用`cron`定时任务和`mysqldump`工具来实现MySQL数据库的每日自动备份。
154 3
|
2月前
|
监控 关系型数据库 MySQL
Linux环境下MySQL数据库自动定时备份策略
在Linux环境下,MySQL数据库的自动定时备份是确保数据安全和可靠性的重要措施。通过设置定时任务,我们可以每天自动执行数据库备份,从而减少人为错误和提高数据恢复的效率。本文将详细介绍如何在Linux下实现MySQL数据库的自动定时备份。
71 3
|
2月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
161 8
|
2月前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
640 6

热门文章

最新文章