堆栈上的舞蹈之释放重引用(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建立链接之前首先了解下计算机的网络通信的模型,我相信学习过计 ...
随机推荐
- Java 面向对象 03
面向对象·三级 代码块的概述和分类 * A:代码块概述 * 在Java中,使用 { } 括起来的代码被称为代码块. * B:代码块分类 * 根据其位置和声明的不同,可以分为局部代码块, ...
- 2020年12月-第02阶段-前端基础-CSS Day03
CSS Day03 盒子模型(CSS重点) css学习三大重点: css 盒子模型 . 浮动 . 定位 主题思路: 理解: 1.能说出盒子模型有那四部分组成 2.能说出内边距的作用以及对盒子的影响 3 ...
- MD摘要算法
import static org.junit.Assert.*; import java.security.MessageDigest; //消息摘要 public class MDCoder { ...
- AES加密--适用于RC2、RC4和Blowfish
package test; import java.security.GeneralSecurityException; import java.security.Key; import javax. ...
- 安装anaconda和第三方库tushare
安装anaconda和第三方库tushare 血泪教训 下载32位的anaconda(同你Python版本,不然会碰到第三方库无法import的问题) 安装anaconda 安装到C盘会比较快,安装到 ...
- 攻防世界 reverse leaked-license-64
mark一下,以后分析 原文:http://sibears.ru/labs/ASIS-CTF-Quals-2016-Leaked_License/ [ASIS CTF Quals 2016] - 泄露 ...
- python3 输出字符
import string'''whitespace -- a string containing all ASCII whitespaceascii_lowercase -- a string co ...
- 深入理解Java并发框架AQS系列(三):独占锁(Exclusive Lock)
一.前言 优秀的源码就在那里 经过了前面两章的铺垫,终于要切入正题了,本章也是整个AQS的核心之一 从本章开始,我们要精读AQS源码,在欣赏它的同时也要学会质疑它.当然本文不会带着大家逐行过源码(会有 ...
- 在ASP.NET Core中用HttpClient(五)——通过CancellationToken取消HTTP请求
用户向服务器发送HTTP请求应用程序页面是一种非常可能的情况.当我们的应用程序处理请求时,用户可以从该页面离开.在这种情况下,我们希望取消HTTP请求,因为响应对该用户不再重要.当然,这只是实际应用 ...
- 历史性突破:使用 .net core 日处理消息量超过 1.7 万条!
业余时间用 .net core 写了一个在线客服系统.并在博客园写了一个系列的文章,写介绍这个开发过程: .net core 和 WPF 开发升讯威在线客服系统:目录 https://blog.she ...