进程内存和内存损坏

本教程的这一部分的先决条件是对ARM汇编的基本了解(在第一个教程系列“ ARM汇编基础 ”中有介绍)。在本章中,您将了解32位Linux环境中进程的内存布局。之后,您将学习堆栈和堆相关的内存损坏的基本原理,以及它们在调试器中的样子。

  1. 缓冲区溢出

    • 堆栈溢出
    • 堆溢出
  2. 摇摇欲坠的指针
  3. 格式字符串

本教程中使用的示例是在ARMv6 32位处理器上编译的。如果您无法访问ARM设备,则可以按照以下教程创建自己的实验室并在VM中模拟Raspberry Pi发行版:使用QEMU模拟Raspberry Pi。此处使用的调试器是具有GEF(GDB增强功能)的GDB。如果您对这些工具不熟悉,可以查看本教程:使用GDB和GEF进行调试

内存布局的过程

每次我们启动一个程序,该程序的内存区域都被保留。这个地区然后分裂成多个地区。这些地区然后分裂成更多地区(细分市场),但我们将坚持总体概述。所以,我们感兴趣的部分是:

  1. 程序图像

在下面的图片中,我们可以看到这些部分是如何在流程内存中进行布置的一般表示。用于指定内存区域的地址仅仅是为了举例,因为它们会因环境而异,特别是在使用ASLR时。

程序图像区域基本上保存了程序的可执行文件,该文件被加载到内存中。这个内存区域可以分成不同的部分:.plt,.text,.got,.data,.bss等等。这些是最相关的。例如,.text包含程序的可执行部分以及所有程序集指令,.data和.bss包含应用程序中使用的变量的变量或指针,.plt和.got存储指向各种导入函数的特定指针,for例如,共享库。从安全角度来看,如果攻击者可能影响.text部分的完整性(重写),他可以执行任意代码。同样,程序链接表(.plt)和全局偏移表(.got)的损坏可能会在特定情况下导致执行任意代码。

的栈和堆区域是由应用程序使用来存储和对被该程序的执行期间使用的临时数据(变量)进行操作。攻击者通常会利用这些区域,因为堆栈和堆区域中的数据通常可以通过用户的输入进行修改,如果处理不当,可能会导致内存损坏。我们将在本章后面介绍这种情况。

除了内存的映射之外,我们需要知道与不同内存区域相关的属性。内存区域可以具有以下属性中的一个或组合:R ead,W rite,e X ecute。该读取属性允许程序从一个特定区域读取数据。同样,Write允许程序将数据写入特定的内存区域,并执行 - 执行该内存区域中的指令。我们可以看到GEF中的进程内存区域(强烈推荐的GDB扩展),如下所示:

azeria @ labs:?/ exp $ gdb程序
...
gef> gef config context.layout“code”
 gef> break main
断点1在0x104c4:文件program.c,第6行。
gef>运行
...
gef> nexti 2
-------------------------------------------------- --------------------------------------- [code:arm] ----
...
      0x104c4 <main + 20> mov r0,#8
      0x104c8 <main + 24> bl 0x1034c <malloc @ plt>
- >     0x104cc <main + 28> mov r3,r0
      0x104d0 <主+ 32> str r3,[r11,#-8]
...
gef> vmmap
开始      结束         偏移      Perm路径
0x00010000 0x00011000 0x00000000 rx / home / azeria / exp / program <---- Program Image
0x00020000 0x00021000 0x00000000 rw- / home / azeria / exp / program <---- Program Image continue ...
0x00021000 0x00042000 0x00000000 rw- [堆] <---- HEAP
0xb6e74000 0xb6f9f000 0x00000000 rx /lib/arm-linux-gnueabihf/libc-2.19.so <----共享库(libc)
0xb6f9f000 0xb6faf000 0x0012b000 --- /lib/arm-linux-gnueabihf/libc-2.19.so <---- libc继续...
0xb6faf000 0xb6fb1000 0x0012b000 r-- /lib/arm-linux-gnueabihf/libc-2.19.so <---- libc继续...
0xb6fb1000 0xb6fb2000 0x0012d000 rw- /lib/arm-linux-gnueabihf/libc-2.19.so <---- libc继续...
0xb6fb2000 0xb6fb5000 0x00000000 rw-
0xb6fcc000 0xb6fec000 0x00000000 rx /lib/arm-linux-gnueabihf/ld-2.19.so <----共享库(ld)
0xb6ffa000 0xb6ffb000 0x00000000 rw-
0xb6ffb000 0xb6ffc000 0x0001f000 r-- /lib/arm-linux-gnueabihf/ld-2.19.so <---- ld继续...
0xb6ffc000 0xb6ffd000 0x00020000 rw- /lib/arm-linux-gnueabihf/ld-2.19.so <---- ld继续...
0xb6ffd000 0xb6fff000 0x00000000 rw-
0xb6fff000 0xb7000000 0x00000000 rx [sigpage]
0xbefdf000 0xbf000000 0x00000000 rw- [stack] <---- STACK
0xffff0000 0xffff1000 0x00000000 rx [vectors]

vmmap命令输出中的Heap部分仅在使用了一些Heap相关函数后才会出现。在这种情况下,我们看到   正在使用malloc函数在堆区域中创建一个缓冲区。所以如果你想尝试一下这个,你需要调试一个调用malloc的程序(你可以在这个页面找到一些例子,向下滚动或者使用find函数)。

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

azeria @ labs:?/ exp $ ps aux | grep程序
azeria 31661 12.3 12.1 38680 30756 pts / 0 S + 23:04 0:10 gdb program
azeria    31665   0.1 0.2 1712 748 pts / 0 t 23:04 0:00 / home / azeria / exp / program
azeria 31670 0.0 0.7 4180 1876 pts / 1 S + 23:05 0:00 grep --color = auto program
azeria @ labs:?/ exp $ cat / proc / 31665 / maps
00010000-00011000 r-xp 00000000 08:02 274721      / home / azeria / exp / program
00020000-00021000 rw -p 00000000 08:02 274721      / home / azeria / exp / program
00021000-00042000 rw-p 00000000 00:00 0           [堆]
b6e74000-b6f9f000 r-xp 00000000 08:02 132394 /lib/arm-linux-gnueabihf/libc-2.19.so
b6f9f000-b6faf000 --- p 0012b000 08:02 132394 /lib/arm-linux-gnueabihf/libc-2.19.so
b6faf000-b6fb1000 r  -  p 0012b000 08:02 132394 /lib/arm-linux-gnueabihf/libc-2.19.so
b6fb1000-b6fb2000 rw-p 0012d000 08:02 132394 /lib/arm-linux-gnueabihf/libc-2.19.so
b6fb2000-b6fb5000 rw-p 00000000 00:00 0
b6fcc000-b6fec000 r-xp 00000000 08:02 132358 /lib/arm-linux-gnueabihf/ld-2.19.so
b6ffa000-b6ffb000 rw-p 00000000 00:00 0
b6ffb000-b6ffc000 r  -  p 0001f000 08:02 132358 /lib/arm-linux-gnueabihf/ld-2.19.so
b6ffc000-b6ffd000 rw-p 00020000 08:02 132358 /lib/arm-linux-gnueabihf/ld-2.19.so
b6ffd000-b6fff000 rw-p 00000000 00:00 0
b6fff000-b7000000 r-xp 00000000 00:00 0 [sigpage]
befdf000-bf000000 rw-p 00000000 00:00 0           [stack]
ffff0000-ffff1000 r-xp 00000000 00:00 0 [矢量]

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

记忆腐败简介

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

  1. 缓冲区溢出

    • 堆栈溢出
    • 堆溢出
  2. 悬挂指针(免费使用)
  3. 格式字符串

在本章中,我们将尝试熟悉缓冲区溢出内存损坏漏洞的基础知识(其余部分将在下一章中介绍)。在我们将要讨论的例子中,我们将会看到,内存损坏漏洞的主要原因是用户输入验证不正确,有时还会有一个逻辑缺陷。对于一个程序来说,输入(或者恶意负载)可能以用户名,文件被打开,网络包等形式出现,并且通常会受到用户的影响。如果程序员没有把潜在有害的用户输入的安全措施,目标程序经常会遇到某种内存相关的问题。

缓冲区溢出

缓冲区溢出是最普遍的内存损坏类之一,通常是由于编程错误导致用户提供的数据多于目标变量(缓冲区)的数据量。例如,在使用易受攻击的函数(如get,strcpy,memcpy等)以及用户提供的数据时,会发生这种情况。这些函数不会检查用户数据的长度,这可能会导致写入(溢出)分配的缓冲区。为了更好地理解,我们将研究 基于堆栈和堆的缓冲区溢出的基础知识。

堆栈溢出

正如名称所示,堆栈溢出是影响堆栈的内存破坏。尽管在大多数情况下,堆栈的任意损坏很可能导致程序崩溃,但精心设计的堆栈缓冲区溢出会导致任意代码执行。下图显示了堆栈如何被损坏的抽象概述。

如上图所示,堆栈帧(整个堆栈专用于特定功能的一小部分)可以具有各种组件:用户数据,先前的帧指针,先前的链接寄存器等。如果用户也提供控制变量的大部分数据,FP和LR字段可能会被覆盖。这会中断程序的执行,因为用户破坏了当前函数完成后应用程序将返回/跳转的地址。

为了检查它在实践中的样子,我们可以使用这个例子:

/ * azeria @ labs:?/ exp $ gcc stack.c -o stack * /
#include“stdio.h”

int main(int argc,char ** argv)
{
字符缓冲区[8];
得到(缓冲液);
}

我们的示例程序使用变量“buffer”,长度为8个字符,并为用户输入一个函数“gets”,它简单地将变量“buffer”的值设置为用户提供的任何输入。这个程序的反汇编代码如下所示:

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

当我们调查我们的例子的堆栈时,我们看到(上图)堆栈帧没有被破坏。这是因为用户提供的输入符合预期的8字节缓冲区,并且堆栈帧中的前一个FP和LR值没有被破坏。现在我们提供16个A,看看会发生什么。

在第二个例子中,我们看到(上图),当我们为函数“gets”提供太多的数据时,它不会停留在目标缓冲区的边界处,而是一直写下“堆栈”。这导致我们以前的FP和LR值被破坏。当我们继续运行程序时,程序崩溃(导致“分段错误”),因为在当前函数的结尾处,FP和LR的先前值被“堆栈”到R11和PC寄存器中,迫使程序跳转到地址0x41414140(由于切换到Thumb模式,最后一个字节自动转换为0x40),在这种情况下,这是非法地址。下面的图片显示了在崩溃时寄存器的值(看看$ pc)。

堆溢出

首先,堆是一个更复杂的内存位置,主要是因为它的管理方式。为了简单起见,我们坚持这样一个事实,即放在堆内存部分的每个对象都被“打包”成一个包含头部和用户数据(有时用户完全控制)两部分的“块”。在堆的情况下,当用户能够写入比预期更多的数据时,会发生内存损坏。在这种情况下,损坏可能发生在块的边界内(块内堆溢出),或跨两个(或更多)块的边界(块间堆溢出)发生。为了更好地理解,下面我们来看看下面的插图。

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

块内堆溢出

为了演示块内堆溢出在实际中的样子,我们可以使用下面的例子,并用“-O”(优化标志)编译它,使其具有更小的(二进制)程序(更易于查看)。

/ * azeria @ labs:?/ exp $ gcc intra_chunk.c -o intra_chunk -O * /
#include“stdlib.h”
#include“stdio.h”

struct u_data //对象模型:名称为8个字节,数字为4个字节
{
 char name [8];
 int数字
};

int main(int argc,char * argv [])
{
 struct u_data * objA = malloc(sizeof(struct u_data)); //在堆中创建对象

 objA-> number = 1234; //将我们的对象的数量设置为一个静态值
 得到(objA->名); //根据用户的输入设置我们的对象的名字

 if(objA-> number == 1234)//检查静态值是否完整
 {
  puts(“Memory valid”);
 }
 否则//在静态值被破坏的情况下继续
 {
  放(“内存损坏”);
 }
}

上述程序执行以下操作:

  1. 用两个字段定义数据结构(u_data)
  2. 创建类型为u_data的对象(在堆内存区域中)
  3. 将静态值分配给对象的数字字段
  4. 提示用户为对象的名称字段提供一个值
  5. 根据数字字段的值打印字符串

所以在这种情况下,我们也怀疑贪污可能发生在“获取”功能之后。我们分解目标程序的主要功能来获取断点的地址。

在这种情况下,我们在地址0x00010498处设置断点 - 在函数“gets”完成之后。我们配置GEF只显示我们的代码。然后我们运行该程序并提供7个A作为用户输入。

一旦中断点被击中,我们快速查找我们的程序的内存布局,以找到我们的堆是在哪里。我们使用vmmap命令,看到我们的Heap从地址0x00021000开始。考虑到我们的对象(objA)是程序创建的第一个也是唯一的对象,我们将从头开始分析堆。

上面的图片向我们展示了与我们的对象相关的Heap块的详细分解。该块有一个头(8字节)和用户的数据部分(12字节)存储我们的对象。我们看到name字段正确存储了提供的7个字符串,以空字节结尾。数字字段存储0x4d2(十进制数1234)。到现在为止还挺好。让我们重复这些步骤,但在这种情况下输入8个A的。

在这次检查堆时,我们看到数字的字段被破坏了(现在等于0x400而不是0x4d2)。空字节终结符覆盖了数字字段的一部分(最后一个字节)。这会导致内部块堆内存损坏。在这种情况下,这种腐败的影响并不是破坏性的,而是可见的。从逻辑上讲,代码中的else语句永远不会达到,因为数字的字段是静态的。但是,我们刚刚观察到的内存损坏使得可能达到这部分代码。这可以通过下面的示例轻松确认。

块间??堆溢出

为了说明在实际中如何实现块间堆溢出,我们可以使用下面的例子,我们现在编译时没有优化标记。

/ * azeria @ labs:?/ exp $ gcc inter_chunk.c -o inter_chunk * /
#include“stdlib.h”
#include“stdio.h”

int main(int argc,char * argv [])
{
 char * some_string = malloc(8); //在堆中创建some_string“对象”
 int * some_number = malloc(4); //在堆中创建some_number“object”

 * some_number = 1234; //为some_number分配一个静态值
 得到(some_string); //请求用户输入some_string

 如果(* some_number == 1234)//检查(some_number的)静态值是否有效
 {
 puts(“Memory valid”);
 }
 否则//在静态some_number被破坏的情况下继续
 {
 放(“内存损坏”);
 }
}

这里的过程和以前的过程类似:在函数“gets”之后设置一个断点,运行程序,提供7个A,调查Heap。

一旦中断点被击中,我们检查堆。在这种情况下,我们有两个块。我们看到(下图),它们的结构是一致的:some_string在其边界内,some_number等于0x4d2。

现在,我们提供16个A,看看会发生什么。

正如您可能已经猜到的那样,提供太多的输入会导致溢出导致相邻块的损坏。在这种情况下,我们看到我们的用户输入损坏了头部和some_number字段的第一个字节。再次,通过破坏some_number我们设法达到逻辑上永远不应该达到的代码部分。

概要

在本教程的这一部分,我们熟悉了进程内存布局以及堆栈和堆相关内存损坏的基础知识。在本教程系列的下一部分,我们将介绍其他内存损坏:悬挂指针和格式字符串。一旦我们覆盖了最常见的内存损坏类型,我们将准备好学习如何编写工作漏洞。

时间: 2024-10-10 00:00:42

进程内存和内存损坏的相关文章

进程通信之内存共享篇

进程通信之_ 内存共享 概念:共享内存是被多个进程共享的一部分物理内存.共享内存是进程间的共享数最快的方法,一个进程向共享内存区域写入数据,共享这个内存区域的所有进程就可以写入数据,所有进程就可以立刻看到其中的内容. 实现步骤;1.创建共享内存,使用shmget函数2.映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数 创建:int shmget (key_t key,int size,int shmflg)key 标识内存功效的键值0/ipc_private.成功返回

进程管理、内存管理、存储管理初步了解

进程管理 进程需要一定的资源(包括CPU时间.内存.文件.I/O设备)以完成其任务.这些资源可以在进程创建时分配给进程,也可以在执行进程时分配给进程.除了在创建时得到各种物理和逻辑资源外,进程还可以接受传输过来的各种初始化数据(输入). 需要注意:程序本省并不是进程,程序是被动的实体,而进程是一个活动的实体. 进程与线程的定义: 进程是具有一定独立功能的程序关于某个数据集合上的依次运行活动,进程是系统进行资源分配和调度的独立单位. 线程是进程的一个实体,是CPU和分配的基本单位.线程自己基本不拥

Windows进程通信 -- 共享内存

享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信.因为是通过内存操作实现通信,因此是一种最高效的数据交换方法. 共享内存在 Windows 中是用 FileMapping 实现的,从具体的实现方法上看主要通过以下几步来实现: 1.调用 CreateFileMapping 创建一个内存文件映射对象: HANDLE CreateFileMapping( HANDLE hFile, // handle to file to map

查看进程占用的内存情况

可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看java用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器 可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看tomcat用户的进程内存使用情况的话可以使用如下的命令: $ top -u tomcat 内容解释: PID:进程的ID    US

进程结束后,进程的所有内存都将被释放,包括堆上的内存泄露的内存。

进程结束后,进程的所有内存都将被释放,包括堆上的内存泄露的内存.原因是,当进程结束时,GDT.LDT和页目录都被操作系统更改,逻辑内存全部消失,可能物理内存的内容还在但是逻辑内存已经从LDT和GDT删除,页目录表全部销毁,所以内存会被全部收回. 一下这段代码,我个人认为可以作为例证,不对请多指教: [c-sharp] view plaincopy #include <cstdlib> using namespace std; const static int const_once = 1024

Android获得全局进程信息以及进程使用的内存情况

Android获得全部进程信息,并获得该进程使用的内存情况. package zhangphil.process; import java.util.List; import android.os.Bundle; import android.os.Debug.MemoryInfo; import android.widget.TextView; import android.app.Activity; import android.app.ActivityManager; import and

Android之——获取进程总数、内存、任务列表

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/47263863 如今,基于Android平台的应用越来越多,不乏这样一些应用程序,这些应用可以多手机中的进程,内存和任务列表进行管理.Android系统中没有自带的应用程序对这些数据进行管理.大家不禁会说,Android的应用设置里面有这些功能啊!是的,虽然应用设置里面有,但是我们如果想看一下系统的进程,还要跑到系统设置里面去看吗?这样是不是很麻烦?是不是很挫呢?那获取Androi

Shell练习-计算一下所有进程所占用内存大小的和

ps 可以查看进程的内存占用大小,写一个脚本计算一下所有进程所占用内存大小的和.(提示,使用ps aux 列出所有进程,过滤出RSS那列,然后求和) #!/bin/bash #Calculate how much memory is occupied by this process #2015/07/22  ps aux |awk -F' ' '{(sum=sum+$6)};END{print sum}'

【UNIX】内核对内核空间内存的管理以及对用户进程的堆内存分布

常用的内核内存分配函数 1)_get_free_pages是最原始的内存分配方式,至二级从伙伴系统中获取原始页框,返回值为第一个页框的起始地址._get_free_pages在实现上只是封装了alloc_pages函数,而alloc_pages分配的长度为1< 2)Kmem_cache_alloc是基于slab分配器的一种内存分配方式,适用于反复分配释放同一大小内存的场合.首先用Kmem_cache_alloc从该高速缓存区域中获取新的内存块.在2.6内核中一次最多能申请1<< 5*4K

一起talk C栗子吧(第一百三十三回:C语言实例--创建进程时的内存细节)

各位看官们,大家好,上一回中咱们说的是从内存角度看进程和线程的例子,这一回咱们说的例子是:创建进程时的内存细节.闲话休提,言归正转.让我们一起talk C栗子吧! 看官们,我们都知道使用fork函数可以创建一个新的进程,今天我们一起说一下使用该函数创建进程时内存的一些细节问题.我们介绍的的重点还是内存布局中的四个分区. 1.在父进程中使用fork时,会创建一个新的进程,我们叫它子进程,子进程有自己的内存空间: 2.子进程的内存空间中没有代码区,子进程和父进程共享代码区: 3.子进程的内存空间中拥