Windows下堆保护机制原理及其绕过

2024-04-09 16:04

本文主要是介绍Windows下堆保护机制原理及其绕过,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前面我们介绍了很多Windows下的保护机制,我们知道Windows对于栈,做出了很多保护机制,那是因为栈上的溢出是很容易的,而对于存在于堆上的漏洞,通常利用起来都比较困难,那我们就来详细看看Windows对于堆做出了哪些保护:

一.堆保护机制详解

微软在堆中也增加了很多的安全校验操作,其中包括:

  1. PEB random:这也就是我们说的PEB机制随机化,我们在介绍ASLR保护机制的时候也提到过,PEB随机化后,就可以在一定程度上抵御攻击存储在PEB中的函数指针,回想一下,在我们DWORD SHOOT的时候,修改PEB中的函数指针,是不是相对来说比较简单?
  2. Safe Unlink:我们知道堆溢出的原理,是因为在双向链表进行拆卸或者合并的时候,不安全的指针修改,具体就像下面这样:
int remove(ListNode* node){node->blink->flink = node->flink;node->flink->blink = node->blink;
}

如果大家学过数据结构,并且自己写过代码,相信这段代码对你来说很熟悉吧?因为我们也经常写出这样的代码。
那我们要如何修改这段代码,让其变得安全呢?我们可以在链表操作之前,验证前向指针和后向指针的完整性,这样可以方式发生DWORD SHOOT,就像这样:

int safe_remove(ListNode* Node){if((node->blink->flink == node) && (node->flink->blink == node)){//在这里完成链表的操作,和上面的差不多}else{//如果进入到了这里,说明链表不完整,进入异常}
}

这样,我们就可以在链表操作之前,验证是否发生溢出。

  1. heap cookie:
    还记得在栈上的Security Cookie吗?我们在堆中也可以使用Security Cookie,用来检测是否发生了堆溢出。
    我们知道在内存中的堆区,有堆首的存在,heap cookie就存储在堆首。
  2. 元数据加密
    微软在操作系统中,将块首中的一些重要数据保存的时候,会与一个4字节的随机数进行异或运算,在使用的时候再还原回来,这样我们就不可以直接破坏这些数据了。

二.堆保护机制绕过方式分析

即使Windows的保护机制有多么完善,总是会有人提出绕过方式的,我们就来看看对于堆的保护,我们如何进行突破:

  1. 不知道大家有没有发现一个问题,就是在堆保护机制中,仅仅是对堆块中的重要数据进行了保护,并没有对堆中存储的内容进行保护,那我们就可以去修改堆中保护的数据了。
  2. 利用chunk重新设置堆块大小:
    我们前面介绍过了,Safe Unlink的精髓就在于:从链表中拆卸的时候,对指针进行完整性验证,但是这里有一个很大的问题,就是在链表拆卸的时候,它会检测,在链表插入的时候,它又不检测了,那就给我们了很好的攻击机会。
    使用这种方式攻击的话,我们就要知道在什么时候,链表才会发生插入操作:
    <1>内存释放后,chunk不再被使用的时候会重新链入链表。
    <2>当chunk的内存空间大于申请的空间的时候,剩余的空间,就会被拆分,建立成一个新的chunk,链入表中。
    这里的第二种方法,给了我们攻击机会。

三.堆保护机制绕过详解

1.利用chunk重设大小进行攻击

我们大家都知道从FreeList[0]上申请空间的过程:
<1>.将FreeList[0]上最后一个chunk的大小与申请的大小进行比较,如果大,则继续分配,如果小,就会拓展空间
<2>.从FreeList[0]的第一个chunk开始,进行检测,知道找到符合要求的chunk,然后将这个chunk拆卸下来
<3>.分配好空间后,如果chunk还有剩余空间,剩余的空间就会被建立成一个新的chunk,并且插入到链表中
我们来细细分析一下这三个步骤:第一步,我们貌似没有攻击机会、第二步,如果我们覆盖掉Safe Unlink,第二步就会被检测出来,第三步就更不用说了
这样看来,我们似乎真的没有攻击机会了。但是Safe Unlink中存在一个让人疑惑的问题:就算Safe Unlink检测到chunk被破坏了,但是它还允许后续的一些操作执行,例如重设chunk的大小。
我们写一段程序,来看看重设chunk的具体过程:

#include <stdio.h>
#include <windows.h>int main(){HLOCAL h1;HANDLE hp;hp = HeapCreate(0,0x1000,0x10000);__asm{int 3;}h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,0x10);return 0;
}

我们将我们的调试器设置为默认调试器:
在这里插入图片描述
然后直接运行程序,程序发生异常的时候,会自动被附加到调试器。
然后我们来看看插入chunk的精髓汇编代码:

lea eax,dword ptr ds:[edi+8]   ;获取新chunk的Flink位置
mov dword ptr ss:[ebp - f0]
mov edx,dword ptr ds:[ecx + 4] ;获取下一个chunk中的Blink的值
mov dword ptr ss:[ebp - f8],edx
mov dword ptr ds:[eax],ecx     ;保存新的chunk的Flink
mov dword ptr ds:[eax + 4],edx ;保存新的chunk的Blink
mov dword ptr ds:[edx],eax     ;保存下一chunk中的Blink->Flink的Flink
mov dword ptr ds:[ecx + 4],eax ;保存下一chunk中的Blink

将这一过程,使用伪代码形式,就是这样:

新chunk -> Flink = 旧chunk -> Flink
新chunk -> Blink = 旧chunk -> Flink -> Blink
旧chunk -> Flink -> Blink = 新chunk
旧chunk -> Flink -> Blink = 新chunk

执行完上面的汇编代码之后,看看内存中FreeList[0]的链表结构,就会发现已经改变了。
这样,了解了重设chunk插入链表之后,我们该如何攻击呢?大家考虑一下,如果说,我们将旧chunk的Flink和Blink都覆盖掉了,那么会发生什么情况???
实际上,相信大家已经发现了,这实际上就是DWORD SHOOT,而Safe Unlik的验证不严密为这样的攻击打开了一扇大门。
这里写出一个带有漏洞的程序:

#include <stdio.h>
#include <windows.h>int main(){char ShellCode[500]{0};HANDLE hFile = CreateFileA("111.txt",GENERIC_READ,NULL, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);DWORD dwReadSize = 0;ReadFile(hFile, ShellCode, 300, &dwReadSize, NULL);HLOACL h1 ,h2;HANDLE Handle;Handle = HeapCreate(0,0x1000,0x10000);__asm{int 3;}h1 = HeapAlloc(Handle,HEAP_ZERO_MEMORY,16);memcpy(h1,ShellCode,300);h2 = HeapAlloc(Handle,HEAP_ZERO_MEMORY,16);int zero = 0;zero = 5/zero;return 0;
}

我们来观察一下这个程序:主函数中,存在明显的堆溢出,我们可以利用这个漏洞来完成攻击。
这里给出攻击思路:
<1>.堆刚初始化,所以我们申请的堆是从FreeList[0]中申请的,从FreeList[0]中拆卸下来的chunk在分配好后将剩余的空间新建一个chunk插入到FreeList[0]中,这样的话,h1后面就会有一大段空闲的空间
<2>.向h1中复制数据之后,超过16字节空间就会覆盖后面的chunk块首
<3>.后面的chunk块首被覆盖了,当h2再申请空间的时候,程序就会从破坏了的chunk中申请空间,并将剩余的空间新建为一个chunk并插入到FreeList[0]中
那我们将chunk的Flink和Blink伪造一下,在新的chunk中插入FreeList[0]的时候,,就可以实现任意地址写了
最后,程序制造出了除零异常,我们使用跳板,劫持流程,让程序转入ShellCode执行。
我们的chunk这样布置:
| 0x90填充 | chunk块首前8个字节 | 覆盖用的Flink和Blink | 0x90填充 | 跳板指令 | 伪造的块首 | 伪造的Flink和Blink | ShellCode |
再次打开程序,发现程序执行流程已经被我们成功劫持。
如果这里的介绍大家看的不是很懂的话,可以参考一下这篇文章:利用Chunk重设大小攻击堆

2.利用Lookaside表进行攻击

我们知道Safe Unlink只对堆表中的空表进行了双向链表的验证,但是没有对块表中的单链表进行验证,那我们就可以去攻击单链表。
借鉴前面的任意地址写固定地址的思路,如果控制单链表操作中的node->next,我们就可以控制Lookaside[n] - > next了,当用户再次申请空间的时候,系统就会将这个伪造的地址当作申请空间的地址返回,我们只要向该内存空间写入数据,就会留下溢出隐患。
我们来写一个带有漏洞的程序:

#include <stdio.h>
#include <windows.h>char ShellCode[300];int main(){HLOCAL h1,h2,h3;HANDLE Handle;Handle = HeapCreate(0,0,0);__asm{int 3;}h1 = HeapAlloc(Handle,HEAP_ZERO_MEMORY,16);h2 = HeapAlloc(Handle,HEAP_ZERO_MEMORY,16);h3 = HeapAlloc(Handle,HEAP_ZERO_MEMORY,16);HeapFree(Handle,0,h3);HeapFree(Handle,0,h2);memcpy(h1,ShellCode,300);h2 = HeapAlloc(Handle,HEAP_ZERO_MEMORY,16);h3 = HeapAlloc(Handle,HEAP_ZERO_MEMORY,16);memcpy(h3,"\x90\x1E\x39\X00",4);int zero = 0;zero = 5/zero;return 0;
}

我们来看看这个程序,程序中存在明显的堆溢出,首先申请3块16字节的空间,然后将其释放,这样它就存在于块表中了,这样我们下一次申请的时候就可以从块表中分配了。通过向h1中复制内存,就可以完成溢出,覆盖掉h2块首中,下一块的指针。我们申请空间的时候,下一块地址就会被赋值给Lookaside[2] -> next,当我们再次申请空间的时候,系统就会将我们伪造的地址作为内存首地址返回。
这里给出我的Payload布置方法:
| 短跳转指令 | \x90填充 | 模拟块首 | 默认异常处理指针 | \x90填充 | ShellCode |
这里由于在堆上,不同计算机的地址差异较大,这里就不再做演示了。如果这里看不懂的话,可以参考这篇文章:重重保护下的堆
然后我们就会发现我们成功绕过了保护,并且成功执行了ShellCode。

这篇关于Windows下堆保护机制原理及其绕过的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/888597

相关文章

Spring @Scheduled注解及工作原理

《Spring@Scheduled注解及工作原理》Spring的@Scheduled注解用于标记定时任务,无需额外库,需配置@EnableScheduling,设置fixedRate、fixedDe... 目录1.@Scheduled注解定义2.配置 @Scheduled2.1 开启定时任务支持2.2 创建

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

基于Python开发Windows屏幕控制工具

《基于Python开发Windows屏幕控制工具》在数字化办公时代,屏幕管理已成为提升工作效率和保护眼睛健康的重要环节,本文将分享一个基于Python和PySide6开发的Windows屏幕控制工具,... 目录概述功能亮点界面展示实现步骤详解1. 环境准备2. 亮度控制模块3. 息屏功能实现4. 息屏时间

在Windows上使用qemu安装ubuntu24.04服务器的详细指南

《在Windows上使用qemu安装ubuntu24.04服务器的详细指南》本文介绍了在Windows上使用QEMU安装Ubuntu24.04的全流程:安装QEMU、准备ISO镜像、创建虚拟磁盘、配置... 目录1. 安装QEMU环境2. 准备Ubuntu 24.04镜像3. 启动QEMU安装Ubuntu4

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

Maven 配置中的 <mirror>绕过 HTTP 阻断机制的方法

《Maven配置中的<mirror>绕过HTTP阻断机制的方法》:本文主要介绍Maven配置中的<mirror>绕过HTTP阻断机制的方法,本文给大家分享问题原因及解决方案,感兴趣的朋友一... 目录一、问题场景:升级 Maven 后构建失败二、解决方案:通过 <mirror> 配置覆盖默认行为1. 配置示

Windows的CMD窗口如何查看并杀死nginx进程

《Windows的CMD窗口如何查看并杀死nginx进程》:本文主要介绍Windows的CMD窗口如何查看并杀死nginx进程问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Windows的CMD窗口查看并杀死nginx进程开启nginx查看nginx进程停止nginx服务