堆栈上的舞蹈之释放重引用(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建立链接之前首先了解下计算机的网络通信的模型,我相信学习过计 ...
随机推荐
- 鸿蒙开源第三方件组件——轮播组件Banner
目录: 1.功能展示 2.Sample解析 3.Library解析 4.<鸿蒙开源第三方组件>系列文章合集 前言 基于安卓平台的轮播组件Banner(https://github.com/ ...
- 漏洞复现-ActiveMq反序列化漏洞(CVE-2015-5254)
0x00 实验环境 攻击机:Win 10 靶机也可作为攻击机:Ubuntu18 (docker搭建的vulhub靶场) 0x01 影响版本 Apache ActiveMQ 5.13.0之前 ...
- FreeBSD 入门 哲学与玄学
『哲学与玄学』 FreeBSD 是一种 UNIX 哲学(如模块化,一切皆文件等,见< UNIX 编程艺术>❩的发展,也是学院派的代表作品.她是一套工具集,她存在目的是为了让人们更好的生活. ...
- LZZ高级程序语言设计之多重for循环
public class Mq { public static void main(String args[]) { System.out.println("到底去还是不去呢?") ...
- PTE 准备之 Repeat sentence
Repeat sentence After listening to a sentence ,repeat the sentence 3-9 seconds 15 seconds Strategies ...
- python列表,元组,字典,集合的比较总结
这四个都是python中的序列,用于存放数据,他们区别总结如下: 列表list 元组tuple 字典dictionary 集合set 是否可变 可变 不可变 可变 可变 是否有序 有序 有序 无序 ...
- C# 通过ServiceStack 操作Redis——ZSet类型的使用及示例
Sorted Sets是将 Set 中的元素增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列 /// <summary> /// Sorted Sets是将 ...
- Net Core 重要的技术点
Net Core 重要的技术点 1.中间件概念 Asp.Net Core作为控制台应用程序启动,在Program的Main方法是入口,通过调用CreateWebHostBuilder创建WebHost ...
- Canal高可用架构部署
一.前言 canal 是阿里的一款开源项目,纯 Java 开发.基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了 MySQL(也支持 mariaDB). canal 模拟 mys ...
- 图文详解Java对象内存布局
作为一名Java程序员,我们在日常工作中使用这款面向对象的编程语言时,做的最频繁的操作大概就是去创建一个个的对象了.对象的创建方式虽然有很多,可以通过new.反射.clone.反序列化等不同方式来创建 ...