渗透测试——内存攻击原理

什么是内存攻击

内存攻击指的是攻击者利用软件安全漏洞,构造恶意输入导致软件在处理输入数据时出现非预期错误,将输入数据写入内存中的某些特定敏感位置,从而劫持软件控制流,转而执行外部输入的指令代码,造成目标系统被获取远程控制或拒绝服务。
内存攻击的表面原因是软件编写错误,诸如过滤输入的条件设置缺陷、变量类型转换错误、逻辑判断错误、指针引用错误等;但究其根本原因,是现代电子计算机在实现图灵机模型时,没有在内存中严格区分数据和指令,这就存在外部输入数据成为指令代码从而被执行的可能。任何操作系统级别的防护措施都不可能完全根除现代计算机体系结构上的这个弊端,只能试图去阻止攻击者利用 (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”,分别指向双向链表中此堆块的前后两个堆块的数据部分。分配一个堆块时,将分配堆块从空闲堆块双链表中删除,会有如下所示的操作:

void DeleteBlock(DListBlock *p)
{
	p->next->previous = p->previous;
	p->previous->next = p->next;
}

在这里插入图片描述

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


摘自:《Metasploit渗透测试魔鬼训练营》

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值