NonPagedPool BufferOverflow
type
status
date
slug
summary
tags
category
icon
password
前言
好久没更新博客了
常见的溢出有栈溢出,堆溢出;
依旧使用到的
HackSysExtremeVulnerableDriver
这个内核漏洞靶场基础知识 会贯穿文中,要是不大适应 建议专门去看些关于池的基础文章
漏洞点
在写驱动的时候 经常会用到
ExAllocatePoolWithTag
函数来分配内存比较常见的就是
NonPagedPool
和PagedPool
他们俩的区别有一点:
- 分页内存是低中断级别的例程可以访问的。
- 非分页内存则是各个中断级别的例程都可以使用的。
池溢出就是 上面说的函数 分配出来的内存缓冲区,被溢出了;
就跟malloc这种 申请出来的堆缓冲区 被溢出了一样;
利用手法些许不同
这里的IOCTL就是0x22200,用到与设备进行通信
call的这个函数 反编译不大准 还是看反汇编吧
从ring3传递过来的,inputBuufer还有对应的大小传递过去
call的这个函数 用f5来看 就非常轻松了
只不过这个溢出 是一个池溢出;和栈溢出 堆溢出 是不同的
缓冲区的大小是0x1F8,
我们编写一个三环程序,来做测试
别忘了下断点,bu HEVD!TriggerBufferOverflowNonPagedPool,然后再给拷贝函数 打个断点
这里是我们的池内存所在的地方,但是是0x200,比我们的0x1F8;多了8字节
这8字节 就类似于堆里的块首;
用 ExAllocatePoolWithTag分配不超过 0x1000字节长度的池内存块时,会使用到 POOL_HEADER 结构
- PreviousSize: 前一个chunk的BlockSize,要乘于8
- PoolIndex : 所在大pool的pool descriptor的index。这是用来检查释放pool的算法是否释放正确了。
- PoolType: Free=0,Allocated=(PoolType|2)
- PoolTag: 4个可打印字符,标明由哪段代码负责。(4 printable characters identifying the code responsible for the allocation)
等memcpy函数调用完后,查看这块地址
可以看到 头8个字节,是没有被0x61填充的
当前的也就是
看下他下一个的池块
0x40 * 0x8 = 0x200 也就是我们上一个
86eb5a78
的大小假如我们填充0x200个pad呢,是不是就破坏了 下一个池的
_POOL_HEADER
可以看到
867a4588
这个池的信息 就没有成功显示出来,在没显示出来的时候 有可能会busy一会接着g就会BSoD了
搜下这个error code都知道是啥
此时poc换为
然后还是打断点
此时看我们 池的附近 都是Even,也就是事件
看下我们下一个Even的池头
这张图 对理解KEVENT对象 没那么好! 我推荐去看学习链接1里有一张图,是作者自己画的 KEVENT对象在池中的结构
然后要说下
_OBJECT_HEADER
这个结构体,代表着一个内核对象头所以pool 的base + 0x18 也就是OBJECT_HEADER的地方
一共0x40个大小
红色:
_pool_header
紫色:
OBJECT_HEADER_QUOTA_INFO
蓝色:
_OBJECT_HEADER
绿色:
_OBJECT_BODY
偏移也就是0xC,所在obTypeIndexTable表中的偏移为0xC;InfoMask就是0x8(掩码),结构也就是OBJECT_HEADER_QUOTA_INFO
obTypeIndexTable表是指把所有的对象类型放在了一个表里(Win7),而xp直接在OBJECT_HEADER里保存了POBJECT_TYPE指针;这是一个区别
这个就是我们的那个Event对象,可以了解下_object_type这个结构
然后我从网上 找了对应属性的解释
接下来的地址 有可能和上面不同了 因为 每次申请 都会更换地址;这不是一次下来的
主要看
+0x028 TypeInfo
这个结构里的数据结构中从偏移0x030开始就保存了各种回调例程
其中的CloseProcedure指定了当Event对象被释放的时候要调用的例程(0x38+0x28=0x60)。
利用手法:
- 淹没后面Event对象的
TypeIndex
,置0
- 这样这个事件对象就会从偏移0处找对象类型,
obTypeIndexTable
的偏移为0的值就是0,我们可以在0地址申请一块内存放置一块我们自己构造的事件对象
- 事件对象偏移0x60的CloseProcedure此时就可以由我们指定,将其指定为ShellCode的地址
- 当程序调用CloseHandle关闭句柄的时候,就会调用这个关闭例程,也就会执行ShellCode
既然是回调的,所以我们替换CloseProcedure在_object_type结构里的数据,为shellcode的地址,在释放后 就会调shellcode了
- 当时我以为要构造完整的
_object_type
对象在0地址上,后来从文章中学到 只需要在0x60处 放上shellcode的地址就可以
- 还有一个点 就是覆盖我们池缓冲区 后面的Event对象的
TypeIndex
为0
但是 独立写这种Exploit我目前还是欠缺的,所以还是跟着1900师傅的代码跟着学把
当然exp的写法很多,公开的 我觉得这个Exp是最”好看”的,比较规范 各种结构一一对应
学习Exp
与驱动通信什么的 就不写了
先看下 定义的结构体
最后组成了
KEVENT
这么一个结构体也就对应着上面提到的
从main函数开始看
这个很简单AllocateEvent函数是就是喷射Event的,SetZeroMemory函数就是在0地址 距离0x60处 放上shellcode的地址
目的就是让我们的池块的下一个池块 为Event对象
参考资料
学习链接:
基础知识文章:
https://saturn35.com/2019/07/22/翻译-Kernel-Pool-Exploitation-on-Windows-7-By-Tarjei-Mandt/