-
-
[原创]内核漏洞学习[3]-HEVD-UAF
-
发表于: 2021-10-31 18:38 11556
-
HEVD:漏洞靶场,包含各种Windows内核漏洞的驱动程序项目,在Github上就可以找到该项目,进行相关的学习
Releases · hacksysteam/HackSysExtremeVulnerableDriver · GitHub
环境准备:
Windows 7 X86 sp1 虚拟机
使用VirtualKD和windbg双机调试
HEVD 3.0+KmdManager+DubugView
简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况
而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。
运行结果如下
简单讲就是第一次申请的内存空间在释放过后没有进行内存回收,导致下次申请内存的时候再次使用该内存块,使得以前的内存指针可以访问修改过的内存。
用户空间是从“堆(Heap)”分配缓冲区的,内核中也有类似的机制,但是不叫“堆”而称为“池(Pool)”。不过二者有着重要的区别。
首先,用户空间的堆是属于进程的;而内核中的池则是全局的,属于整个系统。堆所占的是虚存空间,堆的扩充基本上体现在作为后备存储的页面倒换文件的扩充,而不必同时占用那么多的物理页面。而内核中的池则分成两种:一种是所占页面不可倒换的,每个页面不管其是否受到访问都真金白银地占着物理页面,要求这种池的大小可扩充显然不现实。另一种是所占页面可以倒换的,这种池的大小倒是可以扩充,因为(已分配而)暂时不受访问的(虚存)页面可以被倒换到作为后备的页面倒换文件中。
换页内存池和非换页内存池则是提供给系统内核模块和设备驱动程序使用的。在换页内存池中分配的内存有可能在物理内存紧缺的情况下被换出到外存中;而非换页内存池中分配的内存总是处于物理内存中。
windows内核中定义了许多不同的池
不过,实际使用的基本上就是NonPagedPool 和 PagedPool 两个。
MilnitializeNonPagedPool函数确定非换页内存池的起始和结束物理页面帧MiStartOfInitialPoolFrame 和 MiEndOfInitialPoolFrame,
一旦非换页内存池的结构已建立,接下来系统代码可以通过 MiAllocatePoolPages 和MiFreePoolPages 函数来申请和归还页面。
Windows充分利用这些页面自身的内存来构建起一组空闲页面链表,每个页面都是一个MMFREE_POOL_ENTRY结构,如图所示。MiInitializeNonPagedPool函数已经把数组MmNonPagedPoolFreeListHead初始化成只包含---个完整的空闲内存块,该内存块包括所有的非换页页面。
在非换页内存池的结构中,每个空闲链表中的每个节点包含1、2、3、4或4个以上的页面,在同一个节点上的页面其虚拟地址空间是连续的。第一个页面的List域构成了链表结构,Size域指明了这个节点所包含的页面数量,Owner域指向自己;后续页面的List和 Size域没有使用,但Owner域很重要,它指向第一个页面。
非换页内存池中的页面回收是通过MiFreePoolPages函数来完成的,
内核和设备驱动程序使用非分页池来存储系统无法处理页面错误时可能访问的数据,非页面缓冲池总是保持在物理内存中,非页面缓冲池虚拟内存被分配物理内存。存储在非分页池中的通用系统数据结构包括表示进程和线程的内核和对象,互斥对象,信号灯和事件等同步对象,表示为文件对象的文件引用以及I / O请求包(IRP)代表I / O操作
漏洞原理:UAf漏洞,当一个内存块被释放之后再次被使用,UseAfterFreeNonPagedPool.c中存在释放后没有被设置为 NULL 的内存指针: g_UseAfterFreeObjectNonPagedPool
(1)加载驱动
安装驱动程序,使用kmdManager 加载驱动程序,DebugView检测内核,可以看到加载驱动程序成功。
windbg:
(2)分析漏洞点
对UseAfterFreeNonPagedPool.c源码进行分析,漏洞函数FreeUaFObjectNonPagedPool,存在明显的UAF漏洞,不安全版本中内存指针g_UseAfterFreeObjectNonPagedPool,调用ExFreePoolWithTag函数对非换页内存池的页面回收后 ,没有把该指针g_UseAfterFreeObjectNonPagedPool=NULL,自然安全版本有g_UseAfterFreeObjectNonPagedPool=NULL这个过程
如果申请内存的大小和UAF中内存的大小相同,那么可能申请到刚释放的内存池,构造好了(fake_uaf )中的数据,那么当释放uaf之后,通过fake_uaf 使得uaf 就会指向我们payload的位置,再次使用uaf->payload 从而达到提取的效果。电脑中有许许多多的空闲内存,如果只申请一次内存,不一定申请到释放的那块内存,所以要申请很多次跟释放的内存相同大小的内存。在内核中,换页内存池(PagedPool)和非换页内存池(NonPagedPool)则是提供给系统内核模块和设备驱动程序使用的。对于NonPagedPool,使用ExAllocatePoolWithTag
和ExFreePoolWithTag
函数 申请和释放。
(1)相关的IO控制码定义:
IO控制码对应的分发函数
HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL------AllocateUaFObjectNonPagedPoolIoctlHandler--AllocateUaFObjectNonPagedPool
HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL---AllocateFakeObjectNonPagedPoolIoctlHandler--AllocateFakeObjectNonPagedPool()
HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL---FreeUaFObjectNonPagedPoolIoctlHandler-- FreeUaFObjectNonPagedPool()
HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL---UseUaFObjectNonPagedPoolIoctlHandler--UseUaFObjectNonPagedPool
以下关键代码只放出关键部分,具体看源码,全部贴出来太多了
AllocateUaFObjectNonPagedPool函数:
AllocateFakeObjectNonPagedPool函数:允许在非分页内存池上分配一个假的的对象;该函数允许我们把假的对象分配到原本的已经释放的UAF对象所在的位置上。
FreeUaFObjectNonPagedPool
UseUaFObjectNonPagedPool:由于存在UAF漏洞,利用g_UseAfterFreeObjectNonPagedPool->Callback();执行payload。。
payload功能:遍历进程,得到系统进程的token,把当前进程的token替换,达到提权目的。(跟前两篇一样的原理,替换token)
相关内核结构体:
在内核模式下,fs:[0]指向KPCR结构体
payload:
提权成功:
g_UseAfterFreeObjectNonPagedPool = NULL,free之后,将内存指针设置为NULL。
《Windows内核原理与实现》
《Windows内核情景分析》
https://www.fuzzysecurity.com/tutorials/expDev/19.html
https://bbs.pediy.com/thread-252310.htm
https://bbs.pediy.com/thread-247019.htm
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/use-after-free/#_4
/
/
linux
#include <stdio.h>
#include <cstdlib>
#include <string.h>
int
main()
{
char
*
p1;
p1
=
(char
*
) malloc(sizeof(char)
*
10
);
/
/
申请内存空间
memcpy(p1,
"hello"
,
10
);
printf(
"p1 addr:%x,%s\n"
,p1,p1);
free(p1);
/
/
释放内存空间
char
*
p2;
p2
=
(char
*
)malloc(sizeof(char)
*
10
);
/
/
二次申请内存空间,与第一次大小相同,申请到了同一块内存
memcpy(p1,
"world"
,
10
);
/
/
对内存进行修改
printf(
"p2 addr:%x,%s\n"
,p2,p1);
/
/
验证
return
0
;
}
/
/
1.
指针p1申请内存,打印其地址值
/
/
2.
然后释放p1
/
/
3.
指针p2申请同样大小的内存,打印p2的地址,p1指针指向的值
/
/
linux
#include <stdio.h>
#include <cstdlib>
#include <string.h>
int
main()
{
char
*
p1;
p1
=
(char
*
) malloc(sizeof(char)
*
10
);
/
/
申请内存空间
memcpy(p1,
"hello"
,
10
);
printf(
"p1 addr:%x,%s\n"
,p1,p1);
free(p1);
/
/
释放内存空间
char
*
p2;
p2
=
(char
*
)malloc(sizeof(char)
*
10
);
/
/
二次申请内存空间,与第一次大小相同,申请到了同一块内存
memcpy(p1,
"world"
,
10
);
/
/
对内存进行修改
printf(
"p2 addr:%x,%s\n"
,p2,p1);
/
/
验证
return
0
;
}
/
/
1.
指针p1申请内存,打印其地址值
/
/
2.
然后释放p1
/
/
3.
指针p2申请同样大小的内存,打印p2的地址,p1指针指向的值
p1 addr:
222e010
,hello
p1 addr:
222e010
,world
p1 addr:
222e010
,hello
p1 addr:
222e010
,world
lm 查看所有已加载模块
lm m H
*
设置过滤,查找HEVD模块
lm m HEVD
lm 查看所有已加载模块
lm m H
*
设置过滤,查找HEVD模块
lm m HEVD
ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
g_UseAfterFreeObjectNonPagedPool
=
NULL;
/
/
安全版本。
ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
/
/
不安全版本
ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
g_UseAfterFreeObjectNonPagedPool
=
NULL;
/
/
安全版本。
ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
/
/
不安全版本
IrpDeviceIoCtlHandler(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PIRP Irp
)
{
ULONG IoControlCode
=
0
;
PIO_STACK_LOCATION IrpSp
=
NULL;
NTSTATUS Status
=
STATUS_NOT_SUPPORTED;
UNREFERENCED_PARAMETER(DeviceObject);
PAGED_CODE();
IrpSp
=
IoGetCurrentIrpStackLocation(Irp);
IoControlCode
=
IrpSp
-
>Parameters.DeviceIoControl.IoControlCode;
if
(IrpSp)
{
switch (IoControlCode)
{
case HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL:
DbgPrint(
"****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
Status
=
AllocateUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
DbgPrint(
"****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
break
;
case HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL:
DbgPrint(
"****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
Status
=
UseUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
DbgPrint(
"****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
break
;
case HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL:
DbgPrint(
"****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
Status
=
FreeUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
DbgPrint(
"****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
break
;
case HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL:
DbgPrint(
"****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n"
);
Status
=
AllocateFakeObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
DbgPrint(
"****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n"
);
break
;
}
}
IrpDeviceIoCtlHandler(
_In_ PDEVICE_OBJECT DeviceObject,
_In_ PIRP Irp
)
{
ULONG IoControlCode
=
0
;
PIO_STACK_LOCATION IrpSp
=
NULL;
NTSTATUS Status
=
STATUS_NOT_SUPPORTED;
UNREFERENCED_PARAMETER(DeviceObject);
PAGED_CODE();
IrpSp
=
IoGetCurrentIrpStackLocation(Irp);
IoControlCode
=
IrpSp
-
>Parameters.DeviceIoControl.IoControlCode;
if
(IrpSp)
{
switch (IoControlCode)
{
case HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL:
DbgPrint(
"****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
Status
=
AllocateUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
DbgPrint(
"****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
break
;
case HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL:
DbgPrint(
"****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
Status
=
UseUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
DbgPrint(
"****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
break
;
case HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL:
DbgPrint(
"****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
Status
=
FreeUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
DbgPrint(
"****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n"
);
break
;
case HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL:
DbgPrint(
"****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n"
);
Status
=
AllocateFakeObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
DbgPrint(
"****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n"
);
break
;
}
}
NTSTATUS
AllocateUaFObjectNonPagedPool(
VOID
)
{
__try
{
DbgPrint(
"[+] Allocating UaF Object\n"
);
/
/
ExAllocatePoolWithTag 函数申请非分页内存池,
UseAfterFree
=
(PUSE_AFTER_FREE_NON_PAGED_POOL)ExAllocatePoolWithTag(
NonPagedPool,
sizeof(USE_AFTER_FREE_NON_PAGED_POOL),
(ULONG)POOL_TAG
);
RtlFillMemory((PVOID)UseAfterFree
-
>
Buffer
, sizeof(UseAfterFree
-
>
Buffer
),
0x41
);
/
/
用‘A’填充
UseAfterFree
-
>
Buffer
[sizeof(UseAfterFree
-
>
Buffer
)
-
1
]
=
'\0'
;
/
/
添加一个空终止符
UseAfterFree
-
>Callback
=
&UaFObjectCallbackNonPagedPool;
/
/
设置一个回调指针,这里是利用点,我们要将自己的payload写到这里
g_UseAfterFreeObjectNonPagedPool
=
UseAfterFree;
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
return
Status;
}
NTSTATUS
AllocateUaFObjectNonPagedPool(
VOID
)
{
__try
{
DbgPrint(
"[+] Allocating UaF Object\n"
);
/
/
ExAllocatePoolWithTag 函数申请非分页内存池,
UseAfterFree
=
(PUSE_AFTER_FREE_NON_PAGED_POOL)ExAllocatePoolWithTag(
NonPagedPool,
sizeof(USE_AFTER_FREE_NON_PAGED_POOL),
(ULONG)POOL_TAG
);
RtlFillMemory((PVOID)UseAfterFree
-
>
Buffer
, sizeof(UseAfterFree
-
>
Buffer
),
0x41
);
/
/
用‘A’填充
UseAfterFree
-
>
Buffer
[sizeof(UseAfterFree
-
>
Buffer
)
-
1
]
=
'\0'
;
/
/
添加一个空终止符
UseAfterFree
-
>Callback
=
&UaFObjectCallbackNonPagedPool;
/
/
设置一个回调指针,这里是利用点,我们要将自己的payload写到这里
g_UseAfterFreeObjectNonPagedPool
=
UseAfterFree;
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
return
Status;
}
NTSTATUS
AllocateFakeObjectNonPagedPool(
_In_ PFAKE_OBJECT_NON_PAGED_POOL UserFakeObject
)
{
__try
{
DbgPrint(
"[+] Creating Fake Object\n"
);
/
/
ExAllocatePoolWithTag分配内存
KernelFakeObject
=
(PFAKE_OBJECT_NON_PAGED_POOL)ExAllocatePoolWithTag(
NonPagedPool,
sizeof(FAKE_OBJECT_NON_PAGED_POOL),
(ULONG)POOL_TAG
);
ProbeForRead(
(PVOID)UserFakeObject,
sizeof(FAKE_OBJECT_NON_PAGED_POOL),
(ULONG)__alignof(UCHAR)
);
/
/
ProbeForRead常规检查用户模式缓冲区是否实际上位于地址空间的用户部分,并且正确对齐
RtlCopyMemory(
(PVOID)KernelFakeObject,
(PVOID)UserFakeObject,
sizeof(FAKE_OBJECT_NON_PAGED_POOL)
);
KernelFakeObject
-
>
Buffer
[sizeof(KernelFakeObject
-
>
Buffer
)
-
1
]
=
'\0'
;
DbgPrint(
"[+] Fake Object: 0x%p\n"
, KernelFakeObject);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
return
Status;
}
NTSTATUS
AllocateFakeObjectNonPagedPool(
_In_ PFAKE_OBJECT_NON_PAGED_POOL UserFakeObject
)
{
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
- [原创]CVE-2017-11882分析和白象样本分析 8280
- 银狐样本分析 12986
- [原创]CS[1]exe木马分析 7767
- [原创]内核漏洞学习[6]-HEVD-UninitializedStackVariable 27948
- [原创]植物大战僵尸外挂实现 13107