堆栈上的舞蹈之释放重引用(UAF) 漏洞原理实验分析
0x01 前言
- 释放重引用的英文名名称是 Use After Free,也就是著名的 UAF 漏洞的全称。从字面意思可以看出 After Free 就是释放后的内存空间,Use 就是使用的意思,使用释放后的内存空间。该漏洞的历史可以追溯到 2005 年,当时第一个 UAF 漏洞编号为 CVE-2005-4360,到 2008 年之后结合有关技术已经能稳定的利用了
- 鉴于 UAF 漏洞的特殊性(需要精确的控制堆内存空间的覆盖),所以该漏洞较为成功的利用多出现在浏览器中,因为浏览器可以运行 JavaScript 代码来申请堆空间,而其他软件利用起来则非常的困难

0x02 UAF 漏洞原理
- 很多程序员在编写程序的时候会使用 new 或者 malloc 等方式动态的申请堆空间来存放数据,之后再使用 delete 或者 free 来释放掉。一般来说这样的方式并不会出现什么问题,但是如果程序员粗心,二次使用释放后堆空间的悬挂指针,那么就会造成安全隐患
- 举个例子
#include <stdio.h>
#define size 32
int main(int argc, char **argv)
{
// 申请两个 char 类型的指针
char *buf1; char *buf2;
// 动态申请一个大小为 size 的堆空间
buf1 = (char *)malloc(size);
// 打印出 buf1 的指针指向的地址
printf("buf1: 0x%p\n", buf1);
// 释放 buf1 的堆
free(buf1);
// 动态申请一个大小为 size 的堆空间
buf2 = (char *)malloc(size);
// 打印出 buf2 的指针指向的地址
printf("buf2: 0x%p\n", buf2);
// 将 buf2 的堆空间赋值为 0
memset(buf2, 0, size);
printf("buf2: %d \n", *buf2);
printf("=== User After Free === \n");
// 将 buf1 指向的内存拷贝为 hack
strncpy(buf1, "hack", 5);
printf("buf2: %s\n\n", buf2);
// 释放 buf2 的堆空间
free(buf2);
return 0;
}
- 首先这个程序首先使用 malloc 申请了第一个堆空间 buf1,之后释放了第一个堆空间的地址(注意是释放第一个堆空间的地址,并没有销毁第一个堆空间的指针,所以在释放第一个堆空间地址之后该指针就会变为悬挂指针,或者说老一辈的 C++ 程序员称之为野指针),然后再申请了第二个堆空间 buf2 并且使用 strncpy 函数将第一个堆空间指针 buf1 赋值为字符串 hack (这个过程就称之为释放重引用) ,但是由于 ‘‘占坑效应’’,实际上改变的是第二个堆空间的内存数据
- 下面就是运行的结果,由于占坑效应,两个堆空间的地址是一样的,并且在释放重引用之后,第二个申请的堆空间内存数据被改为了 hack

注:堆空间的占坑是指在第一个堆空间申请并释放之后,申请第二个堆空间会占用第一个堆空间的地址,简单来说就是两个堆空间的地址是一样的,申请的堆空间释放之后留给下面申请的堆空间使用。当然这个也是有条件的,需要依据操作系统的分配原则,如果申请的堆空间大小一样的话,占坑的成功率会大很多
0x03 结合虚表指针执行任意代码
- 以上这个例子只是证明了释放重引用的原理和可以通过悬挂指针修改内存数据,但是并不可以执行任意代码啊。要想执行任意代码还需要结合一样东西,那就是虚标指针,虚标指针是类中的一个概念,类的初始化会自动的添加虚表指针,并且当调用类中的虚函数时,会查阅虚表指针,如果释放重引用覆盖的内存空间的地址刚好为虚表指针的话,那么就可以控制虚表指针去执行任意代码,例子如下:
#include <iostream>
#include <stdio.h>
using namespace std;
class CTest
{
int one = 10;
public:
virtual void vFun1();
virtual void vFun2();
int getOne()
{
return one;
}
};
void CTest::vFun1()
{
cout << "I am vFun1" << endl;
}
void CTest::vFun2()
{
cout << "I am vFun2" << endl;
}
int main(int argc, char **argv)
{
CTest *test1 = new CTest;
cout << "test1 指针指向的堆地址为: " << test1 << endl;
delete test1;
CTest *test2 = new CTest;
cout << "test2 指针指向的堆地址为: " << test2 << endl;
test2->vFun1();
delete test2;
return 0;
}
- 首先动态的申请了一个 CTest 对象 test1,之后释放它,然后再动态申请一个 CTest 对象 test2,最后执行 test2 对象的 vFun1 函数
- 由于占坑效应,所以两次申请的堆空间地址是一样的:

- 开启断点调试,看看虚表指针是如何运作的
- 首先程序会将 8 压入栈中,8 表示的是类的大小,然后调用 operator new 函数申请堆空间 test1,调用完成之后堆空间的首地址会储存再 eax 中

- 这个可以看出刚刚申请的堆空间的首地址为 0x0085e6c0

- 之后调用类的构造函数,其中类对象的首地址会储存在 ecx 中,继续向下调试

- 到这里时,test1 所指向的堆空间已经被 operator delete 函数释放了,同样的 ecx 中储存的是 test1 对象的首地址

- 之后再以同样的方式申请堆空间 test2

- 最后调用 test2 对象的虚函数,具体步骤如下:
- 第一步将 test2 对象地址的头 4 个字节指向的地址的值放入 edx 当中;第二步将 test2 对象的首地址存放在 ecx 当中;第三步将之前的 edx 的值放入 eax 中,call eax


- 这个是 test2 对象的内存空间 0x00590498,头 4 个字节就是虚表指针

- 查询一下虚表,看看是否与 call eax 的值相对应,果然与 call eax 的值相对应

0x04 总结
- 从以上的实验可以看出申请堆空间的首地址会以指针方式存储,而申请的堆空间首地址就是类的首地址,比如 CTest *test1 = new CTest。如果类中有虚函数,那么类的头 4 个字节一定会是虚表指针,而且调用类的虚函数时会将虚表指针指向的值作为函数进行调用,也就是 call eax,这样的话就可以使用释放过的 test1 这个悬挂指针将 test2 的虚表指针进行覆盖,指向我们想要的数据,从而控制 call 指令,执行任意代码
int main(int argc, char **argv)
{
CTest *test1 = new CTest;
cout << "test1 指针指向的堆地址为: " << test1 << endl;
delete test1;
CTest *test2 = new CTest;
cout << "test2 指针指向的堆地址为: " << test2 << endl;
// 释放重引用 test1 将 test2 的头 4 个字节覆盖掉,达到执行任意代码的目的
test2->vFun1();
delete test2;
return 0;
}
以上对释放重引用漏洞的实验分析到此结束,如有错误,欢迎指正
参考资料:0day 安全 + 漏洞战争
堆栈上的舞蹈之释放重引用(UAF) 漏洞原理实验分析的更多相关文章
- Allocation-Free Collections(在堆栈上使用内存)
假设你有一个方法,通过创建临时的List来收集某些数据,并根据这些数据来统计信息,然后销毁这个临时列表.这个方法被经常调用,导致大量内存分配和释放以及增加的内存碎片.此外,所有这些内存管理都需要时间, ...
- WCF服务部署到IIS上,然后通过web服务引用方式出现错误的解决办法
本文转载:http://www.cnblogs.com/shenba/archive/2012/01/06/2313932.html 昨天在用IIS部署一个WCF服务时,碰到了如下错误: 理解了文档内 ...
- 如何判断一个C++对象是否在堆栈上(通过VirtualQuery这个API来获取堆栈的起始地址,然后就可以得到答案了),附许多精彩评论
昨天有人在QQ群里问到如何判断一个C++对象是否在堆栈上, 我在网上搜索了下, 搜到这个么一个CSDN的帖子http://topic.csdn.net/t/20060124/10/4532966. ...
- 重拾算法之复杂度分析(大O表示法)
.katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...
- 【分布式锁】01-使用Redisson实现可重入分布式锁原理
前言 主流的分布式锁一般有三种实现方式: 数据库乐观锁 基于Redis的分布式锁 基于ZooKeeper的分布式锁 之前我在博客上写过关于mysql和redis实现分布式锁的具体方案:https:// ...
- 可重入读写锁ReentrantReadWriteLock基本原理分析
前言 本篇适用于了解ReentrantLock或ReentrantReadWriteLock的使用,但想要进一步了解原理的读者.见于之前的分析都是借鉴大量的JDK源码,这次以流程图的形式代替源码,希望 ...
- Linux Kernel 空指针逆向引用拒绝服务漏洞
漏洞名称: Linux Kernel 空指针逆向引用拒绝服务漏洞 CNNVD编号: CNNVD-201306-449 发布时间: 2013-07-01 更新时间: 2013-07-01 危害等级: ...
- js基础进阶--图片上传时实现本地预览功能的原理
欢迎访问我的个人博客:http://www.xiaolongwu.cn 前言 最近在项目上加一个图片裁剪上传的功能,用的是cropper插件,注意到选择本地图片后就会有预览效果,这里整理一下这种预览效 ...
- Wireshark抓包分析TCP建立/释放链接的过程以及状态变迁分析
Wireshark抓包分析TCP建立/释放链接的过程以及状态变迁分析 一.介绍计算机网络体系结构 1.计算机的网络体系结构 在抓包分析TCP建立链接之前首先了解下计算机的网络通信的模型,我相信学习过计 ...
随机推荐
- 使用python模块plotdigitizer抠取论文图片中的数据
技术背景 对于各行各业的研究人员来说,经常会面临这样的一个问题:有一篇不错的文章里面有很好的数据,但是这个数据在文章中仅以图片的形式出现.而假如我们希望可以从该图片中提取出数据,这样就可以用我们自己的 ...
- 记录 Allsec 解题过程
开局打开URL:http://119.3.191.245:65532/#/allsecPlayGame,前去做游戏 游戏URL:http://119.3.191.245:8877/Login.php ...
- 【Azure Developer】Python 获取Micrisoft Graph API资源的Access Token, 并调用Microsoft Graph API servicePrincipals接口获取应用ID
问题描述 在Azure开发中,我们时常面临获取Authorization问题,需要使用代码获取到Access Token后,在调用对应的API,如servicePrincipals接口. 如果是直接调 ...
- .net 程序员的centos命令总结
1,ssh相关 在初始化一台云服务器的时候,第一件事情就是去把该关的门都关上,首先第一关就是禁用root登录,禁用密码登录,顺便改一下远程登录端口,让登录都通过ssh密钥对来进行,阿里云里有密钥对管理 ...
- SHA算法摘要处理
byte[] input="sha".getBytes();//待做消息摘要算法的原始信息,可以是任意字符串 MessageDigest sha=MessageDigest.get ...
- 在ASP.NET Core中用HttpClient(五)——通过CancellationToken取消HTTP请求
用户向服务器发送HTTP请求应用程序页面是一种非常可能的情况.当我们的应用程序处理请求时,用户可以从该页面离开.在这种情况下,我们希望取消HTTP请求,因为响应对该用户不再重要.当然,这只是实际应用 ...
- 【故障公告】阿里云 RDS SQL Server 数据库实例 CPU 100% 引发全站故障
非常抱歉,今天 8:48 开始,我们使用的阿里云 RDS SQL Server 数据库实例突然出现 CPU 100% 问题,引发全站故障,由此给您带来麻烦,请您谅解. 发现故障后立即进行主备切换,和 ...
- SqlServer视图的创建与使用
SqlServer系列之视图的创建与使用: 什么是视图? 视图的概述 在数据查询中,可以看到数据表设计过程中,考虑到数据的冗余度低.数据一致性等问题,通常对数据表的设计要满足范式的要求,因此也会造成一 ...
- Clang Static Analyzer-使用手册-编写Checker框架
Clang Static Analyzer-使用手册-编写Checker Checker是这个工具的灵魂 有了checker才可以检查你的代码 相当于就是CSA通过checker定义的检查方法去检查代 ...
- Python基础(十五):Python的3种字符串格式化,做个超全对比!
有时候,为了更方便.灵活的运用字符串.在Python中,正好有3种方式,支持格式化字符串的输出 . 3种字符串格式化工具的简单介绍 python2.5版本之前,我们使用的是老式字符串格式化输出%s. ...