百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

渗透测试——内存攻击原理(内存破坏漏洞)

toyiye 2024-09-08 10:02 3 浏览 0 评论

什么是内存攻击

内存攻击指的是攻击者利用软件安全漏洞,构造恶意输入导致软件在处理输入数据时出现非预期错误,将输入数据写入内存中的某些特定敏感位置,从而劫持软件控制流,转而执行外部输入的指令代码,造成目标系统被获取远程控制或拒绝服务。

内存攻击的表面原因是软件编写错误,诸如过滤输入的条件设置缺陷、变量类型转换错误、逻辑判断错误、指针引用错误等;但究其根本原因,是现代电子计算机在实现图灵机模型时,没有在内存中严格区分数据和指令,这就存在外部输入数据成为指令代码从而被执行的可能。任何操作系统级别的防护措施都不可能完全根除现代计算机体系结构上的这个弊端,只能试图去阻止攻击者利用 (Exploit)。

缓冲区溢出漏洞机理

缓冲区溢出 (Buffer Overflow 或 Buffer Overrun) 漏洞是程序由于缺乏对缓冲区边界条件检查而引起的一种异常行为。通常是程序向缓冲区中写数据,但内容超过了程序员设定的缓冲区边界,从而覆盖了相邻的内存区域,造成覆盖程序中的其他变量甚至影响控制流的敏感数据,造成程序的非预期行为。而 C 和 C++ 语言由于缺乏内在安全的内存分配与管理机制,因此很容易导致缓冲区溢出的相关问题。

如下图:内存中保存了相邻的两个变量。A 是 char[] 字符串类型,作为缓冲区用于存储外部输入的字符串,长度为 8 字节;而变量 B 是短整数型。

在程序执行时,某指令向 A 中写入了长度大于 8 的字符串,越过了 A 的边界覆盖了 B 中的内容,造成变量 B 的值被修改。如:写入的字符串是 “abcdefghi”,长度为 9,加上结束符 “\0” 之后将修改 B 的值,从原先的 65535 修改为 0x0069 (十六进制),即105。

一般根据缓冲区溢出的内存位置不同,将缓冲区溢出又分为 栈溢出 (Stack Overflow)堆溢出 (Heap Overflow)

栈溢出利用原理

程序执行过程中的栈,是由操作系统创建和维护的,同时也支持了程序内的函数调用功能。在进行函数调用时,程序会将返回地址压入栈中,而执行完被调用的函数代码后,则会通过 ret 指令从栈中弹出返回地址,装载到 EIP 指令寄存器,从而继续程序运行。

然而这种将控制程序流程的敏感数据与程序变量同时保存在同一段内存空间中的冯诺依曼体系,必然会给缓冲区溢出攻击带来本质上的可行性。

栈溢出发生在程序向位于栈中的内存地址写数据时,当写入的数据长度超过栈分配给缓冲区的空间时,就会造成栈溢出。从栈溢出的原理出发,攻击者可以找到如下三种方式来利用这种类型的漏洞:

  • 覆盖缓冲区附近的程序变量:改变程序的执行流程和结果
  • 覆盖栈中保存的函数返回地址:修改为攻击者指定的地址,当程序返回时,程序流程将跳转到攻击者指定地址,理想情况下可以执行任意代码
  • 覆盖某个函数指针或程序异常处理结构:只要溢出之后目标函数或异常处理例程被执行,同样可以让程序流程跳转到任意地址

而其中最常见的利用方式就是覆盖栈中的函数返回地址。

1. 覆盖函数返回地址利用方式

函数调用是程序中最常见的命令,程序调用函数时,程序流程将暂时转到被调用的函数,函数执行完之后再跳转回原来的位置,所以在执行调用函数前需要保存下一跳指令的地址,让程序在执行完函数调用后能够从这个指令地址处继续执行。若程序将该函数返回地址和函数的调用参数、局部变量异同保存在栈中,这就给了攻击者溢出栈缓冲区从而达到修改函数返回地址的机会。

栈溢出代码示例:

#inclide <string.h>
void foo(char *bar)
{
	char c[8];
	strcpy(c, bar);	//没有进行边界检查,从而存在栈溢出漏洞
}
int main()
{
	char array[] = "ABCDABCDABCD\x18\xFF\x18\x00"
	foo(array);	//调用函数
	return 0;
}

主程序执行了一次对自定义函数 foo 的调用,在子函数中执行了一次字符串复制操作 (strcpy),执行结果是字符串 array 中的内容被复制到局部变量字符串 c 中。

编译执行这一段代码,当调用 strcpy 函数时,进程栈布局如下图左侧所示,从地地址到高地址分别是未分配的栈内存空间、局部变量字符串 c 分配的空间、进入子函数时自动保存的 EBP 寄存器值、返回地址、调用自定义函数 foo 的参数、父函数栈空间。执行 strcpy 函数之后的栈布局如下图右侧所示,上述代码中,源字符串 array 中的16 个字符将复制到 c 中,其中最后 4 个字符覆盖了栈中返回地址,该为字符串 c 的起始地址 0x0018FF18。所以当函数 foo 返回时,程序就会跳转到该地址处,将字符串中的数据作为指令执行。因此,如果攻击者可以控制源字符串,那么他就可以将其替换为 Shellcode 并复制到栈中,然后利用该漏洞执行 Shellcode。

由于程序每次运行时,栈中变量的地址都会发生变化,即上述字符串 c 在栈中位置往往不固定,所以一般会通过一些跳转寄存器的指令作为跳板,使得程序能够执行到栈中的 Shellcode。最常见的是以 JMP ESP 的地址来覆盖返回地址,从而使得程序执行该指令之后重新跳转回栈中,来执行缓冲区溢出之后的数据。

2. 覆盖异常处理结构利用方式

程序在运行过程中可能会发生一些异常,比如除 0 计算、访问无效内存地址等,此时就需要正常指令序列之外的代码处理这些异常。Windows 提供结构化异常处理机制 SEH,可以利用程序自定义的异常处理函数或者操作系统默认的处理函数处理异常。异常处理结构以链表形式春促在栈中,寄存器 FS 指向当前活动线程的 TEB (线程环境块) 结构。结构体有两个 DWORD (双字变量,4字节) 类型的变量:

  • 指向下一异常处理结构体的指针
  • 异常处理函数 SEH 例程的地址

程序在执行生产指令序列出现异常时,会由异常处理过程接管,操作系统从链表头到尾寻找能处理此异常的函数,由找到的第一个函数进行处理。如果没有任何合适的处理函数,则由最后一个函数即系统默认的处理函数来负责处理,通常弹出错误对话框,强制关闭程序。

栈溢出之后覆盖异常处理结构的利用方式,就是用特定地址覆盖栈中异常处理结构体中的异常处理函数指针,并处罚异常,导致去加载篡改之后的处理函数指针。

覆盖异常处理结构的栈溢出利用方式,与覆盖栈中函数返回地址利用方式并没有本质区别。一般来说,异常处理结构接近栈底,所以从缓冲区头部到异常处理结构之间的内存空间很大,利用起来可能更方便。最关键的是,有时缓冲区溢出之后到程序执行到函数返回之前就不可避免地处罚异常,这种情况下,就必须使用覆盖异常处理结构的利用方式,于此同时,这种利用方式也可以绕过操作系统的栈保护机制。

堆溢出利用原理

不同于栈,堆是程序运行时动态分配的内存,用户通过 malloc、new 等函数申请内存,通过返回的起始地址指针对分配的内存进行操作,使用完后要通过 free、delete 等函数释放这部分内存,否则会造成内存泄露。对的操作分为分配、释放、合并三种。因为堆在内存中位置不固定,大小比较自由,多次申请、释放后可能会更加凌乱,系统从性能、空间利用率还有越来越受到重视的安全角度出发,来管理堆,具体实现比较复杂。只简介最常见的空闲块堆操作引起的缓冲区溢出

系统根据大小不同维护一系列的堆块,如下图所示:堆块分为块首和数据区,其中空闲堆块数据区的前两个双字 (DWORD) 分别是双向链表的两个指针。通常同样大小的空闲堆块通过双向链表连接在一起,分配与释放堆,分别对应插入与删除双向链表节点的操作,而合并则会同时进行着两种操作。空闲堆块中两个指针 “Previous block” 和 “Next block”,分别指向双向链表中此堆块的前后两个堆块的数据部分。分配一个堆块时,将分配堆块从空闲堆块双链表中删除,会有如下所示的操作:

同一个堆中的堆块在内存中通常是连续的,由此可能发生的状况是:在向一个已分配堆块中写入数据时,由于数据长度超出了该堆块的大小,导致数据溢出覆盖堆块后方 (高地址处) 的相邻空闲堆块,而包含的两个堆块指针 (即 Previous block 和 Next block) 会被覆盖。

假设有空闲堆块 *p,则 p->previous 是指向双向链表中的 p 的前一堆块的前向指针,p->next 是后向指针。若 p 的两个堆块指针被覆盖,即 p->previous = Xp->next = Y。如果这个空闲堆块被分配出去,需要将这个节点从空闲堆块链表中删除,那么分配过程中的 DeleteBlock 函数 (上述函数) 就会将 p 下一个空闲的前向指针 (p->next->previous)指向 p 之前的空闲块前向指针 (p->previous)。需要注意的是:每个堆块指针指向的就是堆块的 Previous block。所以 p->next->previous 相当于对 Y 进行解引用,即 *Y,因此执行的效果就是 *Y=X。从而可以利用超长数据覆盖空闲堆块的这两个指针,达到向 Y 指向的任意地址处写入 X 包含的任意内容的目的。

涉及内存链表操作的堆内存分配、释放、合并操作都可能实现这一效果,即向攻击者任意指定地址写入 4 字节的任意内容,业内人士称之为 “arbitrary DWORD reset” 或者 “DWORD shoot” 攻击。在得到一个将指定内存地址改写为任意值的机会后,攻击者可以写出利用的程序,用于覆盖内存堆中的一些函数指针地址、C++ 类对象虚函数表、GOT 全局偏移表入口地址或者 DTORS 地址等,而改写的值就是指向内存中的 Shellcode 的地址。

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码