zoukankan      html  css  js  c++  java
  • 堆栈上的舞蹈之释放重引用(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
    ", buf1);
    	
    	// 释放 buf1 的堆 
    	free(buf1);
    	
    	// 动态申请一个大小为 size 的堆空间 
    	buf2 = (char *)malloc(size);
    	
    	// 打印出 buf2 的指针指向的地址 
    	printf("buf2: 0x%p
    ", buf2);
    	
    	// 将 buf2 的堆空间赋值为 0 
    	memset(buf2, 0, size);
    	printf("buf2: %d 
    ", *buf2);
    	
    	printf("=== User After Free === 
    ");
    	
    	// 将 buf1 指向的内存拷贝为 hack 
    	strncpy(buf1, "hack", 5);
    	printf("buf2: %s
    
    ", 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 安全 + 漏洞战争

  • 相关阅读:
    STM32 HAL库 CUBEMX 定时器双通道 高精度捕获PWM波
    STM32的CAN过滤器-bxCAN的过滤器的4种工作模式以及使用方法总结
    FreeRTOS — 消息队列
    STM32CubeMX 定时器配置时钟中的auto-reload preload
    使用TortoiseGit连接GitLab
    STM32CubeMx 定时器实现 微妙级延迟函数
    STM32 Keil新建工程报错“Loading PDSC Debug Description Failed for STMicroelectronics STM32Lxxxxxxx”
    STM32CubmeMx 串口IDLE中断+DMA读取不定长数据
    云龙51单片机视频教程全套包含案例课件及资料
    推荐一本很好的51单片机书籍,适合新手入门学习。
  • 原文地址:https://www.cnblogs.com/csnd/p/11800515.html
Copyright © 2011-2022 走看看